您的位置:首页 > 其它

一个很好用的WinForm数据验证框架(自己开发)

2011-10-17 16:09 465 查看
本文转自:http://www.cnblogs.com/stevenzhang/articles/588726.html

Windows 窗体验证的主要功能

简单地说,验证是指在进行后续处理或存储之前,确保数据的完整性和准确性的过程。对于数据验证,有一条基本原则:”不要让野蛮人进门” ,即必须在表示层及早对用户输入的数据进行验证,以构成前沿验证防御。利用 UI,开发人员通常可以为最终用户构造一个更具人性化、响应性更高并提供更多信息的验证过程,同时还可以避免出现类似于跨 N 层应用程序进行不必要的双向网络通信这样的问题。

考虑图 1 所示窗体:



1. 要求验证的 Add New Employee 窗体

该窗体需要验证以下内容:



必须输入“姓名”、和“手机号码”





“手机号码”必须为11位数字



实现此验证需要一个相应的基础结构,WinForm 窗体提供了该基础结构,并将其直接内置于每个控件中。为指示控件支持验证,将控件 CausesValidation 的属性设置为 True,即所有控件的默认值。 如果某个控件的 CausesValidation 属性设置为 True,则当焦点切换到另一个 CausesValidation 值也设置为 True 的控件时,将引发前一个控件的 Validating 事件。随后,应处理 Validating 以实现验证逻辑,如确保提供“姓名”。 另外,为了在验证未能通过的时候,给用户以醒目提示,需要将控件和ErrorProvider组件相结合来使用,示例代码如下

void txtName_Validating(object sender, CancelEventArgs e) {

// 要求输入姓名

if(txtName.Text.Trim() == "" ) {

e.Cancel = true;

errProvider.SerError(txtName,”姓名必须要输入!”)

return;

} else {

errProvider.SerError(txtName,””);

}

}

代码1 控件Validating事件处理过程

当在姓名文本框中没有输入内容时,显示的界面如图2所示。



图 2. 结合ErrorProvider的验证提示

窗体范围的验证

Validating 事件和 ErrorProvider 组件的组合提供了一个优秀的解决方案,可以在需要时(即当用户输入数据时)动态验证每个控件。遗憾的是,对 Validating 事件的依赖使该解决方案无法自动扩展为支持窗体范围的验证(当用户单击 “确定” 按钮完成数据输入时,需要此验证)。单击”确定”按钮前,一个或更多个控件可能没有焦点,因此不引发其 Validating 事件。窗体范围的验证通过手动调用捆绑在每个 Validating 事件中的验证逻辑来实现,方法是枚举窗体中的所有控件,为每个控件设置焦点,然后调用该窗体的 Validate 方法,如下所示:

void btnOK_Click(object sender, System.EventArgs e) {
foreach( Control control in Controls ) {
// 在控件上设置焦点
control.Focus();
// 验证导致引发控件的验证事件,
// 如果 CausesValidation 为 True
if( !Validate() ) {
errProvider.SetError(control,"错误提示信息");
DialogResult = DialogResult.None;
return;
} else {
errProvider.SetError(control , "");
}
}
}
代码2 “确定”按钮单击,窗体范围数据验证

智能数据验证框架

从工作效率的角度来看,该解决方案存在一个根本性的问题,即大型应用程序通常包含多个窗体,每个窗体通常比本文的小程序示例包含更多的控件,因此需要更多的验证代码。在 UI 日益复杂的情况下编写大量相似的代码是一项不具伸缩性的技术,因此应尽可能地避免。解决方案之一是编写一个通用的验证逻辑框架.应用该框架时,只要为窗体范围中的控件指定验证规则,则数据验证会自动在幕后进行。这有助于减少大量冗余代码,保持代码的优雅和简洁。

该验证框架总体结构如图3所示



图3 数据验证框架的总体结构
FormValidator是主类,该类用于对窗体进行自动验证,其中:

Void DoValid()方法应该在类似用户单击”确定”按钮的场景被调用,以实现窗体范围的数据验证,当应用该框架是,代码2可以简化为:

void btnOK_Click(object sender, System.EventArgs e) {

aFormValidator.ValidateAll(); //aFormValidator代表一个FormValidator对象

}

代码3 运用ValidateAll实现窗体范围的数据验证

此外,ValidateAll会指定跟踪所有的需要验证的控件,对不需要数据验证的控件不会启动验证过程

Void SetupValidatorForControl(Conbtrol controlToValidate , params IValidator[] validators)方法为每个需要数据验证的控件安装多个验证器. 当我们需要对某个控件应用多个验证规则进行数据验证的时候,再也不需要处理Validating事件,框架使用者只需要窗体的初始化时(通常是FormLoading事件)中调用SetUpValidatorForControl方法即可,示例代码如下:

void FormLoading(….) {

SetupValidatorForControl(txtName,new RequireFieldValidator());

}

IValidator代表数据验证器,方法IsValid(controlToValidate:Control)控件进行实际的验证,如果controlToValidate控件通过该数据验证器,则返回true,否则返回false;String ErrorMessage()返回当控件没有通过该数据验证器验证时,应该显示给用户的提示信息.

AbstractValidator实现了Ivalidator接口,为ErrorMessage提供了默认实现

RequireFieldValidator,RegexFiledValidator,EmailValidator,PhoneValidator和RangeValidator都是具体的数据验证器,分别用于验证非空数据,正则表达式数据,Email数据,电话号码数据, 范围数据,是框架为调用者提供的常规数据验证器.

如果框架提供的验证器类不能满足要求,完全可以定义自己的数据验证器类,在此给出一个验证数据必须为指定长度的示例

public class LengthValidator:AbstractValidator

{

/// <summary>

/// 最下长度

/// </summary>

private int mMinLength;

/// <summary>

/// 最大长度

/// </summary>

private int mMaxLength;

/// <summary>

/// </summary>

/// <param name="minLen">长度下限</param>

/// <param name="maxLen">长度上限</param>

/// <param name="errMsg">验证未通过时错误提示信息</param>

public LengthValidator(int minLen, int maxLen, string errMsg)

:base(errMsg)

{

if (minLen < 0)

{

throw new ArgumentOutOfRangeException("字段长度不能为负");

}

if (minLen > maxLen)

{

throw new ArgumentException("最大长度不能小于最下长度");

}

mMinLength = minLen;

mMaxLength = maxLen;

}

/// <summary>

/// 验证控件内容是否在指定长度范围内

/// </summary>

/// <param name="controlToValidate">待验证控件</param>

/// <returns>如果在范围内,返回true;否则返回false</returns>

public override bool IsValid(Control controlToValidate)

{

if ((controlToValidate.Text.Length >= mMinLength) &&

(controlToValidate.Text.Length <= mMaxLength))

{

return true;

}

return false;

}

}

如果对姓名文本框(txtName)应用以下规则验证:1.姓名不能为空 2.姓名必须是2-4个字符,则代码大致如下

Void FormLoading(…) {

aFormValidate. SetupValidatorForControl(txtName,

new RequireFieldValidate(),

new LengthValidate(2,4,”姓名必须是2-4个字”));

}

该验证框架已经在笔者的多个项目中进行应用,为项目开发节省了大量事件,让开发人员完全从重复的数据验证代码中解放出来;而且,实际的使用过程也怎么该框架具有良好的扩展性,可以自己定义验证器来实现业务规则的验证;同时还具有很好的非侵入性,即框架基本不会对已有代码产生不良影响.

进一步的研究

通过AOP或者.net Attribute实现声明性的数据验证可以进一步减少程序代码量

框架源代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;


namespace StevenZhang.FormValidateFrame
{
/// <summary>
/// 窗体验证器,用于对窗体上的所有具有输入焦点的控件进行验证
/// </summary>
public class FormValidator
{
/// <summary>
/// 要验证的窗体
/// </summary>
private Form mFormToValid;
/// <summary>
/// 验证错误提示控件
/// </summary>
private ErrorProvider mProvider;
/// <summary>
/// 待验证控件数组
/// </summary>
private ArrayList mControlsToValidate =
new ArrayList();
/// <summary>
/// 是否启动验证
/// </summary>
private bool mEnableValidate = true;
/// <summary>
/// 创建一个窗体验证器
/// </summary>
/// <param name="frmToValid">要验证的窗体</param>
/// <param name="errProvider">验证所用的errProvider</param>
public FormValidator(Form frmToValid,
ErrorProvider errProvider)
{
mFormToValid = frmToValid;
mProvider = errProvider;
}
/// <summary>
/// 验证窗体的所有控件
/// </summary>
public void ValidAll()
{
foreach (Control control in mControlsToValidate)
{
control.Focus();
mFormToValid.Validate();
}
}
/// <summary>
/// 如果为true,表示验证启动;如果为false,表示验证没有启动
/// </summary>
public bool EnableValidate
{
get
{
return mEnableValidate;
}
set
{
if (value == mEnableValidate)
return;
else
{
mEnableValidate = value;
foreach (Control control in mControlsToValidate)
{
control.CausesValidation = mEnableValidate;
}
}
}
}
/// <summary>
/// 为窗体的所有需要验证的控件设置验证规则
/// </summary>
/// <param name="controlToValidate">要验证的控件</param>
/// <param name="rules">验证规则</param>
public void SetControlValitors(Control controlToValidate,
params IValidator[] validators)
{
//判断要验证的控件是否已经存在与待验证控件数组中

if(!mControlsToValidate.Contains(controlToValidate))
mControlsToValidate.Add(controlToValidate);


controlToValidate.Validating += delegate(Object sender,
CancelEventArgs e)
{
foreach (IValidator validator in validators)
{
if (!validator.IsValid(controlToValidate))
{
e.Cancel = true;
mProvider.SetError(controlToValidate,
validator.ErrorMessage);
return;
}
else
{
mProvider.SetError(controlToValidate, "");
}
}
};

}
}

}

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace StevenZhang.FormValidateFrame
{
public interface IValidator
{
/// <summary>
/// 通过该方法对控件进行验证,如果通过验证,返回true,否则返回false
/// </summary>
/// <param name="controlToValid">待验证的控件</param>
/// <returns>如果控件通过该验证,返回true;否则返回false</returns>
bool IsValid(Control controlToValid);
/// <summary>
/// 验证没有通过的时候,需要显示的错误提示信息
/// </summary>
string ErrorMessage
{
get;
set;
}
}
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace StevenZhang.FormValidateFrame
{
public class LengthValidator:AbstractValidator
{
/// <summary>
/// 最下长度
/// </summary>
private int mMinLength;
/// <summary>
/// 最大长度
/// </summary>
private int mMaxLength;


/// <summary>
/// </summary>
/// <param name="minLen">长度下限</param>
/// <param name="maxLen">长度上限</param>
/// <param name="errMsg">验证未通过时错误提示信息</param>
public LengthValidator(int minLen, int maxLen, string errMsg)
:base(errMsg)
{
if (minLen < 0)
{
throw new ArgumentOutOfRangeException("字段长度不能为负");
}
if (minLen > maxLen)
{
throw new ArgumentException("最大长度不能小于最下长度");
}
mMinLength = minLen;
mMaxLength = maxLen;
}


/// <summary>
/// 验证控件内容是否在指定长度范围内
/// </summary>
/// <param name="controlToValidate">待验证控件</param>
/// <returns>如果在范围内,返回true;否则返回false</returns>
public override bool IsValid(Control controlToValidate)
{
if ((controlToValidate.Text.Length >= mMinLength) &&
(controlToValidate.Text.Length <= mMaxLength))
{
return true;
}
return false;
}
}
}

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;


namespace StevenZhang.FormValidateFrame
{
/// <summary>
/// 正则表达式验证器
/// </summary>
public class RegexValidator:AbstractValidator
{
/// <summary>
/// 正则表达式对象
/// </summary>
private Regex mRegex;


/// <summary>
/// </summary>
/// <param name="pattern">正则表达式</param>
public RegexValidator(string pattern ,string errMsg):base(errMsg)
{
mRegex = new Regex(pattern);
}


#region IValidator部分
public override bool IsValid(Control controlToValidate)
{
return mRegex.IsMatch(controlToValidate.Text);
}
#endregion
}
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;


namespace StevenZhang.FormValidateFrame
{
/// <summary>
/// 非空字段验证器
/// </summary>
public class RequiredFieldValidator:AbstractValidator
{
private const string DefaultErrMsg = "该内容不能为空";
/// <summary>
/// </summary>
/// <param name="errMsg">验证不通过时的错误信息</param>
public RequiredFieldValidator(string errMsg)
: base(errMsg)
{
}
public RequiredFieldValidator():base(DefaultErrMsg)
{
}


#region IValidator部分
/// <summary>
///验证内容必须不为空
/// </summary>
/// <param name="content">要验证的内容</param>
/// <returns></returns>
public override bool IsValid(Control controlToValidate)
{
string content = controlToValidate.Text;


if ((content == null) ||
(content.Trim().Length == 0))
{
return false;
}
return true;
}
#endregion


}
}



根据对博主文章的理解,我写了抽象的AbstractValidator类,但是有一个疑问,如果把启动验证的代码写在Form的确认按钮的点击事件里(例如:业务中需要给出一个创建文件夹的窗体,要求用于输入文件夹名的TextBox内容不能为空):
void btnOK_Click(object sender, System.EventArgs e) {

aFormValidator.ValidateAll(); //aFormValidator代表一个FormValidator对象

}
就算给出了红色的错误提示,窗口关闭后的代码也是继续执行下去了,而没有阻止窗口的关闭,后面的代码就会因为TextBox空值而出现异常!
abstract class AbstractValidator:IValidator
{
protected string _errorMessage;

public AbstractValidator(string errorMsg)
{
_errorMessage = errorMsg;
}
#region IValidator 成员
/// <summary>
/// 错误信息
/// </summary>
public string ErrorMessage
{
get
{
return _errorMessage;
}
set
{
_errorMessage = value;
}
}

public abstract bool IsValid(Control controlToValidate);

#endregion
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐