Want to protect your cyber security and still get fast solutions? Ask a secure question today.Go Premium

x
?
Solved

PreTranslateMessage Capture VK_TAB before focus changes

Posted on 2006-04-26
13
Medium Priority
?
3,756 Views
Last Modified: 2013-11-20
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
0
Comment
Question by:rickatseasoft
  • 5
  • 4
  • 4
13 Comments
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 16544286
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16547152
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
0
 

Author Comment

by:rickatseasoft
ID: 16547482
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

0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 
LVL 49

Expert Comment

by:DanRollins
ID: 16548714
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
0
 

Author Comment

by:rickatseasoft
ID: 16549748
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
0
 

Author Comment

by:rickatseasoft
ID: 16549847
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 16550061
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
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 16550503
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?).
0
 
LVL 45

Accepted Solution

by:
AndyAinscow earned 600 total points
ID: 16550828
Data validation.  I agree with Dan.
I usually check for valid data when the user hits the save button.  (I can remember some years ago coding a nasty little control that tried to keep the focus if the data wasn't valid - on testing I input invalid data and then could I stop the app? Not easily, it may have involved a reboot, I had to have a real hard think about how to stop invalid data but still allow invalid data should the user want to abort the entry !!)

IMHO there is an exception - entering invalid characters into an edit.  That I do trap and throw the chars away BUT the user can still leave the control and the complete entry is still checked for validation when the user wants to save.
0
 

Author Comment

by:rickatseasoft
ID: 16551686
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
0
 

Author Comment

by:rickatseasoft
ID: 16551728
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
0
 
LVL 45

Expert Comment

by:AndyAinscow
ID: 16551882
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.
0
 
LVL 49

Assisted Solution

by:DanRollins
DanRollins earned 1400 total points
ID: 16557056
Normal U/I handling of the TAB key is to move the focus to the next control, or cycle back to the top.  At the point where the new focus is on one of the tabs in the TabControl, then the user can use arrow keys to select the next page (it is Ctrl+Tab in a CPropertySheet)

In other words, the user will not be expecting the new page to come up when he presses the TAB key.  However, IMO, that would be a relatively minor breach of U/I etiquette.

Without having tried it, I'd think that if you detect TAB while focus is on the final control, you could SetFocus to the TabControl, then post simulate a right-arrow keystroke by posting messages to the control:
      m_ctrlTab.SetFocus();
      m_ctrlTab.PostMessage(WM_KEYDOWN, VK_RIGHT );
      m_ctrlTab.PostMessage(WM_KEYUP, VK_RIGHT );
0

Featured Post

[Webinar On Demand] Database Backup and Recovery

Does your company store data on premises, off site, in the cloud, or a combination of these? If you answered “yes”, you need a data backup recovery plan that fits each and every platform. Watch now as as Percona teaches us how to build agile data backup recovery plan.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

In this article, I'll describe -- and show pictures of -- some of the significant additions that have been made available to programmers in the MFC Feature Pack for Visual C++ 2008.  These same feature are in the MFC libraries that come with Visual …
If you use Adobe Reader X it is possible you can't open OLE PDF documents in the standard. The reason is the 'save box mode' in adobe reader X. Many people think the protected Mode of adobe reader x is only to stop the write access. But this fe…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Whether it be Exchange Server Crash Issues, Dirty Shutdown Errors or Failed to mount error, Stellar Phoenix Mailbox Exchange Recovery has always got your back. With the help of its easy to understand user interface and 3 simple steps recovery proced…
Suggested Courses

581 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question