全新的验证器 - 模板扩展






4.26/5 (7投票s)
对标准MFC DDX/DDV机制的扩展,以及在WinAPI程序中进行数据验证的新方法
引言
消息框很糟糕。绝对的。在显示错误消息时,它们应该被视为最后的选择。消息框具有侵入性,会发出令人厌烦的“叮”声,并且有些令人恐惧和不安。这是第一个问题。第二个问题是 MFC 标准的 DDX/DDV 机制令我感到恼火。它非常不灵活,几乎不允许任何自定义。这些是我提出更好的数据验证方法以及显然向用户报告错误原因的主要原因。所以,这就是全部故事……
第一部分 - 基类
坦白说,那是我第一次进行某种规划,并在各种纸张上“草拟”我未来的类层次结构等等。首先,我最初认为这个库是完全基于模板的。同时,我需要一个接口来处理我所有的验证器类。正如您将在下一节中看到的,这种将基类分离为模板类和非模板类的方式是实现验证器容器所必需的,所以这里是这些类
// // General Validator interface. // struct IValidator { // // Virtual destructor virtual ~IValidator(void) { } // // Validation function. Returns true if validation // succeeded virtual bool Validate(void) = 0; };
很简单,不是吗?IValidator
只暴露了一个纯虚函数 Validate()
。
// // Typed validator. Type denotes the actual type of // the value being validated. // template <class Type> struct ITypedValidator : public IValidator { protected: typedef typename Type ValueType; ValueType m_tValue; };
ITypedValidator
只是 IValidator
接口的扩展,它引入了 ValueType
- 本质上是被验证的类型。它还有一个 m_tValue
成员变量,用于保存结果值。接下来是 ValidatorBase
类 - 它保存了两个消息字符串和被验证控件的标识符。
// // Validator Base. Base class for all validators. // class ValidatorBase { protected: // Control handle HWND m_hControl; // Error message title std::string m_strTitle; // Error message text std::string m_strText; // // Constructor ValidatorBase(HWND hControl, const std::string& strTitle, const std::string& strText) : m_hControl(hControl), m_strTitle(strTitle), m_strText(strText) { } // // Copy constructor ValidatorBase(const ValidatorBase& vb) : m_hControl(vb.m_hControl), m_strTitle(vb.m_strTitle), m_strText(vb.m_strText) { } // // Virtual destructor virtual ~ValidatorBase(void) { } // // Assignment operator const ValidatorBase& operator = (const ValidatorBase& vb) { m_hControl = vb.m_hControl; m_strTitle = vb.m_strTitle; m_strText = vb.m_strText; return *this; } };
到目前为止,一切都很清楚,不是吗?
第二部分 - 策略
如果您真的对高级 C++ 技术感兴趣,那么您绝对应该阅读 Andrei Alexandrescu 的“Modern C++ Design”。这是一本非常出色的书,非常值得一读。除了其他主题外,它还涵盖了策略 - 基本来说,是一种配置基于模板的类的方法。我使用策略的动机是提供尽可能大的灵活性(再次,与 MFC 相反)。
所以目前,有用于从控件检索文本、加载资源和向用户报告错误的策略。通过更改这些策略(并编写新的策略),您可以创建一个验证器,该验证器例如将从远程服务器上的 XML 文件加载字符串资源,通过利用正则表达式的力量来验证控件中的文本,并通过写入系统事件日志来报告错误(您的错误真的那么严重吗?)。正如您所见,可能性几乎是无穷无尽的。到目前为止,有用于以通常的 WinAPI 方式从控件检索文本、从可执行模块加载字符串资源以及通过消息框(哎呀……)和以花哨的 .NET 风格向用户报告错误的策略。
第三部分 - 验证器类
现在是时候处理正事了。首先,让我们看一下 GenericTypeValidator
类。
// // Generic Type Validator // Type - Type being validated // TypeValidator - function, returning true if input // string is a valid representative of the Type // TypeConverter - conversion function, converts from _TCHAR* to Type // ControlTextProviderPolicy - Control Text Provider Policy // ErrorReportingPolicy - Error Reporting Policy // ResourceLoaderPolicy - Resource Loader Policy // template <class Type, bool TypeValidator(const _TCHAR*), Type TypeConverter(const _TCHAR*), class ControlTextProviderPolicy = ControlTextProvider, class ErrorReportingPolicy = MessageBoxErrorReporting, class ResourceLoaderPolicy = ResourceLoader> class GenericTypeValidator : public ValidatorBase, public ITypedValidator<Type>, public ControlTextProviderPolicy, public ErrorReportingPolicy, public ResourceLoaderPolicy { protected: // If this class is a base class bool m_bBase; public: // // Constructor GenericTypeValidator(HWND hControl, const std::string& strTitle, const std::string& strText) : ValidatorBase(hControl, strTitle, strText), m_bBase(false) { } // // Constructor GenericTypeValidator(HWND hControl, UINT nIDTitle, UINT nIDText) : ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText)), m_bBase(false) { } // // Copy constructor GenericTypeValidator(const GenericTypeValidator& gtv) : ValidatorBase(gtv), m_bBase(false) { } // // Virtual destructor virtual ~GenericTypeValidator(void) { } // // Assignment operator const GenericTypeValidator& operator = (const GenericTypeValidator& gtv) { ValidatorBase::operator = (gtv); m_bBase = gtv.m_bBase; return *this; } // // Validation function virtual bool Validate(void) { std::string strText = GetControlText(m_hControl); if(!TypeValidator(strText.c_str())) { if(!m_bBase) ReportError(m_hControl, m_strTitle, m_strText); return false; } // if m_tValue = TypeConverter(strText.c_str()); if(!m_bBase) ReportSuccess(m_hControl); return true; } };
这个验证器基本上检查控件中的文本是否是某种特定类型的有效表示 - 无论是浮点值、整数值还是其他任何东西。
现在,我们有了 GenericRangeValidator
。
// // Generic Range Validator // Type - Type being validated // TypeValidator - function, returning true if input string // is a valid representative of the Type // TypeConverter - conversion function, converts from _TCHAR* to Type // Less - returns true if left-hand value is less than right-hand // Grater - returns true if left-hand value is greater than right-hand // ControlTextProviderPolicy - Control Text Provider Policy // ErrorReportingPolicy - Error Reporting Policy // ResourceLoaderPolicy - Resource Loader Policy // template <class Type, bool TypeValidator(const _TCHAR*), Type TypeConverter(const _TCHAR*), bool Less(const Type&, const Type&), bool Greater(const Type&, const Type&), class ControlTextProviderPolicy = ControlTextProvider, class ErrorReportingPolicy = MessageBoxErrorReporting, class ResourceLoaderPolicy = ResourceLoader> class GenericRangeValidator : public GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy, ResourceLoaderPolicy> { // Lower Bound ValueType m_tLower; // Upper bound ValueType m_tUpper; public: // // Constructor GenericRangeValidator(HWND hControl, const std::string& strTitle, const std::string& strText, ValueType tLower, ValueType tUpper) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(hControl, strTitle, strText), m_tLower(tLower), m_tUpper(tUpper) { m_bBase = true; } // // Constructor GenericRangeValidator(HWND hControl, UINT nIDTitle, UINT nIDText, ValueType tLower, ValueType tUpper) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(hControl, nIDTitle, nIDText), m_tLower(tLower), m_tUpper(tUpper) { m_bBase = true; } // // Copy constructor GenericRangeValidator(const GenericRangeValidator& grv) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(grv), m_tLower(grv.m_tLower), m_tUpper(grv.m_tUpper) { m_bBase = true; } // // Virtual destructor virtual ~GenericRangeValidator(void) { } // // Assignment operator const GenericRangeValidator& operator = (const GenericRangeValidator& grv) { GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>::operator = (grv); m_tLower = grv.m_tLower; m_tUpper = grv.m_tUpper; m_bBase = true; return *this; } // // Validation function virtual bool Validate(void) { if(!GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>::Validate()) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if if(Less(m_tValue, m_tLower) || Greater(m_tValue, m_tUpper)) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if ReportSuccess(m_hControl); return true; } };
它继承自 GenericTypeValidator
,并检查 m_tValue
是否在指定的范围内。这个类可以被参数化,例如,使用比较函数。GenericComparisonValidator
的功能与 GenericRangeValidator
几乎相同,但它将 m_tValue
与唯一一个值进行比较,因此可以用于验证,例如,最大值和最小值。
// // Generic Comparison Validator // Type - Type being validated // TypeValidator - function, returning true // if input string is a valid representative of the Type // TypeConverter - conversion function, converts from _TCHAR* to Type // Comparer - returns true if comparison is correct // ControlTextProviderPolicy - Control Text Provider Policy // ErrorReportingPolicy - Error Reporting Policy // ResourceLoaderPolicy - Resource Loader Policy // template <class Type, bool TypeValidator(const _TCHAR*), Type TypeConverter(const _TCHAR*), bool Comparer(const Type&, const Type&), class ControlTextProviderPolicy = ControlTextProvider, class ErrorReportingPolicy = MessageBoxErrorReporting, class ResourceLoaderPolicy = ResourceLoader> class GenericComparisonValidator : public GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy, ResourceLoaderPolicy> { // Base Value ValueType m_tBase; public: // // Constructor GenericComparisonValidator(HWND hControl, const std::string& strTitle, const std::string& strText, ValueType tBase) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(hControl, strTitle, strText), m_tBase(tBase) { m_bBase = true; } // // Constructor GenericComparisonValidator(HWND hControl, UINT nIDTitle, UINT nIDText, ValueType tBase) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(hControl, nIDTitle, nIDText), m_tBase(tBase) { m_bBase = true; } // // Copy constructor GenericComparisonValidator(const GenericComparisonValidator& gcv) : GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>(gcv), m_tBase(gcv.m_tBase) { m_bBase = true; } // // Virtual destructor virtual ~GenericComparisonValidator(void) { } // // Assignment operator const GenericComparisonValidator& operator = (const GenericComparisonValidator& gcv) { GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>::operator = (gcv); m_tBase = true; return *this; } // // Validation function virtual bool Validate(void) { if(!GenericTypeValidator<Type, TypeValidator, TypeConverter, ControlTextProviderPolicy, ErrorReportingPolicy>::Validate()) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if if(!Comparer(m_tBase, m_tValue)) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if ReportSuccess(m_hControl); return true; } };
GenericStringValidator
是一个特殊版本(不是特化)的用于处理字符串的验证器。
// // Generic String Validator // Validator - returns true if comparison is correct // ControlTextProviderPolicy - Control Text Provider Policy // ErrorReportingPolicy - Error Reporting Policy // ResourceLoaderPolicy - Resource Loader Policy // template <bool Validator(const std::string&), class ControlTextProviderPolicy = ControlTextProvider, class ErrorReportingPolicy = MessageBoxErrorReporting, class ResourceLoaderPolicy = ResourceLoader> class GenericStringValidator : public ValidatorBase, public ITypedValidator<std::string>, public ControlTextProviderPolicy, public ErrorReportingPolicy, public ResourceLoaderPolicy { public: // // Constructor GenericStringValidator(HWND hControl, const std::string& strTitle, const std::string& strText) : ValidatorBase(hControl, strTitle, strText) { } // // Constructor GenericStringValidator(HWND hControl, UINT nIDTitle, UINT nIDText) : ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText)) { } // // Copy constructor GenericStringValidator(const GenericStringValidator& gsv) : ValidatorBase(gsv) { } // // Virtual destructor virtual ~GenericStringValidator(void) { } // // Assignment operator const GenericStringValidator& operator = (const GenericStringValidator& gsv) { ValidatorBase::operator = (gsv); return *this; } // // Validation function virtual bool Validate(void) { m_tValue = GetControlText(m_hControl); if(!Validator(m_tValue)) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if ReportSuccess(m_hControl); return true; } };
GenericControlValidator
旨在通过向各种控件发送适当的消息来处理它们。
// // Generic Control Validator // ControlValidator - function, returning true if the control is in valid state // ErrorReportingPolicy - Error Reporting Policy // ResourceLoaderPolicy - Resource Loader Policy // template <bool ControlValidator(HWND), class ErrorReportingPolicy = MessageBoxErrorReporting, class ResourceLoaderPolicy = ResourceLoader> class GenericControlValidator : public ValidatorBase, public IValidator, public ErrorReportingPolicy, public ResourceLoaderPolicy { public: // // Constructor GenericControlValidator(HWND hControl, const std::string& strTitle, const std::string& strText) : ValidatorBase(hControl, strTitle, strText) { } // // Constructor GenericControlValidator(HWND hControl, UINT nIDTitle, UINT nIDText) : ValidatorBase(hControl, LoadString(nIDTitle), LoadString(nIDText)) { } // // Copy constructor GenericControlValidator(const GenericControlValidator& gcv) : ValidatorBase(gcv.m_hControl, gcv.m_strTitle, gcv.m_strText) { } // // Virtual destructor virtual ~GenericControlValidator(void) { } // // Assignment operator const GenericControlValidator& operator = (const GenericControlValidator& gcv) { ValidatorBase::operator = (gcv); return *this; } // // Validation function virtual bool Validate(void) { if(!ControlValidator(m_hControl)) { ReportError(m_hControl, m_strTitle, m_strText); return false; } // if ReportSuccess(m_hControl); return true; } };
第四部分 - 使用它们
这个特定的东西非常直接。假设您有一个对话框,您打算验证其中的内容。现在您有几种选择。您可以在 OnOK
处理程序中以一种比较常规的方式验证所有内容,或者执行即时验证,即在 WM_KICKIDLE
消息处理程序中。这两种选择的差异微不足道,但后者最好使用非侵入性的错误报告策略。好的,让我们开始编码。首先,将 validator.h 添加到您的项目中。然后,在您的对话框类中添加一个 Validator::ValidatorPool
成员变量。您可以添加大量的特定验证器,但自己调用它们并不是一件很有趣的事。但无论如何 - 如果您想要的话……现在我们必须添加一些验证器。这就是这样做的方式
// // Initializing validators m_vpValidators.AddValidator(IDC_EDIT1, IValidatorPtr(new IntegerValidator(*GetDlgItem(IDC_EDIT1), "Integer Value", "Please enter an integer value")), true); m_vpValidators.AddValidator(IDC_EDIT2, IValidatorPtr(new FloatValidator(*GetDlgItem(IDC_EDIT2), "Floating Point Value", "Please enter a floating point value")), true); m_vpValidators.AddValidator(IDC_EDIT3, IValidatorPtr(new IntegerInclusiveRangeValidator(*GetDlgItem(IDC_EDIT3), "Ranged Integer Value", "Please enter an integer value ranging from 0 to 542", -1, 543)), true); m_vpValidators.AddValidator(IDC_EDIT4, IValidatorPtr(new NotEmptyStringValidator(*GetDlgItem(IDC_EDIT4), "Non-Empty String", "Please enter something...")), true);
现在是时候做选择了(我们难道不是一直在做选择吗?)。对于“在 OnOK
中验证”的方法,请重写您对话框类的 OnOK
虚函数,并添加以下行
// ... if(!m_vpValidators.Validate()) return; // ...
就是这样。当然,您可以根据某些条件(例如,复选框被选中,或者列表控件中的项目被选择 - 任何内容)来切换某些特定验证器,以禁用对这些特定控件的验证。这是第二种选择。修改您的 stdafx.h,添加此 include
语句
#include <afxpriv.h>
在您的对话框头类中,添加以下原型
afx_msg LRESULT OnKickIdle(WPARAM wParam, LPARAM lParam);
将此条目添加到消息映射中
ON_MESSAGE(WM_KICKIDLE, OnKickIdle)
并这样实现 OnKickIdle
LRESULT CValidatorsDlg::OnKickIdle(WPARAM wParam, LPARAM lParam) { GetDlgItem(IDOK)->EnableWindow(m_vpValidators.Validate()); return 0; }
再次,就是这样。
第五部分 - 其他
我们几乎完成了。首先,我不能保证此代码可以与所有编译器一起编译。我使用 Visual C++ 7.1 (Microsoft Visual Studio .NET 2003 自带的版本) 编写了它,在那里它可以正常工作。对于任何有关可移植性和兼容性的评论,我将不胜感激。
另外,我想实现的一些事情。首先,正则表达式 - 主要用于验证电子邮件、URL 和信用卡。当然,有 ::PathIsURL
API,但为什么来自 Redmond 的家伙认为所有以 http:// 开头的都是有效的 URL?该死,就连 Pocket Word 也认为这是一个 URL!其次 - 气球工具提示错误报告策略。当然,还有您所有的建议。谢谢!