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数据,电话号码数据, 范围数据,是框架为调用者提供的常规数据验证器.
如果框架提供的验证器类不能满足要求,完全可以定义自己的数据验证器类,在此给出一个验证数据必须为指定长度的示例
publicclassLengthValidator:AbstractValidator
{
/// <summary>
/// 最下长度
/// </summary>
privateint mMinLength;
/// <summary>
/// 最大长度
/// </summary>
privateint 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)
{
thrownewArgumentOutOfRangeException("字段长度不能为负");
}
if (minLen > maxLen)
{
thrownewArgumentException("最大长度不能小于最下长度");
}
mMinLength = minLen;
mMaxLength = maxLen;
}
/// <summary>
/// 验证控件内容是否在指定长度范围内
/// </summary>
/// <param name="controlToValidate">待验证控件</param>
/// <returns>如果在范围内,返回true;否则返回false</returns>
publicoverridebool IsValid(Control controlToValidate)
{
if ((controlToValidate.Text.Length >= mMinLength) &&
(controlToValidate.Text.Length <= mMaxLength))
{
returntrue;
}
returnfalse;
}
}
如果对姓名文本框(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 List<Control> mControlsToValidate =
new List<Control> ();
/// <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;
}
private bool isValid=true;
public bool IsValid
{
get
{
retuValidAll()
rn isValid;
}
}
/// <summary>
/// 验证窗体的所有控件
/// </summary>
public void ValidAll()
{
isValid=true;
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);
isValid=false;
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
}
}
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
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步