2007年3月29日星期四

validate a control in winform

在asp.net中如果要检查页面上的一个输入框是否为空,即需要用户输入一些东西,我们可以使用RequiredFieldValidator,做一些简单的设定就可以保证我们在后台读取这个输入框的时候它的值不会为空,但是不明白为什么winform里没有这样东西。winform中虽然提供一个errorprovider但是仍然需要我们自己编写validating的代码,反反复复的写了很多检验代码,既烦琐又不容易维护。借助Attribute的特性,其实我们可以将类似的检验做的更加简单,比如:
[RequiredFieldAttribute]
public System.Windows.Forms.TextBox textBox1;
只需要在控件上加上[RequiredFieldAttribute],我们就可以让检验变得自动化。

首先我们需要一个验证用的属性Attribute,关于该属性的介绍可以参看MSDN。
public class RequiredFieldAttribute:Attribute,IDisposable
{
public RequiredFieldAttribute()
{
Validating_EventHandler = new CancelEventHandler(Validate);
}

private void Validate(object sender, CancelEventArgs e)
{
Control ctr = sender as Control;
e.Cancel = (ctr.Text == "");
}

public CancelEventHandler ValidatingDelegate
{
set{
Validating_EventHandler = value;
}
get
{
return Validating_EventHandler;
}
}
CancelEventHandler Validating_EventHandler;

#region IDisposable 成员

public void Dispose()
{
Validating_EventHandler = null;
}

#endregion
}
在定义好属性之后,就可以像前面说的那样将属性附加到我们要验证的控件上去:
[RequiredFieldAttribute]
public System.Windows.Forms.TextBox textBox1;
不过这里要求该控件的定义是“public”的,后面会讲到为什么。附加的属性不会自己起作用,除非是.net framework中默认那些,对于我们自己定义的属性CLR并不了解,所以要使自己定义的属性起作用,还需要自己做一些工作。下面我们定义一个ValidatorEngine的类:
public class ValidatorEngine
{
public ValidatorEngine(ContainerControl container)
{
this.container = (ContainerControl)container;
this.typeOfContainer = container.GetType();
}

public void Active()
{
Active(this.container);
}
private void Active(ContainerControl container)
{
//get all public fields
FieldInfo[] fields = typeOfContainer.GetFields();//1
foreach (FieldInfo fi in fields)
{
//get attributes assigned to the field
object[] attributes = fi.GetCustomAttributes(false);//2
foreach (Attribute attr in attributes)
{
RequiredFieldAttribute validator = attr as RequiredFieldAttribute;//3
if (validator != null)
{
Control[] childControls = container.Controls.Find(fi.Name, false);//4
//the number of child controls should always be "1"
if(childControls.Length>0)
//register validator defined in RequiredFieldAttribute for our controls
childControls[0].Validating += validator.ValidatingDelegate;//5
}
}
}
}
Type typeOfContainer;
ContainerControl container;
}
engine做了些什么工作?
  1. 取得容器控件(比如我们的Form)的所有公开(public)的字段。设为public的可以取到,设计器默认的private好像取不到,试验了加上BindingFlags的枚举好像也不行,如果有知道的请留言告知:)
  2. 对于一个字段,读取附加到它上面的自定义属性。参数是表示是否要搜说该字段所继承的属性,这里不需要所以设为false。
  3. 判断找到的属性是不是我们所设定的,即RequiredFieldAttribute,如果是那说明这个字段表示的控件是不许为空的。
  4. 如果控件前面有我们附加的属性,那么通过字段名找到这个控件。本例子中的控件就在容器控件的第一级子控件集合中,所以Find的第2个参数为false,不在更深的层次中搜索。
  5. 给控件注册Validating事件。例子中使用了我们在RequiredFieldAttribute类中设定的处理方法,这样可以让各种类似的控件使用相同的验证代码。如果要自定验证方法,可以通过:
    public CancelEventHandler ValidatingDelegate 属性传入,但是这样我们附加属性的代码就与前面的略微有一些不同了,需要使用到属性的命名参数,请参考MSDN:
    [RequiredFieldAttribute(ValidatingDelegate =yourValidatingHandler)]
    public System.Windows.Forms.TextBox textBox1;
最后来看一下如何使用Engine。Engine不会自己执行自己,而且他需要一个容器控件作为参数传入,于是我们可以考虑在任何的容器控件初始化之后来启动Engine,这里就以Form为例:
private void FormBase_Load(object sender, EventArgs e)
{
//give engine the reference of a ContainerControl
engine = new ValidatorEngine(this);
//start then engine ,it will process our custom attributes
engine.Active();
}
//define a engine
ValidatorEngine engine;

这个例子很简单,只有一个自定义属性,但是它已经可以让我们看到Attribute的威力了。

没有评论: