在编写WebForm或WinForm程序时,我们经常需要编写很多获取,设置UI控件值代码.这确实是一件重复,麻烦而又容易出错的工作.所以我们应该将这个工作交给计算机去做解放我们的劳动力.一般来说UI上的控件都是和我们的EntityObject相对应的,所以利用反射将EntityObject中属性值赋给控件或通过控件填充EntityObject是非常方便的.但是要想让计算机自动干活就要顶一个规则,就是控件ID = 前缀 + 属性名.比如我们有一个User类其定义如下:
class User
{
string _name;
string _pwd;
public string Name
{
get { return this._name; }
set { this._name = value;}
}
public string Pwd
{
get { return this._pwd; }
set { this._pwd = value; }
}
};
而我们要写一个登录界面,那我们就会有txtName和txtPwd两个文本框来接受输入(我习惯用三个字母缩写来做前缀),这样反射才能派上用场.RoR有条编程理念叫”约定由于配置”,我很同意,这可以使我们减少很多无意义的工作.
//控件类型枚举,一些常用的WinForm和WebForm控件
public enum CtlType
{
TextBox,
DropDownList,
ComboBox,
Hidden,
CheckBox,
}
//值对象和控件根据名字相互映射
public class ControlHelper
{
//id名称前缀表
public static readonly string[] PreFix_Table = new string[5];
static ControlHelper()
{
PreFix_Table[(int)CtlType.TextBox] = "txt";
PreFix_Table[(int)CtlType.DropDownList] = "drp";
PreFix_Table[(int)CtlType.ComboBox] = "cmb";
PreFix_Table[(int)CtlType.Hidden] = "hdn";
PreFix_Table[(int)CtlType.CheckBox] = "chk";
}
//解析控件名
private static string ParseCtlID(string id, CtlType ctlType)
{
return id.Replace(PreFix_Table[(int)ctlType], "");
}
}
在上面的代码中我用ParseCtlID方法来得到控件相对应的属性名.
在继续完成这类之前我需要写一个接口IControl来统一访问WinForm Control和WebForm Control类的属性和方法.
//一下五个using语句用来减少输入
using WebFormH = System.Web.UI.HtmlControls;
using WebFormW = System.Web.UI.WebControls;
using WinForms = System.Windows.Forms;
using WebCtl = System.Web.UI.Control;
using WinCtl = System.Windows.Forms.Control;
//统一访问接口
interface IControl
{
object RealCtl //得到真实的控件对象
{
get;
}
string GetCtlID(); //得到控件ID
IEnumerable<IControl> GetSubCtls(); //得到子控件集合
}
struct WebControl : IControl
{
WebCtl _ctl;
public object RealCtl
{
get { return this._ctl; }
}
public WebControl(WebCtl ctl)
{
this._ctl = ctl;
}
public string GetCtlID()
{
return this._ctl.ID;
}
public IEnumerable<IControl> GetSubCtls()
{
foreach (WebCtl c in this._ctl.Controls)
yield return new WebControl(c);
}
};
struct WinControl : IControl
{
WinCtl _ctl;
public object RealCtl
{
get { return this._ctl; }
}
public WinControl(WinCtl ctl)
{
this._ctl = ctl;
}
public string GetCtlID()
{
return this._ctl.Name;
}
public IEnumerable<IControl> GetSubCtls()
{
foreach (WinCtl c in this._ctl.Controls)
yield return new WinControl(c);
}
};
好了有了上面的代码我们就能方便来完成剩下的工作了,继续写ControlHelper类.遍历和过滤我们需要的子控件,这里使用了Tuple类中的HasType方法进行过滤,GetNullInstance方法来自这里:
private static IEnumerable<IControl> Iterator<_Tuple>(IControl ctl) where _Tuple : Tuple
{
Tuple tuple = Tuple.GetNullInstance<_Tuple>();
foreach (IControl c in ctl.GetSubCtls())
{
if (!tuple.HasType(c.RealCtl))
{
foreach (IControl cc in Iterator<_Tuple>(c))
yield return cc;
}
else
yield return c;
}
}
最后一步使用反射,这里只贴了WebForm的函数WinForm的类似
//将EntityObject的值赋给控件
public static void SetCtlsVal<T>(WebCtl baseCtl, T obj)
{
//Tuple保存了我们需要的控件类型
foreach (IControl c in Iterator<Tuple<WebFormW.TextBox,
WebFormW.DropDownList,
WebFormH.HtmlInputHidden
>>(new WebControl(baseCtl)))
{
if (c.RealCtl is WebFormW.TextBox)
((WebFormW.TextBox)c.RealCtl).Text =
QR_Helper<T>.GetPropertyValue_ByName(ParseCtlID(c.GetCtlID(),
CtlType.TextBox),obj).ToString();
if (c.RealCtl is WebFormW.DropDownList)
((WebFormW.DropDownList)c.RealCtl).SelectedValue =
QR_Helper<T>.GetPropertyValue_ByName(ParseCtlID(c.GetCtlID(),
CtlType.TextBox), obj).ToString();
if (c.RealCtl is WebFormH.HtmlInputHidden)
((WebFormH.HtmlInputHidden)c.RealCtl).Value =
QR_Helper<T>.GetPropertyValue_ByName(ParseCtlID(c.GetCtlID(),
CtlType.TextBox), obj).ToString();
}
}
Ps: QR_Helper<T>.GetPropertyValue_ByName(ParseCtlID是我写的用来方便放射对象的类,以后会做介绍,当然也可以直接用GetValue().
有了ControlHelper类不但可以很简便的获取,设置控件的值还能实现UI和后台逻辑的解耦.如我们可以借鉴MVC的思想,将一些后台控制逻辑放到Controll类中就可以不需要知道具体是那些UI在使用这个Controll,还是那个User登录的例子
class Controller
{
Form _this;
User _user = new User();
public Controller(Form thisObj)
{
this._this = thisObj;
}
//验证输入是否正确
public bool IsValidated()
{
ControlHelper.GetCtlsVal<User>(_this, _user);
if (this._user.Name != null && this._user.Name != ""
&& this._user.Pwd != null && this._user.Pwd != "")
return true;
else
return false;
}
最后,扩展这个类我们还能实现统一设置UI上控件的Style,或把TextBox都清空这样的操作.