Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / MFC
Article

WM_KILLFOCUS doesn't appear in PreTranslateMessage

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
18 Sep 20016 min read 135.5K   1.9K   25   12
How to detect when the control looses its focus - the easy way.

dialog

Introduction

So, you have your dialog/view/property page/whatever. You have some fields, and you don't want to use the boring way of letting the user change all the fields in the form and validate them all when he presses the "ok/submit" button. A good way is to validate and set the data when the user presses Enter over a field, or when the user jumps to another control in the same window (pressing tab, or with the mouse). If the validation fails, the focus will return to the control that has failed, and the original data in that field will be restored. Doing the things like this is quite OK when you have fields whose values depend on other fields that can be modified in that form.

The first solution that every developer tries is just to override the OnKillFocus method in every edit field. This is ok if you have to do it only for two windows and eight controls, but if you need to make that in 30 windows with a lot of controls, its pretty boring to do that (and it is very easy to forget one control) and you can get "spaghetti" code.

Another solution that seems to be logical but doesn't work is just override PreTranslateMessage and capture the WM_KILLFOCUS message, but there is a problem: PreTranslateMessage only sees messages that come from the message queue. The WM_KILLFOCUS messages are sent to the window as a result of processing the mouse message that causes the focus change, so you can add the code there, but it would do nothing.

Another possibility is to use ON_CONTROL_RANGE and ON_NOTIFY_RANGE, to handle messages for a bunch of controls using the same method when they receive the kill focus message - but again a problem. You have to take care for different types of kill focus messages (for example, modern controls send notify, and older one just send commands).

So here's the good news. For the old good controls (edit controls, combos, etc.) you can capture the focus messages by overriding the WM_COMMAND message. Use the class wizard and select the WM_COMMAND message and change the implementation to something like:

BOOL CMyPropertyPage::OnCommand(WPARAM wParam, LPARAM lParam) 
{
   UINT notificationCode = (UINT) HIWORD(wParam);

   // For List control I handle it in another way....
   if((notificationCode == EN_KILLFOCUS)        ||   
            (notificationCode == LBN_KILLFOCUS) ||
            (notificationCode == CBN_KILLFOCUS) ||
            (notificationCode == NM_KILLFOCUS)  ||
            (notificationCode == WM_KILLFOCUS)) {
   
      CWnd *pFocus =  CWnd::GetFocus(); 
      // call to a static function 

      // If we are changing the focus to another
      // control of the same window... 
         
      if(pFocus &&(pFocus->GetParent() ==  this))
      {
         // Ok, if the focus is not in the cancel button...
         if(pFocus->GetDlgCtrlID() != IDCANCEL) {
            // Ok, my own method passing the ID with the focus...
            ValAndSubmit(LOWORD(wParam));  
         }
      }       
   }
   
   return CPropertyPage::OnCommand(wParam, lParam);
}

What does this method do? It just checks if some of the Killfocus messages have arrived (for edit controls, for list boxes, for combos, etc.) and then checks if the focus is not going out of the window (to avoid showing messages when switching to other applications), and then checks if the user hasn't pressed the cancel (or close) button (that button normally cancels the current change with no validation). Then I call there my own function to handle the changes in the selected control (LOWORD(wParam)), and then it returns the ID of the control that is losing the focus.

And for the newer controls: (from Microsoft Technical Note 061)

For controls that existed in Windows 3.1, the Win32 API uses most of the notification messages that were used in Windows 3.x. However, Win32 also adds a number of sophisticated ( uau !), complex controls to those supported in Window 3.x. Frequently, these controls need to send additional data with their notification messages. Rather than adding new WM_* message for each new notification that needs additional data, the designers of Win32 API chose to add just one message, WM_NOTIFY, which can pass any amount of additional data in a standardized way.
WM_NOTIFY messages contain the ID of the control sending the message in wParam and a pointer to a structure in lParam. This structure is either a NMHDR structure or some larger structure that has a NMHDR structure as its first member. Note that, since the NMHDR member is first, a pointer to this structure can be used as either a pointer to a NMHDR or as a pointer to the larger structure, depending on how you cast it.
The NMHDR structure or initial member contains the handle and ID of the control sending the message, and the notification code. The format of the NMHDR structure is shown below:
typedef struct tagNMHDR {
  HWND hwndFrom;
  UINT idFrom;
  UNIT code;
}

Ok, so now to handle the WM_NOTIFY commands, we override that message (class wizard), and we change the OnNotify method to:

BOOL CMyPropertyPage::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
{
   NMHDR* pNMHDR =  (NMHDR *)lParam; 
   if(pNMHDR &&pNMHDR->code == NM_KILLFOCUS) {      
      CWnd *pFocus =  CWnd::GetFocus(); // call to a static function 
      if(pFocus &&(pFocus->GetParent() ==  this)){
         if(pFocus->GetDlgCtrlID() !=    IDCANCEL) { 
             // Ok, if the focus is not in the cancelbutton... 
             if(pNMHDR->idFrom){
                ValAndSubmit(pNMHDR->idFrom);
             }
         }
      }       
   }

    return CPropertyPage::OnNotify(wParam, lParam, pResult);
}

The idea is the same as that in OnCommand, but we must play with the new values for the lParam (cast it to the NMHDR structure and get the data from there).

Ok, so what is the ValAndSubmit method? Well, if you prefer to use DDV instead of this, replace that call with UpdateData(TRUE). The ValAndSubmit function just calls UpdateData(TRUE), and depending on the ID of the control, it sets the new data in the application (this method must be implemented in each dialog), for example:

/* -------------------------------------------------------------------------
   ------------------------------------------------------------------------- */
BOOL CGenPage::ValAndSubmit(int DlgCtrlID)
{
   BOOL bIsOk             = FALSE;
           
   UpdateData(TRUE);      
   switch(DlgCtrlID) {
     case ID_EDNAME:      // For example a name          
       // Insert the code here to update the
       // name in the application or database or whatever...
     break;         
     case ID_ADDRESS:     // For example an address          
       // Insert the code here to update the
       // address in the application or database or whatever...
     break;         
     case ID_PHONE:       // For example a telephon          
       // Insert the code here to update the
       // phone in the application or database or whatever...
     break;  
     case ID_WHATEVER:    // Whatever...          
       // Insert the code here to update the
       // ... in the application or database or whatever...
     break;                               
   }         
   return bIsOk;
}

So just as easily, capture the messages and call your own method (or DDV), just to update the data in your application, BUT... doesn't this mean I have to repeat this in every dialog that I made? NO! OOP to the rescue. The only function that you need to implement in every dialog is the one to update the data (the ValAndSubmit one), that function changes in every dialog. But for the other two, you can make a base class that inherits from e.g. CDialog (or CPropertyPage etc.) depending on what you need, and implement the method there. You then have that common method implemented for every dialog (your dialog instead of CDialog - make sure your dialogs inherit from your new base class e.g.: CMyBaseDialog). One more detail: if you use the ValAndSubmit method, declare it in your Dialog base class as pure virtual, then the base class knows that it exists, and the inherited classes must implement it.

About the Sample

The sample included shows a dialog, and makes the validation over that dialog (with the enter, and kill focus stuff). It uses a base class, to store the OnCommand and OnNotify code (then it is only one added method in every dialog).

The sample is made with the purpose of showing how it works, it is not perfect, and it can be enhanced a lot, but that's your task (please feel free to post any question), if you want you can make a new article based on this.

If you want, you can use the BaseDialog class as a black box, and inherit your dialogs from that class, and implement in your dialog's class, only the ValAndSubmit method.

About the Article

This article has been done researching on deja-news, taking a look at MSDN (the technical note 61), asking in CodeGuru, and reading the quite old good friend "Inside Visual C++ V 6.0" Kruglinski, Sheperd, Wingo), ... a lot of things are used to make this simple article :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Spain Spain
Braulio is a developer specialized on Ms Technologies (Silverlight, ASP .net, SSRS, SSAS, SSIS). Currently he is working for Avanade in Málaga and as a journalist for the Spanish .net magazine Dotnetmania. He also is the webmaster of an specializad in .net technologies site Tipsdotnet.com, and Silverlight Based DB Schema Editor (www.dbschemaeditor.com).

Comments and Discussions

 
GeneralMy vote of 5 Pin
shahankit11-Nov-10 20:08
shahankit11-Nov-10 20:08 
GeneralEdit Field of ComboBox Pin
bruno leclerc2-Dec-02 21:44
bruno leclerc2-Dec-02 21:44 
GeneralRe: Edit Field of ComboBox Pin
Braulio Dez5-Dec-02 0:47
Braulio Dez5-Dec-02 0:47 
GeneralIDC=1001 Pin
bruno leclerc2-Dec-02 6:04
bruno leclerc2-Dec-02 6:04 
GeneralRe: IDC=1001 Pin
Braulio Dez5-Dec-02 0:46
Braulio Dez5-Dec-02 0:46 
GeneralGood but needs slight tweak for PropertyPages Pin
mstephens24-Jul-02 5:49
mstephens24-Jul-02 5:49 
GeneralRe: Good but needs slight tweak for PropertyPages Pin
Braulio Dez24-Jul-02 5:53
Braulio Dez24-Jul-02 5:53 
Generalad wrong value Pin
19-Sep-01 21:11
suss19-Sep-01 21:11 
GeneralThanks for the note Pin
Braulio Dez19-Sep-01 22:22
Braulio Dez19-Sep-01 22:22 
Thanks !,

As you said, it can be annoying to loose what you are editing, using the way you said would need to disable the "Ok" ( or submit or save...) button, to avoid the user press there and show a message ( not ok data...)...

When I did this code it was done for a property sheet like the Visual Studio properties ( by the way there is an article in codeproject showing how to create a pinned dialog that is quite good !), wihtout Ok button, and when the user presses tab or enter the data would be validated and submited, and he could jump from the properties windows to the main one or close or whatever..., and I choose to reset the field if the validation failed, ... it has some good things and some bad things.

Thanks very much for your interest ( at least somebody has read my lousy article Wink | ;-) ), Bye !
Braulio
Generalad wrong value Pin
20-Sep-01 0:01
suss20-Sep-01 0:01 
GeneralBut... Pin
Braulio Dez20-Sep-01 3:36
Braulio Dez20-Sep-01 3:36 
Generalx button Pin
23-Sep-01 19:20
suss23-Sep-01 19:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.