65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (17投票s)

2003 年 9 月 25 日

5分钟阅读

viewsIcon

106926

downloadIcon

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 年。我没有在其他地方看到过这个想法的文档,也没有看到过其他类似的产品,所以我希望这项技术能够为将来做出重大贡献。

© . All rights reserved.