Introduction
One of the many tasks that a programmer has is to handle invalid data entered by the operator. For MFC forms (e.g. dialogs, form views), whether to perform the validation as soon as the control loses focus or validate the entire form when the user clicks OK button and how to show the error(s) preventing the user from clicking OK button is the subject of continuing controversy.
In 2001 I worked on a database application that had an extremely complex list of input validations. At first, I thought about using the validating edit control which more likely do the validation on the WM_KILLFOCUS (OnKillFocus)
event, but it didn't let you leave the edit box until you got it right. It was very inconvenient when you had fields whose validation depends on other fields that could be modified. To make matters worse, I run into the most annoying problem many users asked for: they want to input data as fast as possible, sometimes use keypad to type numbers and press the Enter key to focus the next field, if they make an error in a field, a message box pops up telling the information about the error, and they can fix the problem without having to get rid of a error message box first.
Could it be possible to solve all the problem without changing the form's standard behavior? The answer is affirmative. After trying various approaches, I came up with a complete solution which would make me easy to do the forms' validation.
Now I stripped these classes from a running program; I had to make some minor changes to it to remove dependencies on my application and I hope it will help lots of people.
Features
Below are some features:
- The data will be validated when you click/Press OK button, any error will stop the form from closing. Focus will then be set to the first erroneous field.
- If there is any error when clicking OK button or if the current focused field is just the first erroneous field when pressing OK button, the error message will be displayed in a little window (similar to tooltip) just above the erroneous field for a certain laps of time (by default 5s).
- If you press any key or click mouse button, the error message will disappear preventing the user from clicking OK button and still get the message across
- If you want the error message to be displayed for a long time, just move the mouse cursor on it.
- You can always leave the form using the cancel button or the ESC key, even if the current field is in error, just like the normal behaviour of a dialog.
Using the code
Using the class is very simple and comfortable. Let's take a dialog for example, derive your class from CDialogExt
, and override OnValidate
function. That's it.
TestDialog.h : header file
#include "DialogExt.h"
class CTestDialog : public CDialogExt
{
public:
......
virtual void OnValidate (UINT &nCtrlID, CString &strError);
protected:
virtual void DoDataExchange(CDataExchange* pDX);
......
}
TestDialog.cpp : implementation file
void CTestDialog::OnValidate(UINT &nCtrlID, CString &strError)
{
UpdateData();
CString strEdit;
GetDlgItemText(IDC_SINGLELINE_EDIT, strEdit);
if (strEdit.IsEmpty()){
strError = "Single-line edit cannot be empty string.";
nCtrlID = IDC_SINGLELINE_EDIT;
return;
}
}
Technical details
CMessageTip
CMessagTip
is derived from CWnd
. To use the tool simply call Create(...)
and specify the parent window. When the Show(...)
member function is called I install the hook and use SetWindowPos(...)
to show the message box, and then start a timer at a 0.5s interval. Any time I get the Hook event in Hook proc, I post WM_CLOSE
message directly. In OnClose()
handler, I just call Hide()
member function which kill timer, dismiss the message box and remove the hook. In the timer proc, I keep decreasing the count by interval and periodically check if the mouse is still within the window bounds or switching to other applications. When the count reaches zero or switching to other applications I call Hide()
member function.
CDialogExt
CDialogExt
is derived from CDialog
. I declare a CMessagTip
member variable m_tip
and override WM_COMMAND
message handler OnCmdMsg(...)
. In OnCmdMsg(...)
, when nID
equal to IDOK
(click/press OK button) I call the virtual function OnValidate(...)
to get erroneous control ID and error message text specified in your overrided OnValidate(...)
function.
There is something worth mentioning when displaying the error message.
In order to mimic the behaviour of pressing Enter key to focus the next field, I show the error message only if the current cursor is still in the first erroneous field when pressing the OK button.
CFormViewExt
CFormViewExt
is derived from CFormView
. It is similar to CDialogExt
. I added two virtual functions OnOK()
and OnCancel()
. You can override them and add extra code there just like dialog.
CPropertyPageExt
CPropertyPageExt
is derived from CPropertyPage
. I declare a CMessagTip
member variable m_tip
and provide a virtual function OnValidate()
. ShowMessage(...)
and HideMessage()
will be called in CPropertySheetExt
class.
CPropertySheetExt
CPropertySheetExt
is derived from CPropertySheet
. I override WM_COMMAND
message handler OnCmdMsg(...)
here. In OnCmdMsg(...)
, I count the number of pages and handle all of them just like CDialogExt
. I notice that Enter key can not be sent from multi-line edit controls. So there is a little difference from CDialogExt
when displaying the error message.
Summary
The technique presented in this article have been extensively used in our application for nearly 3 years. I've not seen this idea documented elsewhere or any other products like that, so I hope this technique contributes significantly to the latter.