Link to home
Start Free TrialLog in
Avatar of rickatseasoft
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_KEYDOWN){
            if(pMsg->wParam==13){ //enter or return key pressed
                  if(GetKeyState(VK_CONTROL)&0x8000){  //Control Key Is Pressed
                        if(m_ButtonOK.IsWindowEnabled())OnOK();
                        return TRUE;
                  }
                  pMsg->wParam=VK_TAB;
            }
            if(pMsg->wParam==VK_TAB&&GetFocus()==&m_ButtonOK){
                  m_TabPages[0]->SetFocus();
                  return TRUE;
            }
      }
      return CDialog::PreTranslateMessage(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
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

Just to be sure change
if(pMsg->wParam==VK_TAB&&GetFocus()==&m_ButtonOK){
to
if((pMsg->wParam==VK_TAB) && (GetFocus()->GetSafeHwnd() == m_ButtonOK.GetSafeHwnd())){

braces to make certain everything is chaecked we want and comparing the handles of the windows - not pointers to windows
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::PreTranslateMessage(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
Avatar of rickatseasoft
rickatseasoft

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

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->Attach( hSomeRandomHWND);

     CallRicksFunction( pwndTmpToSendToRick );

     pwndTmpToSendToRick->Detach();
     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
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()->GetSafeHwnd();
      NewFocusWindow=CWnd::FromHandle(hWnd);
      if(wParam==IDC_31E_EMP_ZIP){
            if(Ama5.GetZip(this,NULL,&m_CtrlEmpCity,&m_CtrlEmpState,&m_CtrlEmpZip)){
                  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
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
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
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
Avatar of AndyAinscow
AndyAinscow
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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_KEYDOWN&&pMsg->wParam==VK_TAB){
            int ii;
            CWnd *CurFocusWnd;
            CAmarun35Dlg *lpParent;
            lpParent=(CAmarun35Dlg *)m_pParent;
            CurFocusWnd=GetFocus();
            if(CurFocusWnd==GetDlgItem(IDC_35_TAXONOMY)){ //last item on the page
                  CurFocusWnd=GetDlgItem(IDC_35_MEDICARE);
                  CurFocusWnd->SetFocus();  //reset to first field in case of return
                  ii=lpParent->IncrementTabPage();
                  ((CTabCtrl *)m_TabCtrlParent)->SetCurSel(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
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
Minor point - no need for the local var CWnd *CurFocusWnd.
You can use code like GetDlgItem(IDC_35_MEDICARE)->SetFocus();

Instead of
((CTabCtrl *)m_TabCtrlParent)->SetCurSel(ii);
You may want to consider sending a custom message instructing the parent you want to change to another tab.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial