winform控件驗證技術
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
}