Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A simple solution for validation of MFC forms

0.00/5 (No votes)
24 Sep 2003 1  
An easy way to do validation for MFC Dialogs, FormViews, PropertyPages

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
{
// Construction

public:
......
// Overrides

    virtual void OnValidate (UINT &nCtrlID, CString &strError);
// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CTestDialog)

protected:
    virtual void DoDataExchange(CDataExchange* pDX); 
//}}AFX_VIRTUAL

......
}

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.

  • 1001 is the ID value for the edit control associated to the combobox. Assume nID is the ID value of combobox and "Dropdown" style is set. If the combobox control is focused, you can use GetDlgItem(nID)->GetDlgItem(1001) to get the focused window.
  • For multi-line edit controls, the Enter key can be sent but only if the edit control is a multi-line edit control and if the "Want Return" style is set. If you press Enter key over a multi-line edit control, you can get it's window in PreTranslateMessage handler:
    if (pMsg->message ==  WM_KEYDOWN &&pMsg->wParam == VK_RETURN)
    {
        CWnd *pWnd = FromHandle(pMsg->hwnd);
        if (pWnd != GetDlgItem(IDOK)) 
           m_pWndFrom = pWnd;
    }
    

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.

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