MFC 表单验证的简单解决方案






4.94/5 (17投票s)
2003 年 9 月 25 日
5分钟阅读

106926

2657
一种简单的方法来验证 MFC 对话框、窗体视图、属性页
引言
程序员面临的众多任务之一是处理操作员输入的无效数据。对于 MFC 表单(例如对话框、窗体视图),是在控件失去焦点时执行验证,还是在用户单击“确定”按钮时验证整个表单,以及如何显示错误以阻止用户单击“确定”按钮,这是一个持续争议的话题。
2001 年,我参与了一个数据库应用程序,该应用程序具有极其复杂的输入验证列表。起初,我考虑使用验证编辑控件,它更可能在 WM_KILLFOCUS (OnKillFocus)
事件上执行验证,但这直到你正确输入之前不允许你离开编辑框。当有些字段的验证依赖于可能被修改的其他字段时,这非常不方便。更糟糕的是,我遇到了许多用户要求的、最令人恼火的问题:他们希望尽快输入数据,有时使用小键盘输入数字并按 Enter 键聚焦到下一个字段,如果他们在某个字段中出错,就会弹出一个消息框告知错误信息,而他们可以在不先处理错误消息框的情况下修复问题。
能否在不改变表单标准行为的情况下解决所有问题?答案是肯定的。在尝试了各种方法之后,我得出了一个完整的解决方案,这将使我能够轻松地进行表单验证。
现在我从一个正在运行的程序中剥离了这些类;我不得不对它做了一些小的修改,以移除对我的应用程序的依赖,希望它能帮助很多人。
特点
以下是一些特性
- 数据将在您单击/按下“确定”按钮时进行验证,任何错误都将阻止表单关闭。然后焦点将设置到第一个有错误的字段。
- 如果在单击“确定”按钮时出现任何错误,或者在按下“确定”按钮时当前聚焦的字段恰好是第一个有错误的字段,错误消息将显示在一个小窗口中(类似于工具提示),位于有错误字段的上方,持续一段时间(默认为 5 秒)。
- 如果您按下任何键或单击鼠标按钮,错误消息将消失,阻止用户单击“确定”按钮,但仍能传达信息。
- 如果您希望错误消息长时间显示,只需将鼠标光标悬停在其上。
- 您始终可以使用“取消”按钮或 ESC 键离开表单,即使当前字段有错误,就像对话框的正常行为一样。
使用代码
使用该类非常简单舒适。以对话框为例,将您的类从 CDialogExt
派生,并重写 OnValidate
函数。就是这样。
TestDialog.h : 头文件
#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 : 实现文件
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; } }
技术细节
CMessageTip
CMessagTip
派生自 CWnd
。要使用此工具,只需调用 Create(...)
并指定父窗口。当调用 Show(...)
成员函数时,我将安装钩子并使用 SetWindowPos(...)
显示消息框,然后以 0.5 秒的间隔启动一个计时器。任何时候我在 Hook proc 中收到 Hook 事件,我都会直接发布 WM_CLOSE
消息。在 OnClose()
处理程序中,我只需调用 Hide()
成员函数,它会杀死计时器、关闭消息框并移除钩子。在计时器 proc 中,我不断地根据间隔减少计数,并定期检查鼠标是否仍在窗口范围内或切换到其他应用程序。当计数达到零或切换到其他应用程序时,我调用 Hide()
成员函数。
CDialogExt
CDialogExt
派生自 CDialog
。我声明了一个 CMessagTip
成员变量 m_tip
,并重写了 WM_COMMAND
消息处理程序 OnCmdMsg(...)
。在 OnCmdMsg(...)
中,当 nID
等于 IDOK
(单击/按下“确定”按钮)时,我调用虚拟函数 OnValidate(...)
来获取您在重写的 OnValidate(...)
函数中指定的错误控件 ID 和错误消息文本。
在显示错误消息时,有几点值得一提。
- 1001 是与组合框关联的编辑控件的 ID 值。假设
nID
是组合框的 ID 值,并且设置了“Dropdown”样式。如果组合框控件获得焦点,您可以使用GetDlgItem(nID)->GetDlgItem(1001)
来获取焦点窗口。 - 对于多行编辑控件,可以发送 Enter 键,但前提是编辑控件是多行编辑控件并且设置了“Want Return”样式。如果您在多行编辑控件上按下 Enter 键,您可以在
PreTranslateMessage
处理程序中获取其窗口。if (pMsg->message == WM_KEYDOWN &&pMsg->wParam == VK_RETURN) { CWnd *pWnd = FromHandle(pMsg->hwnd); if (pWnd != GetDlgItem(IDOK)) m_pWndFrom = pWnd; }
为了模仿按下 Enter 键聚焦到下一个字段的行为,我仅当在按下“确定”按钮时当前光标仍在第一个有错误字段时才显示错误消息。
CFormViewExt
CFormViewExt
派生自 CFormView
。它类似于 CDialogExt
。我添加了两个虚拟函数 OnOK()
和 OnCancel()
。您可以重写它们并添加额外的代码,就像对话框一样。
CPropertyPageExt
CPropertyPageExt
派生自 CPropertyPage
。我声明了一个 CMessagTip
成员变量 m_tip
,并提供了一个虚拟函数 OnValidate()
。ShowMessage(...)
和 HideMessage()
将在 CPropertySheetExt
类中被调用。
CPropertySheetExt
CPropertySheetExt
派生自 CPropertySheet
。我在此处重写了 WM_COMMAND
消息处理程序 OnCmdMsg(...)
。在 OnCmdMsg(...)
中,我计算页数并像 CDialogExt
一样处理所有页面。我注意到 Enter 键无法从多行编辑控件发送。因此,与 CDialogExt
在显示错误消息方面存在一些小差异。
摘要
本文介绍的技术在我们的应用程序中已经使用了将近 3 年。我没有在其他地方看到过这个想法的文档,也没有看到过其他类似的产品,所以我希望这项技术能够为将来做出重大贡献。