如何添加网站板块,wordpress评论 ajax,手机怎么查看网站代码实现的,asp黑网站源码摘要
一直对WinForm中没有像WebForm中那样的验证控件耿耿于怀#xff0c;这几天准备开发一套类似的控件。在网上找到大牛Michael Weinhardt的一个系列文章#xff0c;写得非常棒#xff0c;所以基本上按他的思路下来的。 在获取用户输入及后续的处理过程中#xff0c;数据…摘要
一直对WinForm中没有像WebForm中那样的验证控件耿耿于怀这几天准备开发一套类似的控件。在网上找到大牛Michael Weinhardt的一个系列文章写得非常棒所以基本上按他的思路下来的。 在获取用户输入及后续的处理过程中数据校验是关键的一步。本文将对Windows Forms中的校验机制进行探讨分析如何通过开发自定义验证组件来提供更为高效的验证体验类似于ASP.NET中的验证控件。
Windows Forms 验证机制介绍
简单地说验证是对数据进行处理前确保其完整和正确的过程。验证可以实现在数据层和业务规则层而应当在表现层进行前端的”保护”。开发人员通常在UI中为用户提供友好的、可交互的验证体验而要避免在N层应用程序中进行不必要的网络间往返验证。验证包含数据类型、范围或业务规则等类型看下面这个简单的例子
!--[if !vml]--!--[endif]--
这个窗体中需要进行下列验证
NameDate of Birth和Phone Number为必填项 Date of Birth必须为正确的日期值 Phone Number必须为正确的格式 新添加的雇员必须年满18岁杜绝童工
要完成这些验证需要一个合适的机制Windows Forms已经提供了一种内置在每个控件中。要使控件支持验证须将它的CausesValidation 属性设置为true这也是所有控件的默认值。如果控件的CausesValidation 属性设置为true那么在它将焦点转移到另一个控件并且它的CausesValidation也为true时会触发Validating 事件。因此我们可以处理控件的Validating事件在这里实现验证逻辑像下面这样 private void txtName_Validating( object sender, CancelEventArgs e){ if (txtName.Text.Trim().Length 0 ) { e.Cancel true ; return ; }} Validating 事件提供了CancelEventArgs 类型的参数它的Cancel属性使我们可以指定控件的值是否有效。如果Cancel为true即是无效的焦点仍然停留在无效的控件中如果Cancel值为false即通过了验证则会触发Validated事件焦点也会转移到新的控件。
现在责任落到了我们开发人员这边要以可视化的方式通知用户数据是否有效也许你想到的是状态栏这种方式存在两个问题 状态栏只能每次显式一条错误信息即使窗体包含多个无效的控件输入 状态栏离输入控件”很远”很难确切指明哪个控件出现了错误。
据传闻微软曾做过这么一个可用性研究人们坐在椅子上运行一个程序状态栏给出一个通知信息叫他们往椅子底下看这样就可以得到50美元奖金。但在测试期间竟没有任何人能拿走这50美元 此时ErrorProvider组件是更好的选择
ErrorProvider组件的用法非常简单此处不再赘述Validating事件的代码如下 if (txtName.Text.Trim().Length 0 ){ errorProvider1.SetError(txtName, Name is required. ); e.Cancel true ; return ;}errorProvider1.SetError(txtName, string .Empty); CausesValidation、Validating和ErrorProvider提供了控件级验证的基础机制我们可以用它们对控件逐一进行验证。
窗体级验证
Validating和
ErrorProvider这对组合是一个不错的解决方案可以在用户输入数据的时候进行验证。不幸的是这种方法可能会使得我们无法进行窗体级的验证而这在用户点击OK按钮提交数据时显然是必要的因为用户在点击OK按钮前有些控件可能未曾获得过焦点它们的控件级验证代码也就不起作用了。先看看窗体级验证的代码 foreach (Control ctrl in this .Controls){ ctrl.Focus(); if ( ! Validate()) { this .DialogResult DialogResult.None; return ; }} 但Cancel按钮就不需要实现窗体级的验证了它的工作往往是简单地将窗体关闭。但是现在如果当前拥有焦点的控件数据是无效的Cancel按钮将不能点击因为Cancel按钮的CausesValidation属性默认为true焦点会一直停留在无效的控件上。我们只要将Cancel按钮的CausesValidation属性设置为false就好了。
注意这里的窗体应当是模式窗体否则即使CausesValidation属性设置为false也不能点击。 至此使用数十行代码我们的AddEmployee窗体就可以支持基本的验证了。 编程式验证 vs. 声明式验证
从生产力的角度来看上面的解决方案有一个根本的问题如果一个程序包含多个窗体而每个窗体又包含多个控件那么将需要大量的用于验证的代码。这些代码增大了UI的复杂性使得程序难以维护显式是应当避免的。一种方法是将那些通用的验证逻辑抽象为可重用的类型。有了这样的类型还仅仅是第一步它仍需要编写代码。{TODO}我们需要这样的解决方案它具有Windows Forms UI的特点因此Windows Forms组件或控件是我们不错的选择。以这种方式封装后开发人员的工作就变成从工具箱上拖一个组件或控件放到窗体上通过诸如属性浏览器Property Browser这样的设计期特性来配置它然后让Windows Forms设计器帮我们将这些配置转换为代码这些代码会出现在InitializeComponent方法中。这样原来的编程式programmatic体验变成了声明式declarative体验而后者往往意味着高效。
添加设计期支持
第一步是添加设计期的支持如果我们的实现不需要UI支持可以从三种设计期组件继承System.ComponentModel.Component System.Windows.Forms.Control和 System.Windows.Forms.UserControl. Component否则可以继承Control或UserControl。Control和UserControl的不同之处在于其呈现的方式前者需要编写代码来呈现它而后者则通过其它控件或组件来呈现它。我们在前面使用的验证代码没有绘制任何内容而是借助于ErrorProvider来提示用户。因此Component是我们最合适的选择。
Imitation Is the Sincerest Form of Flattery
下一步是要确定我们需要哪些种类的验证组件可以参考一下ASP.NET中验证控件的实现机制。这样能保持一定的一致性而且也不需要”重新发明轮子”了。这样那些ASP.NET的开发人员也更容易上手。ASP.NET现在提供了如下的验证控件 验证控件 描述 RequiredFieldValidator 计算输入控件的值以确保用户输入值。 RegularExpressionValidator 计算输入控件的值以确定该值是否与某个正则表达式所定义的模式相匹配。 CompareValidator 将由用户输入到输入控件的值与输入到其他输入控件的值或常数值进行比较。 RangeValidator 检查输入控件的值是否在指定的值范围内。 CustomValidator 对输入控件执行用户定义的验证。
同时我们还要考虑可扩展性开发人员在必要的时候可以较为容易地开发自定义的验证组件。最后这个实现应当利用Windows Forms中已有的验证机制前面提及的部分。
引入RequiredFieldValidator
有了上面的设计思路现在要来点真的了。让我们从最简单的验证情形开始RequiredFieldValidator。
建立一个Component类名称为RequiredFieldValidator其接口应当与ASP.NET中的对应类相同 public partial class RequiredFieldValidator : Component{ string ControlToValidate { get ; set ;} string ErrorMessage { get ; set ;} string InitialValue { get ; set ;} bool IsValid { get ; set ;} void Validate();} 下面是每个成员的含义 成员 描述 ControlToValidate 指定要验证的控件 ErrorMessage 控件未通过验证时显式的信息。 InitialValue 某些情况下控件的默认值用作提示如”请选择种类”这时必填项意味着必须与默认值不同。此时用InitialValue。 IsValid 在调用Validate方法后报告控件的数据是否有效默认为true。 Validate 验证指定控件的值并设置IsValid。
在ASP.NET中ControlToValidate是字符串类型的这种间接的做法在基于请求、无状态的Web应用程序中是必要的。但在Windows Forms中我们则不必这么做我们可以直接引用控件。同时我们要在内部使用ErrorProvider组件所以为其添加一个Icon属性 public partial class RequiredFieldValidator : Component{ … Control ControlToValidate { get ; set ;} Icon Icon { get ; set ;} …} 好来看看具体的实现代码 public partial class RequiredFieldValidator : Component { #region Private Fields private Control controlToValidate null; private string errorMessage string.Empty; private string initialValue string.Empty; private bool isValid true; private Icon icon new Icon(typeof(ErrorProvider), Error.ico); private ErrorProvider errorProvider new ErrorProvider(); #endregion #region Constructors public RequiredFieldValidator() { InitializeComponent(); } public RequiredFieldValidator(IContainer container) { container.Add(this); InitializeComponent(); } #endregion #region Public Properties [Category(Behaviour)] [Description(Get or sets the control to validate.)] [DefaultValue(null)] [TypeConverter(typeof(ValidatableControlConverter))] public Control ControlToValidate { get { return controlToValidate; } set { controlToValidate value; if ((controlToValidate ! null) (!DesignMode)) { controlToValidate.Validating new CancelEventHandler(controlToValidate_Validating); } } } [Category(Appearance)] [Description(Gets or sets the text for the error message.)] [DefaultValue()] public string ErrorMessage { get { return errorMessage; } set { errorMessage value; } } [Category(Appearance)] [Description(Gets or sets the Icon to display error message.)] public Icon Icon { get { return icon; } set { icon value; } } [Category(Behaviour)] [Description(Gets or sets the default value to validate against.)] [DefaultValue()] public string InitialValue { get { return initialValue; } set { initialValue value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsValid { get { return isValid; } set { isValid value; } } #endregion public void Validate() { if (controlToValidate null) { isValid true; return; } string controlValue controlToValidate.Text.Trim(); string _initValue; if (initialValue null) { _initValue string.Empty; } else { _initValue initialValue.Trim(); } isValid (controlValue ! _initValue); if (isValid) { errorProvider.SetError(controlToValidate, string.Empty); } else { errorProvider.SetError(controlToValidate, errorMessage); } } private void controlToValidate_Validating(object sender, CancelEventArgs e) { Validate(); } } #region ValidatableControlConverter public class ValidatableControlConverter : ReferenceConverter { public ValidatableControlConverter(Type type) : base(type) { } protected override bool IsValueAllowed(ITypeDescriptorContext context, object value) { return ((value is TextBox) || (value is ListBox) || (value is ComboBox) || (value is UserControl)); } } #endregion 这种实现的关键在于如何挂接ControlValidate控件的Validating事件这种做法与前面的控件级验证相一致还有一个额外的好处这里的ControlToValidate_Validating方法中没有设置CancelEventArgs参数的Cancel属性这样就不会把用户困在一个控件中。
组件的验证功能已经实现了同时还为其添加了设计期支持。最终实现还提供了其它一些设计期特性
!--[if !supportLists]--指定了在属性浏览器中设置ControlToValidate时可以选择的控件种类 在属性浏览器中隐藏了IsValid属性因为它是运行时的属性。
编译然后将组件添加到工具箱。
让我们回到前面的AddEmployee窗体现在不再需要处理Validating事件了只要拖3个组件到窗体然后为它们设置属性。
!--[if !vml]--!--[endif]--
其中Phone Number域的验证组件的InitialValue为”Your number here.”。怎么样是不是很high
BaseValidator分而治之 实现了RequiredFieldValidator后其它类型的验证组件应当比较容易实现了。先别急可没你想的那么简单。RequiredFieldValidator类把
特定的”必填”逻辑和其它对每个验证组件都适用的
通用逻辑耦合在一起了。这种情况下应当把RequiredFieldValidator分解为两个类型BaseValidator和减肥后的RequiredFieldValidator。 abstract class BaseValidator : Component { void Validate() { _isValid EvaluateIsValid(); } protected abstract bool EvaluateIsValid();} 这样定义的效果是BaseValidator必须通过继承后才能使用而EvaluateIsValid则必须实现。Validate方法通过EvaluateIsValid方法来设置IsValid。这种技术也应用在了ASP.NET的验证控件上。
BaseValidator实现后需要对RequiredFieldValidator进行重构 [ToolboxBitmap( typeof (RequiredFieldValidator), RequiredFieldValidator.ico )] class RequiredFieldValidator : BaseValidator { string InitialValue {} protected override bool EvaluateIsValid() { string controlValue ControlToValidate.Text.Trim(); string initialValue; if ( _initialValue null ) initialValue ; else initialValue _initialValue.Trim(); return (controlValue ! initialValue); }} 更进一步实现其它验证组件
通过使用基类和派生类将通用逻辑和特定逻辑分离后我们可以把注意力集中在特定的验证逻辑这在RequiredFieldValidator中效果不错。下面会看到对于其它类型的验证组件同样很好它们是
!--[if !supportLists]--RegularExpressionValidatorCustomValidatorCompareValidatorRangeValidator
现在把它们一一实现。
RegularExpressionValidator
正则表达式是一种强大的文本模式匹配技术。如果文本域需要一定的模式正则表达式无疑是很好的选择。 using System.Text.RegularExpressions;[ToolboxBitmap( typeof (RegularExpressionValidator), RegularExpressionValidator.ico )] class RegularExpressionValidator : BaseValidator { string ValidationExpression {} protected override bool EvaluateIsValid() { // Dont validate if empty if ( ControlToValidate.Text.Trim() ) return true ; // Successful if match matches the entire text of ControlToValidate string field ControlToValidate.Text.Trim(); return Regex.IsMatch(field, _validationExpression.Trim()); }} 在设计时开发人员可以通过属性浏览器提供用于验证的正则表达式。 CustomValidator
人生在世不如意者十有八九。我们定义的验证组件不可能解决所有问题尤其是面对复杂的业务规则的时候。这时只能编写自定义代码CustomValidator 允许我们编写这些自定义代码同时仍能与其它的验证组件保持一致这在窗体级的统一验证过程中很重要。CustomValidator 提供了Validating事件和ValidatingCancelEventArgs 处理CustomValidator的Validating事件时只需在属性浏览器中双击 然后只需添加合适的验证逻辑以确保新增的雇员不小于18岁 private void customValidator1_Validating( object sender, CustomValidator.ValidatingCancelEventArgs e){ DateTime birth; bool isDate DateTime.TryParse(txtBirth.Text, out birth); if (isDate) { DateTime legal DateTime.Now.AddYears( - 18 ); e.Valid (birth legal); } else { e.Valid false ; }} 如果小于18岁就会提示用户 BaseCompareValidator 到目前为止我们的组件只能处理单个文本域的值。但在某些情况下验证过程可能涉及多个文本域或值比如确保文本域的值在两个值之间RangeValidator或比较两个文本域的值是否相等CompareValidator。不管哪种情况我们都需要考虑类型检查、转换和比较等过程。这个功能应当封装在一个新的类型中
BaseCompareValidator而RangeValidator和CompareValidator则继承自它。 ValidationDataType是一个自定义枚举类型在何种数据类型下进行比较验证。
RangeValidator
如果需要确保控件的输入值在指定的范围内RangeValidator 可以满足需要。它需要开发人员指定最大值和最小值还有输入值的数据类型。
!--[if !vml]--!--[endif]--
CompareValidator
最后来看看CompareValidator它用来进行控件的等值测试可以与另一个控件的值或者指定的值进行比较。Operator属性指定了比较操作的类型ControlToCompare和 ValueToCompare则指定了要比较的控件和指定值。如果Operator属性为DataTypeCheck则还可以判断控件的值是否为指定类型。
!--[if !vml]--!--[endif]--
完整的自定义验证组件结构
我们身在何处
首先我们对Windows Forms中的校验机制进行了探讨然后将这些验证逻辑封装到了几个支持设计时操作的组件通过开发自定义验证组件来提供更为高效的验证体验类似于ASP.NET中的验证控件。但目前还仅限于控件级的验证。下一篇文章中将讨论如何进行窗体级的验证届时ValidationSummary组件也会闪亮登场。示例代码下载CustomValidatorSample.rar参考1. Extending Windows Forms with a Custom Validation Component Library. By Michael Weinhardt2. Windows Forms Programming in C#. By Chris Sells备注本文引用自http://www.cnblogs.com/anderslly/archive/2007/04/18/customvalidatorpart1.html 我们身在何处 上一篇中我们利用Windows Forms中的验证机制实现了一套组件它们是可重用的并且可以利用VS的窗体设计器最终我们实现了控件级的验证。也就是说当用户在控件间转移时进行验证。 不幸的是用户数据填写完毕进行提交时我们无法保证他们能够填写过每个控件当然也就没法验证所有控件了。这时窗体级的验证就很有必要了。好了我们先来看看如何利用已有的组件以编程的方式进行窗体级的验证。 编程式的窗体级验证 最简单的方法是在用户提交数据时对控件逐一进行验证。每个验证组件都提供了Validate方法和IsValid属性可以以此来判断是不是每个控件的输入都是有效的。备注本文引用自http://www.cnblogs.com/anderslly/archive/2007/05/08/customvalidationlibrary2.html private void btnOK_Click( object sender, EventArgs e) { // 验证所有控件 reqName.Validate(); reqBirth.Validate(); rngSpeed.Validate(); reqCommence.Validate(); // 判断输入是否有效 if ((reqName.IsValid) (reqBirth.IsValid) (reqCommence.IsValid) (rngSpeed.IsValid)) { this.DialogResult DialogResult.OK; } else { MessageBox.Show(Form not valid.); } } 这种方法确实可以却相当无趣而且每当我们添加了新的验证组件都要编写相应的代码。 可以看到上面的代码出现了很多重复的Validate和IsValid出现这种情况我们往往可以考虑进行枚举风格的重构。Form窗体类没有实现像Controls这样的用于组件的可枚举集合属性但Windows Forms窗体设计器却有一个设计器生成的组件集合名称为components。 private System.ComponentModel.IContainer components null ; components管理那些需要使用非托管资源的组件在其宿主窗体释放时也将它们释放。一个例子是System.Windows.Forms.Timer它使用了非托管的Win32系统的定时器。components集合是由窗体设计器管理的而且我们的自定义组件没有使用非托管资源所以我们不能使用components来进行枚举。我们需要自己去创建这样的集合类。 [Serializable] public class ValidatorCollection : ICollection, IList, IEnumerable, ICloneable { // CollectionGen implementation } ValidationManager 现在我们已经有了一个ValidatorCollection进行枚举了但还要保证它能够包含窗体中所有的验证组件我们需要实现一种机制以在运行时添加和移除验证组件。ValidationManager可以满足这个需要 public class ValidatorManager { private static Hashtable _validators new Hashtable(); public static void Register(BaseValidator validator, Form hostingForm) { // Create form bucket if it doesnt exist if (_validators[hostingForm] null) { _validators[hostingForm] new ValidatorCollection(); } // Add this validator to the list of registered validators ValidatorCollection validators (ValidatorCollection)_validators[hostingForm]; validators.Add(validator); } public static ValidatorCollection GetValidators(Form hostingForm) { return (ValidatorCollection)_validators[hostingForm]; } public static void DeRegister(BaseValidator validator, Form hostingForm) { // Remove this validator from the list of registered validators ValidatorCollection validators (ValidatorCollection)_validators[hostingForm]; validators.Remove(validator); // Remove form bucket if all validators on the form are de-registered if (validators.Count 0) _validators.Remove(hostingForm); }} ValidationManager使用_validators哈希表来管理一个或多个ValidationCollection而每一个ValidationCollection则用于表示特定窗体上的验证组件集合。通过ValidationManager的Register和DeRegister方法将组件注册和反注册至其宿主窗体。 另一方面更新BaseValidator Register和DeRegister方法需要在合适的地方进行调用而把这个调用过程放在BaseValidator中是再合适不过了因为这个逻辑对于所有验证组件都是一样的。因为BaseValidator的创建与销毁与其宿主窗体息息相关对Register和DeRegister的调用还要与宿主窗体的生命期保持同步尤其是通过处理窗体的Load和Closed事件 BaseValidator Update public abstract partial class BaseValidator : Component, ISupportInitialize{ private void Form_Load(object sender, EventArgs e) { // Register with ValidatorManager ValidatorManager.Register(this, (Form)sender); } private void Form_Closed(object sender, EventArgs e) { // DeRegister from ValidatorCollection ValidatorManager.DeRegister(this, (Form)sender); } } 下一步是将这些事件处理函数与Load和Closed事件“挂接”。我们需要的窗体是BaseValidator的ControlToValidator的宿主窗体ControlToValidator的类型为Control我们可以调用它的FindForm方法来获取窗体。很不幸我们不能在BaseValidator的构造函数内调用FindForm此时ControlToValidate可能还没有设置窗体。这是窗体设计器使用InitializeComponeng来保存构造窗体的代码然后将控件赋给窗体的结果。 正如你所见的控件实例在赋给父窗体之前就已创建。这时我们可以转向ISupportInitialize可以帮我们解决上面的问题。 BaseValidator : ISupportInitialize public abstract partial class BaseValidator : Component, ISupportInitialize{ ISupportInitialize Members#region ISupportInitialize Members public void BeginInit() { } public void EndInit() { // Hook up ControlToValidates parent forms Load and Closed events // to register and unregister with the ValidationManager // ONLY if _controlToValidate exists at run-time and has a parent form // ie has been added to a Forms Controls collection // NOTE: if there is no form, we dont add this instance to the ValidatorManager // so it is not available for form-wide validation which makes sense // since there is no form and therefore no form scope. Form host _controlToValidate.FindForm(); if ((_controlToValidate ! null) (!DesignMode) (host ! null)) { host.Load new EventHandler(Form_Load); host.Closed new EventHandler(Form_Closed); } } #endregion } 枚举ValidatiorCollection 创建了ValidatorCollection、ValidatorManager更新了BaseValidator我们也完成了枚举所需要的注册机制。下图描述了其内部实现 要利用更新后的设计我们要做的只是简单地更新OK按钮的Click事件处理函数: // 更好的验证 ValidatorCollection validators ValidatorManager.GetValidators( this ); // 确保检查每个验证组件 foreach (BaseValidator validator in validators) { if (!validator.IsValid) { MessageBox.Show(Form not valid.); return; }} DialogResult DialogResult.OK; 这段代码比前面的实现要优雅得多可维护性也更好——即使向窗体添加验证组件验证代码却无须修改。 声明式的窗体级验证 懒的程序员才是好的程序员我们还想把代码变得更少。还是看看ASP.NET中的机制Page类提供了如下验证相关的成员 ValidatorCollection如此命名也是为了一致性Validate和IsValid的功能是在提交时实现的。不幸的是尽管Form类实现了Validate却不能满足我们的需要。我们还是继续前进实现可重用的组件FormValidator。 ASP.NET Page Class public class Page : TemplateControl, IHttpHandler { public virtual void Validate(); public bool IsValid { get; } public ValidatorCollection Validators { get; } } 我们已经有了 利用FormValidatorOK按钮的Click事件处理函数减少到三行代码 formValidator1.Validate(); if (formValidator1.IsValid) { DialogResult DialogResult.OK; } else { MessageBox.Show(Form not valid.); } 将客户代码减少到三行代码已经很棒了如果完全不写代码岂不更好这需要将这三行代码移到FormValidator中然后在合适的时候执行这应当是窗体的AcceptButton点击的时候。 窗体的AcceptButton和CancelButton属性都可以在属性浏览器中进行设置。这实际上指定了当用户按下回车键时AcceptButton会被点击当用户按下ESC键时CancelButton会被点击。FormValidator需要确定所在窗体的AcceptButton然后处理其Click事件。AcceptButton是在InitializeComponent中设置的所以我们需要实现ISupportInitialize。 事情的真相 这种方法之所以有效是因为我对使用FormValidator的窗体进行了设置包括将其FormBorderStyle属性为FixedDialog还设置了它的AcceptButton和CancelButton同时AcceptButton和CancelButton的DialogResult分别为None和Cancel。其结果是按下Cancel按钮会关闭窗体而我们需要处理AcceptButton的Click事件这正是FormValidator做的事情。 这样我们就不需要写任何代码了。但这是假定你的窗体模型是验证窗体在返回父窗体后处理收集的数据。但很多时候我们需要另一种方式验证窗体在返回父窗体前处理收集的数据如添加、编辑记录后返回。后面方法的问题是自动验证导致AcceptButton会有两个事件处理函数一个由开发人员创建另一个由FormValidator创建。这两种方法都可以采用我们添加了一个属性ValidateOnAccept它来指定是否进行自动验证。 ValidateOnAccept默认为true此外还添加了ErrorMessage属性进一步提高组件的可定制性。 按Tab顺序验证 另一个对用户有用的是验证的顺序。现在FormValidator会选中Tab顺序指定的第一个无效的控件而不是窗体中所看到第一个控件下图是Add New Employee窗体中的TabIndex以Tab顺序验证可使用户按指定的顺序修复各个无效的输入这要比随机的修复更直观些。下图演示了将焦点置于第一个无效的控件如果控件是文本框则会选中文本框的文本。 我们身在何处 这次我们继续上次的脚步利用上篇中的验证组件实现了窗体级的验证。取决于你使用模式对话框的方式FormValidator可支持彻底的声明式验证体验。别高兴得太早我们仅仅是做到了两点控件和窗体验证但Windows Forms可能会包含Tab控件它有几个属性页组成相互之间无甚关联需要各自的验证。Windows桌面的属性对话框是个很好的例子在每个属性页点击应用按钮时都需要不同的验证。在这种情况下容器级的验证将更有意义。 在下篇中我们将着手解决这个问题。同时我们还将扩展验证组件库使它可以显示总结性错误信息Summary。示例代码下载CustomValidatorSample part2