
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);
if((notificationCode == EN_KILLFOCUS) ||
(notificationCode == LBN_KILLFOCUS) ||
(notificationCode == CBN_KILLFOCUS) ||
(notificationCode == NM_KILLFOCUS) ||
(notificationCode == WM_KILLFOCUS)) {
CWnd *pFocus = CWnd::GetFocus();
if(pFocus &&(pFocus->GetParent() == this))
{
if(pFocus->GetDlgCtrlID() != IDCANCEL) {
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();
if(pFocus &&(pFocus->GetParent() == this)){
if(pFocus->GetDlgCtrlID() != IDCANCEL) {
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:
break;
case ID_ADDRESS:
break;
case ID_PHONE:
break;
case ID_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 :-)
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).