在实际应用中,你会将这些值拷贝到其最终目的地。Figure 6 是 CMainDlg::OnOK 的全部代码。通过数据交换中的非耦合式的数据验证,CRegexForm 给予你更充分的 UI 控制,并使你避免 MFC 固有的缺陷。
Figure 6 CMainDlg::OnOK ////////////////// // User pressed OK: validate form and display results: error message // or field values—but don't call base class to end dialog. // void CMainDlg::OnOK() { UpdateData(TRUE); // get dialog data int nBad = m_rgxForm.Validate(); // validate CString msg; if (nBad>0) { vector<UINT> badFields = m_rgxForm.GetBadFields(); BOOL beep = TRUE; if (nBad>1) { // Multiple bad fields: show message box with bad fields. msg = _T("The following fields are bad:\n\n"); vector<UINT>::iterator it; for (it = badFields.begin(); it!=badFields.end(); it++) { UINT nID = *it; CString s; s.Format(_T("%s: %s\n"), m_rgxForm.GetFieldName(nID),m_rgxForm.GetFieldErrorMsg(nID)); msg += s; } MessageBox(msg,_T("Oops—Some fields are bad."),MB_ICONEXCLAMATION); beep = FALSE; // message box already beeped; don't beep again } // to highlight first bad field whether one or many UINT nID = badFields[0]; m_rgxForm.ShowBadField(nID, beep, TRUE); } else { // all fields OK: show feeback msg = _T("You Entered:\n\n"); for (int i=0; MyRegexForm[i].id; i++) { CString name = m_rgxForm.GetFieldName(MyRegexForm[i].id); CString val = m_rgxForm.GetFieldValue(MyRegexForm[i].id); if (val.IsEmpty()) val = _T("(nothing)"); CString temp; temp.Format(_T("%s = %s\n"), name, val); msg += temp; } MessageBox(msg,_T("Congratulations! All fields OK."),MB_OK); m_rgxForm.Feedback(_T(" All fields OK or empty!")); } } 我提到的反馈窗口,它由 CRegexForm 全权管理;你只需通过调用 CRegexForm::SetFeedBackWindow 提供一个这样的窗口即可。你可以为出错信息指定一种颜色。CRegexForm 还负责提示功能。默认情况下,当用户tab到某个新输入域时显示域提示(如图 Figure 1)。你可以调用 CRegexForm::SetShowHints(FALSE)来关闭提示功能。SetShowHints(TRUE, nDelay, nTimeout) 可以再次打开提示功能,这里 nDelay 是显示提示之前要等待的毫秒数(默认值=250),nTimeout 是提示显示的毫秒数(默认值=0,表示一直显示)。当用户离开输入域时(EN_KILLFOCUS),CRegexForm 自动清除其提示。TestForm 使用 SetShowHints 实现复选框提示的开启和关闭(参见 Figure 1)。
还有另外一个特性是否提出来我有些举棋不定。CRegexForm 有一个选项来做立即验证。我不建议使用它,因为我觉得那是一种不好的GUI设计,但很多时候在没有输入有效的信息之前你不想让用户tab到下一个输入域。在这种情况下,你可以在域映射中使用 RGXF_IMMED,或调用 SetValidateImmed 来使所有域都进行立即验证。TestForm 有一个复选框专门控制立即验证功能的开关。如果选中,你会看到为什么立即验证不是个好主意。
最后,看看最小/最大和字符限制约束特性?最小/最大是正则表达式无法做到的一件事。虽然“.{0,35}”是一个描述最多35个字符长的字符串正则表达式,但你还是愿意使用 EM_LIMITTEXT 来限制编辑控制的文本长度,所以当用户敲入过长的字符时发出蜂鸣声。因为我容忍实现了一个窗体验证系统,但它缺乏 MFC 已经支持的特性,我引进了“伪正则表达式”的概念。例如,IDC_AGE 的正则表达式(Age 输入域)为“rgx:minmax:int:1:120,maxchars:3”。显然,它不是一个真正的正则表达式。它是一个伪表达式,它由 CRegexForm 自己来识别和解释。其一般格式是:“rgx:expr,expr,..expr”,这里每个表达式描述不同的约束。目前支持两种类型:“minmax:type:minval:maxval”(此处 type 是 int 或 double)以及“maxchars:maxval”。CRegexForm 负责做专门解析,并对 maxchars 使用 EM_LIMITTEXT,就像 DDV_MinMaxInt 一样。具体实现细节请下载源代码。至于资源子串,正则表达式解析它们简直是小菜一碟。
CRegexForm 所能做的事情我已经说完了。它是如何工作的呢?由于篇幅所限,恕不赘言,但我大概勾勒一下。CRegexForm 我的 CSubclassWnd 来子类化对话框。对之前不了解它的读者来说,CSubclassWnd 是我很早以前写的一个类,它使用Windows的子类化机制来截获发送到另一个窗口的消息。CSubclassWnd 真正酷的地方是它不需要在你的类层次中插入新类便能让你子类某个 MFC 窗口。我可以从 CDialog 派生出 CRegexForm,但那样你就得从 CRegexForm 派生自己的对话框。那么如果你已经自己实现了从 CDialog 派生的 CBetterDialog 怎么办呢?此时,你不得不进行外科手术来插入 CRegexForm,结果将不可预见。
这是MFC的一大缺陷:它用派生实现子类化,所以类层次确切地反映了 Windows 子类机制。但没必要这样,编写像 CRegexForm 这样的插件类来子类化你的对话框而不用改动你的类层次是更好的解决方法。对我来说,CSubclassWnd 是如此不可或缺,没有它我无法编程!
CRegexForm 使用 CSubclassWnd 截获 EN_KILLFOCUS 和 EN_SETFOCUS 以隐藏和显示提示,截获 EN_CHANGE 以便用户敲入任何信息后清除输入域的出错状态。CRegexForm 用我的 CPopupText 类实现本身的提示,CPopupText 类第一次讨论是在 2000年九月的专栏。为了防止用户敲入不允许的字符,CRegexForm 为每一个具有 LegalChars 正则表达式的编辑控制安装了另一个 CSubclassWnd 派生的钩子。这是一个嵌套类,CRegexForm::CEditHook 截获发送到编辑控制的 WM_CHAR 消息,它吃掉任何非法输入字符并发出蜂鸣。 |