|
I would like to contribute some choice (or improvement) of the control, as I have benefitted a lot form your
work. Thank you.
This is to allow formatting of the control.
Note: I make the conversion to accept user's statement, although it is not necessary for the formattin alone.
Note: You may simply it if you do not want to accept user statement.
Note: I have check with different formatting statements, but I have not check "ALL" possibilities.
Note: Please feel free to use the functions.
Note: If anyone finds any error or improvement of the logarithm, please let me know.
Note: notify me at (hshliang@hotmail.com). Thank you.
To add the Formatting function:
1) Add these definitions to class CDateTimeEditCtrl in DateTimeEditCtrl.h
under class CDateTimeEditCtrl:
public:
CString ConvertDateFormatString(LPCTSTR szFormat);
protected:
CString _convert(LPCTSTR szformat);
2) Add these functions to class CDateTimeEditCtrl in DateTimeEditCtrl.cpp
CString CDateTimeEditCtrl::ConvertDateFormatString(LPCTSTR szFormat)
{
//use string for find_first_of functions.. etc
CString fformat; //final string
string sformat = szFormat;
//Parse '' (added text) and format strings
int nQuote1 = 0;
int nQuote2 = 0;
CStringArray saT; //to hold statements
CStringArray saF; //to hold format strings
//search first (')
nQuote1 = sformat.find_first_of('\'',0);
//if one is found, read the string and skip to next quote
//if no quote found, start with first char
if(nQuote1 == -1) nQuote1 = 0;
else nQuote1 += 1; //start with first char after quote
while(nQuote1 < sformat.size() && nQuote2 < sformat.size()){
nQuote2 = sformat.find_first_of('\'',nQuote1);
//if no Quote2 found, use last
if(nQuote2 == -1 || nQuote2 >= sformat.size()){
nQuote2 = sformat.size();
saF.Add(sformat.substr(nQuote1, nQuote2 - nQuote1).c_str());
}
else{ //second quote found, read the format string, then the following text
saT.Add(sformat.substr(nQuote1, nQuote2 - nQuote1).c_str());
//next
nQuote1 = sformat.find_first_of('\'',nQuote2+1);
//if next quote not found, use last char
if(nQuote1 == -1) nQuote1 = sformat.size();
else nQuote1++;
//assume format string if nQuote2 != sformat.size() //last letter
saF.Add(sformat.substr(nQuote2 + 1, nQuote1 - nQuote2 - 2).c_str());
}
}
//form final string
for( int i = 0; i < _MAX(saT.GetSize(), saF.GetSize()); i++){
if(i < saT.GetSize()) fformat += saT.GetAt(i);
if(i < saF.GetSize()) fformat += _convert(saF.GetAt(i));
}
//convert format strings
return fformat;
}
CString CDateTimeEditCtrl::_convert(LPCTSTR szformat)
{
CString sformat; //format string
const TCHAR * pch = 0; //character in question
TCHAR lastchar = 0; //character of the last group
int nchar = 0; //number of char in the last group in question
//scan the string
pch = szformat;
while(*pch != 0 || lastchar != 0){ //valid char OR still a last group to manage
if(*pch != s_chDateSep && /*skip separator*/
*pch != ' ' && /*skip space*/
NULL == strchr(".,",*pch)) /*skip punctuation marks*/
{
if(strchr("dMyhHmt",lastchar)){ //if a format character
if(nchar <1){ //new set
lastchar = *pch; //save the char as last group
nchar ++; //increment by one
pch ++; //next char
continue; //see next char
}
else if(lastchar == *pch){ //same set
nchar ++; //step one
pch ++; //next char
continue; //see next char
}
//if not the same char, finish up with last set
}
else{
ASSERT(FALSE); //not an accepted character for format string! stop!
}
}
if(nchar > 0 && lastchar != 0){
switch(lastchar){
case 'd':{
switch(nchar){
case 1: //d
sformat += _T("%#d"); break;
case 2: //dd
sformat += _T("%d"); break;
case 3: //ddd
sformat += _T("%a"); break;
case 4: //dddd
sformat += _T("%A"); break;
}
break;
}
case 'M':{
switch(nchar){
case 1: //M
sformat += _T("%#m"); break;
case 2: //MM
sformat += _T("%m"); break;
case 3: //MMM
sformat += _T("%b"); break;
case 4: //MMMM
sformat += _T("%B"); break;
}
break;
}
case 'y':{
switch(nchar){
case 2: //yy
sformat += _T("%y"); break;
case 4: //yyyy
sformat += _T("%Y"); break;
}
break;
}
case 'h':{
switch(nchar){
case 1: //h
sformat += _T("%#I"); break;
case 2: //hh
sformat += _T("%I"); break;
}
break;
}
case 'H':{
switch(nchar){
case 1: //H
sformat += _T("%#H"); break;
case 2: //HH
sformat += _T("%H"); break;
}
break;
}
case 'm':{
switch(nchar){
case 1: //m
sformat += _T("%#M"); break;
case 2: //mm
sformat += _T("%M"); break;
}
break;
}
case 't':{
switch(nchar){
case 1: //t
sformat += _T("%#p"); break; //no equivalent option (%#p == %p
'#' is ignored)
case 2: //tt
sformat += _T("%p"); break;
}
break;
}
default:
if(*pch != 0) sformat += lastchar;
}
if(*pch != 0) sformat += *pch; //save the char
nchar = 0; //new group
lastchar = 0;
}
else{
if(*pch != 0) sformat += *pch;
}
if(strchr("dMyhHmt",*pch)){
lastchar = *pch; //save the char if a format set
}
if(*pch != 0) pch ++; //next char, if has not reached end yet (need to prevent overrun)
}
return sformat;
}
3) In WindowProc function of CDateTimeEditCtrl, make these changes:
Change the display formatting in DTM_SETSYSTEMTIME
Add function strcpy(s_szDateFormat, (LPCTSTR)lParam); to the function call of DTM_SETFORMAT
like:
LRESULT CDateTimeEditCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
..........
..........
case DTM_SETSYSTEMTIME:
..........
Instead of : CString sDate = date.Format(VAR_DATEVALUEONLY);
Use : CString sDate = date.Format(ConvertDateFormatString(s_szDateFormat));
..........
..........
case DTM_SETFORMAT:
TRACE("Got DTM: %s\n", FormatDTM(message));
TRACE("lParam: %s\n", (LPCTSTR)lParam);
strcpy(s_szDateFormat, (LPCTSTR)lParam);
break;
case DTM_GETRANGE:
case DTM_SETRANGE:
ASSERT(FALSE); // not supported
AfxThrowNotSupportedException();
break;
..........
..........
}
4) In OnNotify function of CDateTimeEditCtrl, make these changes:
Add statement to convert the date to the formatted string:
BOOL CDateTimeEditCtrl::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
..........
..........
Instead of : CString sDate = date.Format(VAR_DATEVALUEONLY);
Use : CString sDate = date.Format(ConvertDateFormatString(s_szDateFormat));
..........
..........
}
////////////////////////////////////
Voila, now you can modify the format like the original CDateTimeCtrl control
E.g.:
In:
BOOL CDateTimeEditCtrlDemoDlg::OnInitDialog()
{
Add::
DateTime_SetFormat(m_date.GetSafeHwnd(),_T("d/M/yyyy")); //to give 5/5/2005
OR::
DateTime_SetFormat(m_date.GetSafeHwnd(),_T("dd/MM/yy")); //to give 05/05/05
Etc:
}
you should get what you want. If you find any problem, please let me know.
Henry
hshliang@hotmail.com
|
|
|
|
|
I must say that this is the best DateTimeControl I have ever come across! I have been trying to capture the edit control of the CDateTimeCtrl for years, and this is the first time that I have been able to do something with the Edit Box.
Thank you Paul.
Now there is a strange behaviour that I cannot explain. Normally, if you set the edit box to Readonly, it will have a colored background so the user recognises that it is not changable. With your SetNonEditable(), the edit box remain to have the same look. that is a strange behaviour. Is there an explanation to it? I try to change the m_pEdit with SetReadOnly(), it will stop the user entry, but the edit box remains the same in appreance. It seems not to be a problem of your code but somehow the CEdit of the CDateTimeCtrl does not response as any other CEdit.
H Liang
|
|
|
|
|
I am sorry to ask this question. I found out today that you purposely make it different from the standard Editbox response. Hence, I only have to remove the special funciton to make the background white and I have what I want. Thank you.
H Liang
|
|
|
|
|
Hi, Sorry to bother you with what might be a brain-dead case, but I've added your class to a dll in my application, and when try to link it I keep getting the following error:
Linking...
Creating library Debug/Archive.lib and object Debug/Archive.exp
DateTimeEditCtrl.obj : error LNK2001: unresolved external symbol "public: static struct CRuntimeClass const CDateTimeCtrl::classCDateTimeCtrl" (?classCDateTimeCtrl@CDateTimeCtrl@@2UCRuntimeClass@@B)
Debug/Archive.dll : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.
I've added /GR to the project setting in both my dll and my main application - but I still get this error.
Any help you could provide would be much appreciated!!
Thank you
HC
|
|
|
|
|
Oops, my bad.
When I modified the following line in the file DateTimeEditCtrl.cpp (as in a previous reply to this article), it resolved my link error.
IMPLEMENT_DYNAMIC(CDateTimeEditCtrl, CDateTimeCtrl)
to be
IMPLEMENT_DYNAMIC(CDateTimeEditCtrl, CDateTimeEditCtrl)
-HC
|
|
|
|
|
OK, but I wouldn't do it this way because, as I said in my reply, it stops you using IsKindOf (or DYNAMIC_DOWNCAST) to determine if your control is a CDateTimeCtrl.
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
Same problem, any other ideas ?
Thanks ))
|
|
|
|
|
As I have existing code, when I implemented the CDateTimeEditCtrl control, I experienced a minor side effect. In my app, there are constraints to the dates which can be entered by the user (i.e., min and max). These constraints are tested when a DTN_DATETIMECHANGE message is passed (i.e., date changed by the user), not when the calendar is "closed up" (DTN_CLOSEUP). This works for the CDateTimeCtrl. However, with the CDateTimeEditCtrl, the date selected by the user is put into the edit control (effectively overriding the edit constraints).
Casey
|
|
|
|
|
To fix this, you need to move some code in the CDateTimeEditCtrl::OnNotify function.
Move the code:
LPNMSELCHANGE lpnmsc = (LPNMSELCHANGE)pnmhdr;
COleDateTime date(lpnmsc->stSelStart);
ASSERT(date.GetStatus() == COleDateTime::valid);
CString sDate = date.Format(VAR_DATEVALUEONLY);
m_pEdit->SetWindowText(sDate);
to be the first block of code after the line:
else if (pnmhdr->code == MCN_SELCHANGE)
I was grabbing the date from the calendar and overwriting what had been selected (or in this case constrained) by the last DTN_DATETIMECHANGE message. It now behaves (at least in this respect) the same as the standard control.
I have uploaded a fixed version of the source (version 3.1).
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
When I attempted to implement the control in a DLL (needing only to export the constructor and destructor), I encountered a Link Reference Error for CRuntime info of CDateTimeCtrl (Windows standard control). It seems that runtime info is not exported for this control (at least not in the libraries I'm linking to).
Link Error is as follows:
error LNK2001: unresolved external symbol "public: static struct CRuntimeClass const CDateTimeCtrl::classCDateTimeCtrl" (classCDateTimeCtrl@CDateTimeCtrl@@2UCRuntimeClass@@B)
I was able to get around this link error by modifying the following line in the file DateTimeEditCtrl.cpp.
IMPLEMENT_DYNAMIC(CDateTimeEditCtrl, CDateTimeCtrl)
to be
IMPLEMENT_DYNAMIC(CDateTimeEditCtrl, CDateTimeEditCtrl)
When I changed the second parameter from CDateTimeCtrl to CDateTimeEditCtrl, this macro no longer causes a link reference error, and functions perfectly.
***
Paul's solution was similar (although more in-depth)...he writes the following,
Meanwhile, I've been looking into it too. I got the same problem, but only as part of an extension DLL.
After a lot of tweaking, I've found it's down to the CDateTimeCtrl. It doesn't have its IMPLEMENT_DYNAMIC(CDateTimeCtrl, CWnd) exported. Dumping the strings of the MFC libs gives me the following for CDateTimeCtrl (some variants and duplicates omitted):
NAFXCWD.LIB: ?classCDateTimeCtrl@CDateTimeCtrl@@2UCRuntimeClass@@B
NAFXCW.LIB: ?classCDateTimeCtrl@CDateTimeCtrl@@2UCRuntimeClass@@B
Whereas doing the same for CListCtrl (which I am also exporting an override to) gives me this:
NAFXCWD.LIB: ?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
NAFXCW.LIB: ?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
MFC42D.LIB: __imp_?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
MFC42D.LIB: ?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
MFC42.LIB: __imp_?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
MFC42.LIB: ?classCListCtrl@CListCtrl@@2UCRuntimeClass@@B
The difference being that CDateTimeCtrl doesn't have an 'importable' version of the runtime stuff.
To get round this add the following somewhere in a cpp file, where it will only get used once, for example in your stdafx.cpp:
#ifdef _AFXDLL
IMPLEMENT_DYNAMIC(CDateTimeCtrl, CWnd)
#endif
[You could put it in the DateTimeEditCtrl cpp file, but then it'll error if it also happens to be elsewhere]
***
All the best,
Casey
|
|
|
|
|
Thinking about it more, unfortunately your solution will prevent things like the following:
BOOL bDateCtrl = m_date.IsKindOf(RUNTIME_CLASS(CDateTimeCtrl)); or
CDateTimeCtrl* pDate = DYNAMIC_DOWNCAST(CDateTimeCtrl, &m_date); as the runtime class code will think it's only derived from itself!
(Although my solution is prone to causing the opposite to the original problem, ie that the runtime class functions are defined more than once.)
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
I've encountered (what I believe to be) a synchronization problem when initializing the control (SetTime) from the OnInitDialog method.
I encountered an Assertion failure in the WindowProc method (message=DTM_SETSYSTEMTIME), as the member variable m_pEdit was not yet created ( ASSERT(::IsWindow(m_hWnd)) ). It seems that the WindowProc receives (and processes) the DTCEM_RECREATE message (invoking OnRecreate which creates m_pEdit) AFTER the DTM_SETSYSTEMTIME message.
I find this control extremely useful and far better than the standard CDateTimeCtrl. Thanks very much to Paul.
Kind regards,
Casey
|
|
|
|
|
After some investigation I found that this occurs if sub-classing the control (ie not created explicitly using Create()), and you call Get/SetWindowText, SetFont, or any function which causes a DTM_XXX message to be sent in the WM_INITDIALOG handler of the dialog.
It is indeed due to the fact that, although the message has been posted to recreate the control during the DoDataExchange, it doesn't process the message until the next time it services the message loop, which is not until after WM_INITDIALOG has finished.
This means that when a DTM_XXX message arrives at this time (or you call my versions of Set/GetWindowText or SetFont) that the child edit control has not yet been created, and so calls to m_pEdit's methods assert on ASSERT(::IsWindow(m_hWnd)).
To fix this problem, I have, at the head of the WindowProc override, added code to remove and dispatch the DTCEM_RECREATE message from the message loop if it is there. This ensures that the edit control is created before processing any messages that expect the edit control to be there.
Also, as part of this fix, I have removed my Set/GetWindowText and SetFont functions and handled the WM_SETTEXT/WM_GETTEXT/WM_SETFONT messages instead. (The functions were a bit naughty anyway as I was 'overriding' non-virtual functions.)
I have included this fix in version 3.0 of the code which I have just uploaded.
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
Hi,
When I clicked on CDateTimeCtrl drop down arrow it shows
me Month calendar, when I do select date or cancels on
calendar control, focus gets dissappear. Now if I press
tab the focus goes to next subsequent control and when
come back to the same CDateTimeCtrl control th'r tab, it
doesn't get selected.
Anybody of you even encounter this, or do anybody of know how to resolve this?
Thanks & Regards,
- Vijay
God is Great
|
|
|
|
|
I like what you have done here. The standard date picker control is really a pain-in-the-butt if you ask me. This makes it much easier to use.
I have actually developed my own date picker control (published here on CP at http://www.codeproject.com/editctrl/dateparser.asp[^]). My control looks similar to the one in outlook, but more importantly allows for many more input possibilities. I would suggest integrating my COleDateTimeEx class with your picker control either entirely or as an option. This would allow your control to have most of the same features as mine while maintaining full compatability with the standard date picker control.
Your decision to modify the standard date picker control will make it easier for other developers to integrate your control than mine does. If I had had your control 18 months ago, I might not have developed my own picker control.
Anyway,
Good work.
|
|
|
|
|
I've always had a love-hate relationship with the date picker control. It prevents stupid users from doing something stupid but is annoying for the rest of us.
A few questions:
1. Can you add a way of querying the control to determine whether the date entered is a valid date (for example, formatted correctly, isn't Feb 29th on a non leapyear, etc)?
2. What about including a flag that would just allow numeric values and their separators (like '/' or '-'). This is to me might be the best of both worlds--the ability to restrict input but make the control more user friendly.
3. What about allowing the up-down arrow keys to still change the month, day, and year, depending on where the cursor is?
Even without the above features, this is still nice work! I think I'll add it into my framework right away!
Thanks!
Marc
Help! I'm an AI running around in someone's f*cked up universe simulator. Sensitivity and ethnic diversity means celebrating difference, not hiding from it. - Christian Graus Every line of code is a liability - Taka Muraoka
|
|
|
|
|
Marc Clifton wrote:
It prevents stupid users from doing something stupid but is annoying for the rest of us.
That sums up my feelings about Windows and Office in one sentence.
"If by 'rough it' you mean a room without a minibar and free hookers, then, uh, we have a problem." -Duckman
|
|
|
|
|
I'm glad you like the control. It's certainly made my life easier using this rather than the standard control.
I like your ideas, and will look at implementing them in my control. I will post an update once I've got something.
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
I've posted an update with all your suggestions included. Please let me know how you get on with them! (The code for 3 isn't very pretty, but I think it works, so it'll do for now.)
Paul.
---
"The way of a fool seems right to him, but a wise man listens to advice" - Proverbs 12:15 (NIV)
|
|
|
|
|
Excellent! I'm going to pop this into my framework right away. And the great thing is, I just need to update my window manager component and all my apps instantly have the new capability, without recompiling them!
I feel rather guilty--you've done something that makes my client's software better, and I'm not even paying you for it.
Marc
Help! I'm an AI running around in someone's f*cked up universe simulator. Sensitivity and ethnic diversity means celebrating difference, not hiding from it. - Christian Graus Every line of code is a liability - Taka Muraoka
|
|
|
|
|