190 likes | 340 Vues
Dialog Field Validation. Using MFC Data Exchange and Validation Code David J. Kruglinski. Summary. What is field validation? Existing MFC validation support The “Validator Class” solution The CValidDialog class Using CValidDialog in a sample app Cross-validation Technical details
E N D
Dialog Field Validation Using MFC Data Exchange and Validation Code David J. Kruglinski
Summary • What is field validation? • Existing MFC validation support • The “Validator Class” solution • The CValidDialog class • Using CValidDialog in a sample app • Cross-validation • Technical details • The CValidForm class for form-view apps
The Field Validation Problem • More people are using Windows for data entry applications. • Users expect immediate feedback after they make an error in a field. • Windows dialog box prodedure not optimized to support field validation. • Field validation not easy with SDK or MFC.
What is Field Validation? • When the user tries to leave a control (in a dialog box) that contains text (edit control, combobox).... • A function validates the data just entered. • Allows user to leave if data is OK. • Displays message box if data not OK. • Returns user to the same field.
What is Field Validation? • All fields are validated on OK button. • No validation on Escape button. • User is allowed to leave a control when moving to another application.
Existing MFC Validation Support • Validation of all fields on OK button • Derived dialog class has data members corresponding to controls • Generated DDX/DDV code called from OnOK virtual function • Validation is “too late” • No immediate feedback to user • Does allow cross-validation • Full support from ClassWizard
Existing MFC Validation Support • Control subclassing for keystroke validation • CWnd::SubclassDlgItem traps control’s messages before standard WndProc • A derived control class can map messages • Example: A derived CEdit class can accept only numeric keystrokes
The “Validator Class” Solution • Standard base class CValidDialog • CValidDialog base class is MFC’s CDialog. • Your dialog classes should be derived from CValidDialog instead of CDialog. • You override one virtual function, ValidateDlgItem. • #include “valid.h” • Add VALID.CPP to your project.
Sample Application A modal dialog displayed from the main menu
Writing Validation Code • Standard MFC DDX/DDV code • Generated by ClassWizard void CAboutDlg::DoDataExchange(CDataExchange* pDX) { //{{AFX_DATA_MAP(CAboutDlg) DDX_Text(pDX, IDC_EDIT1, m_nEdit1); DDV_MinMaxInt(pDX, m_nEdit1, 0, 10); DDX_Text(pDX, IDC_EDIT2, m_nEdit2); DDV_MinMaxInt(pDX, m_nEdit2, 20, 30); DDX_Text(pDX, IDC_EDIT3, m_strEdit3); DDV_MaxChars(pDX, m_strEdit3, 5); //}}AFX_DATA_MAP }
Writing Validation Code • The ValidateDlgItem function void CAboutDlg::ValidateDlgItem(CDataExchange* pDX, UINT uID) { switch (uID) { case IDC_EDIT1: DDX_Text(pDX, IDC_EDIT1, m_nEdit1); DDV_MinMaxInt(pDX, m_nEdit1, 0, 10); break; case IDC_EDIT2: DDX_Text(pDX, IDC_EDIT2, m_nEdit2); DDV_MinMaxInt(pDX, m_nEdit2, 20, 30); break; case IDC_EDIT3: DDX_Text(pDX, IDC_EDIT3, m_strEdit3); DDV_MaxChars(pDX, m_strEdit3, 5); default: break; } }
Writing Validation Code • Cross-validation • Independent of field validation • Executed on OK button • Code added to end of DoDataExchange • after //}}AFX_DATA_MAP if(pDX->m_bSaveAndValidate) { if(m_nEdit1 + m_nEdit2 < 30) { AfxMessageBox("Sum of Edit1 and Edit2 must be >= 30"); pDX->Fail(); } }
How CValidDialog Works • Traps KILLFOCUS message as user tries to leave control • Controls send “notification” WM_COMMAND messages to their parent dialog window • Edit control sends EN_KILLFOCUS • Calls virtual ValidDlgItem to validate data • MFC exception processing returns user to the field if there is an error
How CValidDialog Works -- Complications • Can’t call CWnd::SetFocus while a KILLFOCUS operation is in progress • Post user-defined message WM_VALIDATE • Handler sets focus back after KILLFOCUS completes • Must recognize the KILLFOCUS that results from the SetFocus call • Use a flag data member, m_bValidateOn
How CValidDialog Works -- Complications • Must regognize a KILLFOCUS when user switches to another app • Can call CWnd::GetParent to test if new parent is our same dialog window • Similar logic for modeless dialogs
MFC Command ProcessingControl Notifications • MFC first processes WM_COMMAND messages with a function CWnd::OnCommand( wParam, lParam) • Both parameters are 32 bits (Win32) • wParam LOWORD = control child window ID • wParam HIWORD = notification code • lParam = control window HWND • Base class handler dispatches command through message map
CValidDialog::OnCommand BOOL CValidDialog::OnCommand(WPARAM wParam, LPARAM lParam) { if(m_bValidationOn) { // might be a killfocus UINT notificationCode = (UINT) HIWORD( wParam ); if((notificationCode == EN_KILLFOCUS) || (notificationCode == LBN_KILLFOCUS) || (notificationCode == CBN_KILLFOCUS) ) { CWnd* pFocus = CWnd::GetFocus(); // if we're changing focus to another control in same dialog if( pFocus && (pFocus->GetParent() == this) ){ if(pFocus->GetDlgCtrlID() != IDCANCEL) { // and focus not in Cancel button // validate AFTER drawing finished PostMessage(WM_VALIDATE, wParam); } } } } return CDialog::OnCommand(wParam, lParam); // pass it on }
CValidDialog::OnValidate • Handler for WM_VALIDATE message ON_MESSAGE(WM_VALIDATE, OnValidate) LONG CValidDialog::OnValidate(UINT wParam, LONG lParam) { TRACE("Entering CValidDialog::OnValidate\n"); CDataExchange dx(this, TRUE); m_bValidationOn = FALSE; // temporarily off UINT controlID = (UINT) LOWORD( wParam ); TRY { ValidateDlgItem(&dx, controlID); } CATCH(CUserException, pUE) { // fall through -- user already alerted via message box } END_CATCH m_bValidationOn = TRUE; return 0; // goes no further }
CFormView Considerations • Same as dialog except use CValidForm derived from MFC CFormView • CValidForm is the new base class for your form views • ValidateDlgItem, OnCommand, OnValidate are the same • Could use multiple inheritance to save code duplication