rickatseasoft
asked on
PreTranslateMessage Capture VK_TAB before focus changes
I have the following code in PreTranslateMessage--there is no other code within this function
if(pMsg->message==WM_KEYDO WN){
if(pMsg->wParam==13){ //enter or return key pressed
if(GetKeyState(VK_CONTROL) &0x8000){ //Control Key Is Pressed
if(m_ButtonOK.IsWindowEnab led())OnOK ();
return TRUE;
}
pMsg->wParam=VK_TAB;
}
if(pMsg->wParam==VK_TAB&&G etFocus()= =&m_Button OK){
m_TabPages[0]->SetFocus();
return TRUE;
}
}
return CDialog::PreTranslateMessa ge(pMsg);
This is a TAB Control with "CANCEL", "OK AND EDIT ANOTHER" and "OK" keys on the main dialog, with all of the other stuff contained on the various tabbed dialogs.
I want the ENTER key to act like the TAB key which it does. I also want ^ENTER to act like the OK button was pressed which it does. If the ENTER or TAB keys are pressed when the focus is on the OK button (the last button on the main dialog), I want to activate TAB0 of that dialog and return it to focus. Actually everything works exactly as planned except that when I press TAB while on "OK EDIT ANOTHER" (this button is immediately before the OK key in the TAB Order), the TAB0 Dialog is activated. It seems that the OK button gets focus before the PreTranslateMessage is called.
Can anyone explain this behavoir? Is there a better way?
Thanks in advance, Rick
if(pMsg->message==WM_KEYDO
if(pMsg->wParam==13){ //enter or return key pressed
if(GetKeyState(VK_CONTROL)
if(m_ButtonOK.IsWindowEnab
return TRUE;
}
pMsg->wParam=VK_TAB;
}
if(pMsg->wParam==VK_TAB&&G
m_TabPages[0]->SetFocus();
return TRUE;
}
}
return CDialog::PreTranslateMessa
This is a TAB Control with "CANCEL", "OK AND EDIT ANOTHER" and "OK" keys on the main dialog, with all of the other stuff contained on the various tabbed dialogs.
I want the ENTER key to act like the TAB key which it does. I also want ^ENTER to act like the OK button was pressed which it does. If the ENTER or TAB keys are pressed when the focus is on the OK button (the last button on the main dialog), I want to activate TAB0 of that dialog and return it to focus. Actually everything works exactly as planned except that when I press TAB while on "OK EDIT ANOTHER" (this button is immediately before the OK key in the TAB Order), the TAB0 Dialog is activated. It seems that the OK button gets focus before the PreTranslateMessage is called.
Can anyone explain this behavoir? Is there a better way?
Thanks in advance, Rick
In support of AndyAiscows comment:
The GetFocus() function returns a temporary CWnd*, so that's why it's not a good idea to compare it to anything.
However, it is unlikely that that part of the code is being executed... that is the
... &GetFocus()==&m_ButtonOK){
will probably never be true. That means execution is falling through to
return CDialog::PreTranslateMessa ge(pMsg);
The observed behavior would occur if the final button in the dialog is disabled at the time the TAB key is pressed. Normal TAB key handling would cycle focus back to the first control and my guess is that is your TAB0...
You can verify that by placing a breakpoint on this line:
m_TabPages[0]->SetFocus();
If my hunch is right, then it is not getting executed in the situation that is causing the problem.
There may be a reason for the default key handler to skip the final button (other than it being disabled), but I don't know what it might be...
As with all message-related questions, you will find the answer by using Spy++ to monitor the messages.
-- Dan
The GetFocus() function returns a temporary CWnd*, so that's why it's not a good idea to compare it to anything.
However, it is unlikely that that part of the code is being executed... that is the
... &GetFocus()==&m_ButtonOK){
will probably never be true. That means execution is falling through to
return CDialog::PreTranslateMessa
The observed behavior would occur if the final button in the dialog is disabled at the time the TAB key is pressed. Normal TAB key handling would cycle focus back to the first control and my guess is that is your TAB0...
You can verify that by placing a breakpoint on this line:
m_TabPages[0]->SetFocus();
If my hunch is right, then it is not getting executed in the situation that is causing the problem.
There may be a reason for the default key handler to skip the final button (other than it being disabled), but I don't know what it might be...
As with all message-related questions, you will find the answer by using Spy++ to monitor the messages.
-- Dan
ASKER
I found the problem with a mis-placed bracket. But your answers raise some interesting questions.
Andy: Why do we want to compare handles rather than pointers?
Dan:
Please explain the notion of a temporary CWnd *. I realize that I can't store it in some variable, and use it later----but what is the lifetime of the value. If I can't count on if(GetFocus()==SomeWindow) giving me the correct value because the window is TEMPORARY, then how can I depend on the value returned by GetFocus()->GetSafeHWnd();
Thanks again, Rick
Andy: Why do we want to compare handles rather than pointers?
Dan:
Please explain the notion of a temporary CWnd *. I realize that I can't store it in some variable, and use it later----but what is the lifetime of the value. If I can't count on if(GetFocus()==SomeWindow)
Thanks again, Rick
The pointer may be temporary, even if the physical window itself is as permanent as the rock of Gibraltar.
The framework gets an HWND from the system and for your convenience, it sends you a a CWnd* constructed from that HWND. The pointer itself is going to go away. Consider...
(somewhere deep in MFC)...
CWnd* pwndTmpToSendToRick= new CWnd;
pwndTmpToSendToRick->Attac h( hSomeRandomHWND);
CallRicksFunction( pwndTmpToSendToRick );
pwndTmpToSendToRick->Detac h();
delete pwndTmpToSendToRick; // poof, it's gone
that's why you can't save it for later -- it has been deleted. THe pointer points to nothing valid.
Now why can't you compare it to an existing CWnd* ??? Consider this:
CButton* pwndButton= new CButton;
HWND hWnd= ::GetDlgItem( m_hWnd, IDC_BUTTON1);
pwndButton->m_hWnd= hWnd; // note: don't do this
pwndButton->SetWindowText( "Hi there!"); // this works as good...
m_ctrlBtn1.SetWindowText( "Goodbye"); // ... as this
if ( pwndButton == &m_ctrlBtn1 ) {
MessageBox("Get out the holy water and wafers, a miracle has happened!");
}
The real miracle is: Why can you *usually* get away with it? MFC goes to a lot of trouble to keep track of all "permanent" CWnd objects used by your program. It maintains a list and does a lookup. For instance, try single-step tracing through a call to
GetDlgItem()
... it creates a CWnd (and passes you that CWnd*) only if the dialog item is not already associated with one in the existing ("permanent window") table. If there is an object in its list that has the same HWND, then it returns the corresponding CWnd* -- so you CAN compare it with other CWnd* values and it will match or not match correctly. Also the MFC system prevents a lot of programmer errors like using
pwnd->Attach( hWndWhatever )
if hWndWhatever is already in the permanent list. If it did not do that, then you would end up with two CWnd*'s that access the same physical window (as I did in my example, above). And that would create all kinds of havock if you closed or deleted one... the HWND (used in nearly every operation) would become invalid for the other.
-- Dan
The framework gets an HWND from the system and for your convenience, it sends you a a CWnd* constructed from that HWND. The pointer itself is going to go away. Consider...
(somewhere deep in MFC)...
CWnd* pwndTmpToSendToRick= new CWnd;
pwndTmpToSendToRick->Attac
CallRicksFunction( pwndTmpToSendToRick );
pwndTmpToSendToRick->Detac
delete pwndTmpToSendToRick; // poof, it's gone
that's why you can't save it for later -- it has been deleted. THe pointer points to nothing valid.
Now why can't you compare it to an existing CWnd* ??? Consider this:
CButton* pwndButton= new CButton;
HWND hWnd= ::GetDlgItem( m_hWnd, IDC_BUTTON1);
pwndButton->m_hWnd= hWnd; // note: don't do this
pwndButton->SetWindowText(
m_ctrlBtn1.SetWindowText( "Goodbye"); // ... as this
if ( pwndButton == &m_ctrlBtn1 ) {
MessageBox("Get out the holy water and wafers, a miracle has happened!");
}
The real miracle is: Why can you *usually* get away with it? MFC goes to a lot of trouble to keep track of all "permanent" CWnd objects used by your program. It maintains a list and does a lookup. For instance, try single-step tracing through a call to
GetDlgItem()
... it creates a CWnd (and passes you that CWnd*) only if the dialog item is not already associated with one in the existing ("permanent window") table. If there is an object in its list that has the same HWND, then it returns the corresponding CWnd* -- so you CAN compare it with other CWnd* values and it will match or not match correctly. Also the MFC system prevents a lot of programmer errors like using
pwnd->Attach( hWndWhatever )
if hWndWhatever is already in the permanent list. If it did not do that, then you would end up with two CWnd*'s that access the same physical window (as I did in my example, above). And that would create all kinds of havock if you closed or deleted one... the HWND (used in nearly every operation) would become invalid for the other.
-- Dan
ASKER
Dan:
Thanks a lot for taking the time to answer, and it is beginning to make sense. So, suppose that I have a control that sends a private message to the main dialog whenever it loses focus, and the main dialog's job is to verify the data entered. This should be in the main dialog because there are several controls and/or variables that, in this case represent city, state, and zip, and the verification is that the city matches the state and zip. If the zip is blank then a search is done for the city and state looking for the appropriate zip, if the city is blank, then a zip and state search is done, etc. So, when the zip control loses focus, it triggers a verification mechanism that will possibly prompt the user to select from a list control, and/or return either success of failure. If failure, then the focus should be set back to the zip control.
On the other hand, if success, then the field should be set to whatever field the user had selected(Perhaps the user went back to the name field to correct a misspelling, etc. Based on the advice above, I used the following in the message handler:
CAma5 Ama5;
HWND hWnd;
CWnd *NewFocusWindow;
hWnd=GetFocus()->GetSafeHw nd();
NewFocusWindow=CWnd::FromH andle(hWnd );
if(wParam==IDC_31E_EMP_ZIP ){
if(Ama5.GetZip(this,NULL,& m_CtrlEmpC ity,&m_Ctr lEmpState, &m_CtrlEmp Zip)){
NewFocusWindow->SetFocus() ;
}else{
m_CtrlEmpZip.SetFocus();
}
}
return 1;
It works reasonably well, but the cursor does weird things. First, the cursor is placed after the last character of the control, and if I move the mouse to the left of the control hiving not touched the mouse button, even though dozens of lines below the control, it hilights all of the data in the control as if I had clicked on the window, and, holding the left button down, drug the cursor over the data from right to left. It is almost as if it has created a new window for me that almost matches the existing window (CEdit), but doesn't quite get the job done.
So what is the best way to detect focus, and return to that control when finished handling some other house keeping.
Thanks again for taking the time to answer. I know that my questions might sometimes seem sarcastic, but I can assure you that they are not. I am just trying to understand the process.
Rick
Thanks a lot for taking the time to answer, and it is beginning to make sense. So, suppose that I have a control that sends a private message to the main dialog whenever it loses focus, and the main dialog's job is to verify the data entered. This should be in the main dialog because there are several controls and/or variables that, in this case represent city, state, and zip, and the verification is that the city matches the state and zip. If the zip is blank then a search is done for the city and state looking for the appropriate zip, if the city is blank, then a zip and state search is done, etc. So, when the zip control loses focus, it triggers a verification mechanism that will possibly prompt the user to select from a list control, and/or return either success of failure. If failure, then the focus should be set back to the zip control.
On the other hand, if success, then the field should be set to whatever field the user had selected(Perhaps the user went back to the name field to correct a misspelling, etc. Based on the advice above, I used the following in the message handler:
CAma5 Ama5;
HWND hWnd;
CWnd *NewFocusWindow;
hWnd=GetFocus()->GetSafeHw
NewFocusWindow=CWnd::FromH
if(wParam==IDC_31E_EMP_ZIP
if(Ama5.GetZip(this,NULL,&
NewFocusWindow->SetFocus()
}else{
m_CtrlEmpZip.SetFocus();
}
}
return 1;
It works reasonably well, but the cursor does weird things. First, the cursor is placed after the last character of the control, and if I move the mouse to the left of the control hiving not touched the mouse button, even though dozens of lines below the control, it hilights all of the data in the control as if I had clicked on the window, and, holding the left button down, drug the cursor over the data from right to left. It is almost as if it has created a new window for me that almost matches the existing window (CEdit), but doesn't quite get the job done.
So what is the best way to detect focus, and return to that control when finished handling some other house keeping.
Thanks again for taking the time to answer. I know that my questions might sometimes seem sarcastic, but I can assure you that they are not. I am just trying to understand the process.
Rick
ASKER
Dan or Andy:
So it occurs to me that the reason for loss of focus may be that the user clicked on some other program or dialog. It seems to me that it is unreasonable/impossible to try to return control to that window.
Sooooo, I am wondering if I should compare the return from GetFocus() to a list of Controls used in the current dialog, and if there is a match, then return focus there, if not, then let the focus float to wherever Windows wants to put it? Further, if the user has clicked on CANCEL, the I really don't want to do anything other than let the cancel run its course.
Rick
So it occurs to me that the reason for loss of focus may be that the user clicked on some other program or dialog. It seems to me that it is unreasonable/impossible to try to return control to that window.
Sooooo, I am wondering if I should compare the return from GetFocus() to a list of Controls used in the current dialog, and if there is a match, then return focus there, if not, then let the focus float to wherever Windows wants to put it? Further, if the user has clicked on CANCEL, the I really don't want to do anything other than let the cancel run its course.
Rick
Remember that Windows GUI guidelines indicate that programatically setting the focus is an unusual event -- the idea being that the *user* is in charge of which control is the one he wants to work with. Things seem to be unusual in these situations -- because programatically setting the focus *IS* unusual :-)
For instance, if you pressed the tab key to move to an Edit control, you would not be surprised if the existing contents then became highlighted. So when you SetFocus() there, you should not be surprised at seeing that same effect.
Also note that there are few applications that do "real-time input validation" -- it is tricky and it's easy to get stuck in recursive loops... AND it is counter-intuitive to people who are familiar to the way the Windows GUI works (i.e., everybody). You might consider a less ambitious scheme. For instance:
If the user tries to change focus from an Edit box but the input is clearly not allowed, then beep and/or throw up a messagebox telling him the problem and don't let him do anything but fix it or cancel. However, you can do some of what you want; fFor instance, if the user enters the ZIP code, then you can fill-in the city and state -- but do it without without changing focus (let the user change the focus).
When I had to meet a similar challenge, I did the input validation at the end -- when the user clicked "OK" (or "Apply" in my case). I had a function that would look at each input and if there was something wrong, I'd pop up a message box indicating the reason ("Last name is missing" or whatever) and only then set the focus to the control that was invalid. Since I had to do this on dozens of forms with hundreds of inputs, I wrote some functions into a CPropertyPage-derived base class that let me setup the control and the message and some action flags in a nice tidy table. I *could have* worked out the issues related to real-time validation, but in the end, I decided not to go that route.
-- Dan
For instance, if you pressed the tab key to move to an Edit control, you would not be surprised if the existing contents then became highlighted. So when you SetFocus() there, you should not be surprised at seeing that same effect.
Also note that there are few applications that do "real-time input validation" -- it is tricky and it's easy to get stuck in recursive loops... AND it is counter-intuitive to people who are familiar to the way the Windows GUI works (i.e., everybody). You might consider a less ambitious scheme. For instance:
If the user tries to change focus from an Edit box but the input is clearly not allowed, then beep and/or throw up a messagebox telling him the problem and don't let him do anything but fix it or cancel. However, you can do some of what you want; fFor instance, if the user enters the ZIP code, then you can fill-in the city and state -- but do it without without changing focus (let the user change the focus).
When I had to meet a similar challenge, I did the input validation at the end -- when the user clicked "OK" (or "Apply" in my case). I had a function that would look at each input and if there was something wrong, I'd pop up a message box indicating the reason ("Last name is missing" or whatever) and only then set the focus to the control that was invalid. Since I had to do this on dozens of forms with hundreds of inputs, I wrote some functions into a CPropertyPage-derived base class that let me setup the control and the message and some action flags in a nice tidy table. I *could have* worked out the issues related to real-time validation, but in the end, I decided not to go that route.
-- Dan
Dan has probably answered it already but one compares handles because that is a native value that is valid and unchanged for the lifetime of the window. Pointer to CWnd objects returned from some MFC functions are likely to be temporary and you can't guarantee that it is the same as a subclassed control variable (it may well be so BUT do you want to rely on chance for your app to function?).
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I have just a couple of more questions that I hope you can answer, then I'll close this one down.
1. With a tab control, when the user pressed the TAB key at the end of a dialog, I want to proceed to the next Tab on the control. I have been using PreTranslateMessage() to trap the VK_TAB key, and if the current focus is the last item on the particular page, then activate the next tab page. Something like:
if(pMsg->message==WM_KEYDO WN&&pMsg-> wParam==VK _TAB){
int ii;
CWnd *CurFocusWnd;
CAmarun35Dlg *lpParent;
lpParent=(CAmarun35Dlg *)m_pParent;
CurFocusWnd=GetFocus();
if(CurFocusWnd==GetDlgItem (IDC_35_TA XONOMY)){ //last item on the page
CurFocusWnd=GetDlgItem(IDC _35_MEDICA RE);
CurFocusWnd->SetFocus(); //reset to first field in case of return
ii=lpParent->IncrementTabP age();
((CTabCtrl *)m_TabCtrlParent)->SetCur Sel(ii);
return TRUE;
}
}
2. What do you do when, for whatever reason, you interrupt the user with a MessageBox, etc? After the user responds to the query, where and how do you place the focus? Based on your above comments, it would seem that you never or almost never have an interruption.
Thanks again, Rick
1. With a tab control, when the user pressed the TAB key at the end of a dialog, I want to proceed to the next Tab on the control. I have been using PreTranslateMessage() to trap the VK_TAB key, and if the current focus is the last item on the particular page, then activate the next tab page. Something like:
if(pMsg->message==WM_KEYDO
int ii;
CWnd *CurFocusWnd;
CAmarun35Dlg *lpParent;
lpParent=(CAmarun35Dlg *)m_pParent;
CurFocusWnd=GetFocus();
if(CurFocusWnd==GetDlgItem
CurFocusWnd=GetDlgItem(IDC
CurFocusWnd->SetFocus(); //reset to first field in case of return
ii=lpParent->IncrementTabP
((CTabCtrl *)m_TabCtrlParent)->SetCur
return TRUE;
}
}
2. What do you do when, for whatever reason, you interrupt the user with a MessageBox, etc? After the user responds to the query, where and how do you place the focus? Based on your above comments, it would seem that you never or almost never have an interruption.
Thanks again, Rick
ASKER
In the above example, I wasn't explicit enough. In case 1, I am effectively setting the focus programatically. In this sort of situation is it a no no, and if so, is there a better way.
Also, I will almost certainly take your advice about the validation; it is very tricky, and prone to unforseen problems.
Rick
Also, I will almost certainly take your advice about the validation; it is very tricky, and prone to unforseen problems.
Rick
Minor point - no need for the local var CWnd *CurFocusWnd.
You can use code like GetDlgItem(IDC_35_MEDICARE )->SetFocu s();
Instead of
((CTabCtrl *)m_TabCtrlParent)->SetCur Sel(ii);
You may want to consider sending a custom message instructing the parent you want to change to another tab.
You can use code like GetDlgItem(IDC_35_MEDICARE
Instead of
((CTabCtrl *)m_TabCtrlParent)->SetCur
You may want to consider sending a custom message instructing the parent you want to change to another tab.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
if(pMsg->wParam==VK_TAB&&G
to
if((pMsg->wParam==VK_TAB) && (GetFocus()->GetSafeHwnd()
braces to make certain everything is chaecked we want and comparing the handles of the windows - not pointers to windows