好久好久没有露面了,呵呵,对于写文章都有点生疏了。
在拿到任何一个项目,不管是b/s的还是c/s,我不会立即开始写代码,我一般会为使这些项目能够快速开发制定一系列的支持组件,虽然可能前期会付出一些代价,但不管是应付当前的任务,还是为以后形成一种可持续改进的开发模式,都是有意义的。
最近几年都忙于应付b/s方面的项目,所以winform的一些东西已经不是怎么拿得出手了,虽然以前也写过一系列的组件,毕竟技术革新太快了,现在已经不太适应了。
今天介绍的只是一小部份,主要实现信息编辑窗体中各控件与数据属性之间的绑定、取值与存值、数据验证。
大家知道,这种小型的MIS项目最繁琐的莫过于编辑页面的布局,数据显示和数据保存,这往往会占用一半的时间。
一、窗体与实体类的映射
需要定义一个Form基类,并提供一个EntityType属性,这个属性用于绑定一个实体类,因为一个单一的窗体一般只会与一个实体相关联。
namespace EasyBook.Client.Forms { /// <summary> /// 定义信息编辑的窗体。 /// </summary> public partial class EditForm : FormBase, IEntitySupport { public EditForm() { InitializeComponent(); } /// <summary> /// 获取或设置实体类型。 /// </summary> [Editor(typeof(EntityTypeEditor), typeof(UITypeEditor))] [Description("获取或设置实体类型。")] public Type EntityType { get; set; } } }
IEntitySupport接口只定义了EntityType属性。
注意到属性上的Editor特性了吗,它提供一种编辑器,可以从当前的程序集中枚举出所有的实体类型,以供我们选择。
// ----------------------------------------------------------------------- // <copyright company="Fireasy" // email="faib920@126.com" // qq="55570729"> // (c) Copyright Fireasy. All rights reserved. // </copyright> // ----------------------------------------------------------------------- using EasyBook.Common; using Fireasy.Common.Extensions; using Fireasy.Data.Entity; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing.Design; using System.Linq; using System.Reflection; using System.Windows.Forms; using System.Windows.Forms.Design; namespace EasyBook.Client.Forms { public class EntityTypeEditor : UITypeEditor { EntityTypeListBox modelUI; public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { if (provider != null) { var edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); var support = (IEntitySupport)context.Instance; if (edSvc == null) { return value; } modelUI = new EntityTypeListBox(support); modelUI.Start(edSvc, value); edSvc.DropDownControl(modelUI); value = modelUI.Value; modelUI.End(); } return value; } public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; } public override bool IsDropDownResizable { get { return true; } } private class EntityTypeListBox : ListView { private IEntitySupport support; private IWindowsFormsEditorService edSvc; public EntityTypeListBox(IEntitySupport support) { View = System.Windows.Forms.View.Details; Columns.Add(new ColumnHeader { Text = "", Width = 160 }); HeaderStyle = ColumnHeaderStyle.None; FullRowSelect = true; HideSelection = false; Height = 400; Click += EntityTypeListBox_Click; this.support = support; LoadTypes(); } void EntityTypeListBox_Click(object sender, EventArgs e) { Value = base.SelectedItems[0].Tag; edSvc.CloseDropDown(); } /// <summary> /// 加载所有可选择的类型。 /// </summary> private void LoadTypes() { //循环所引用的所有程序集 foreach (var assemblyName in support.GetType().Assembly.GetReferencedAssemblies()) { try { var assembly = Assembly.Load(assemblyName.FullName); if (!IsFireasyEntityAssembly(assembly)) { continue; } foreach (var type in GetEntityTypes(assembly)) { var item = new ListViewItem(type.Name); item.Tag = type; Items.Add(item); } } catch { } } } /// <summary> /// 判断程序集是否是 Fireasy Entity 实体程序集。 /// </summary> /// <param name="assembly"></param> /// <returns></returns> private bool IsFireasyEntityAssembly(Assembly assembly) { return assembly.IsDefined<FireasyEntityAssemblyAttribute>(); } /// <summary> /// 获取指定程序集中的实体类集合。 /// </summary> /// <param name="assembly"></param> /// <returns></returns> private IEnumerable<Type> GetEntityTypes(Assembly assembly) { return assembly.GetExportedTypes().Where(s => s.IsPublic && !s.IsAbstract && typeof(EntityObject).IsAssignableFrom(s)); } public void Start(IWindowsFormsEditorService edSvc, object value) { SelectedItems.Clear(); this.edSvc = edSvc; Value = value; if (value == null) { return; } //循环所有项,选中 foreach (ListViewItem item in base.Items) { if (item.Tag != null && item.Tag.Equals(value)) { item.Focused = true; item.Selected = true; item.EnsureVisible(); break; } } } public void End() { edSvc = null; } public object Value { get; set; } } } }
为了提高搜索实体程序集的效率,定义了FireasyEntityAssemblyAttribute特性,在实体类所属的程序集中进行修饰。
二、控件与属性的映射
基本的思想还是使用IExtenderProvider接口,对输入控件进行扩展,使之与实体类的属性相对应。
// ----------------------------------------------------------------------- // <copyright company="Fireasy" // email="faib920@126.com" // qq="55570729"> // (c) Copyright Fireasy. All rights reserved. // </copyright> // ----------------------------------------------------------------------- using System.Collections.Generic; using System.ComponentModel; using System.Drawing.Design; using System.Windows.Forms; namespace EasyBook.Client.Forms { /// <summary> /// 扩展输入控件,使它们绑定到实体类中的某一个属性,以便能够自动化处理数据显示和数据保存。 /// </summary> [ProvideProperty("PropertyName", typeof(Control))] public class EntityPropertyExtend : Component, IExtenderProvider { //控件与属性名称的键值对 private Dictionary<Control, string> properties; public EntityPropertyExtend() { properties = new Dictionary<Control, string>(); } public EntityPropertyExtend(IContainer container) : this() { container.Add(this); } /// <summary> /// 获取控件与属性名称的键值对。 /// </summary> /// <returns></returns> public Dictionary<Control, string> GetProperties() { return properties; } /// <summary> /// 判断哪些控件能够被扩展。 /// </summary> /// <param name="extendee"></param> /// <returns></returns> public bool CanExtend(object extendee) { return ControlEntityMapHelper.IsSupported(extendee.GetType()); } /// <summary> /// 获取控件所对应的属性的名称。此属性能够使用编辑器选择。 /// </summary> /// <param name="control"></param> /// <returns></returns> [Editor(typeof(EntityPropertyEditor), typeof(UITypeEditor))] public string GetPropertyName(Control control) { if (properties.ContainsKey(control)) { return properties[control]; } return string.Empty; } /// <summary> /// 设置控件所对应的属性名称。 /// </summary> /// <param name="control"></param> /// <param name="propertyName"></param> public void SetPropertyName(Control control, string propertyName) { if (properties.ContainsKey(control)) { if (string.IsNullOrEmpty(propertyName)) { properties.Remove(control); } else { properties[control] = propertyName; } } else if (!string.IsNullOrEmpty(propertyName)) { properties.Add(control, propertyName); } } } }
注意,属性名称的指定也提供了一个Editor进行选择,这个编辑器比较简单。
// ----------------------------------------------------------------------- // <copyright company="Fireasy" // email="faib920@126.com" // qq="55570729"> // (c) Copyright Fireasy. All rights reserved. // </copyright> // ----------------------------------------------------------------------- using Fireasy.Data.Entity; using System; using System.ComponentModel; using System.Drawing.Design; using System.Windows.Forms; using System.Windows.Forms.Design; namespace EasyBook.Client.Forms { /// <summary> /// 实体属性选择编辑器。 /// </summary> public class EntityPropertyEditor : UITypeEditor { private EntityPropertyListBox modelUI; public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) { if (provider != null) { var edSvc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); var control = (Control)context.Instance; var form = control.FindForm() as EditForm; if (edSvc == null || form == null || form.EntityType == null) { return value; } if (form.EntityType == null) { return value; } modelUI = new EntityPropertyListBox(form.EntityType); modelUI.Start(edSvc, value); edSvc.DropDownControl(modelUI); value = modelUI.Value; modelUI.End(); } return value; } public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) { return UITypeEditorEditStyle.DropDown; } public override bool IsDropDownResizable { get { return true; } } private class EntityPropertyListBox : ListView { private IWindowsFormsEditorService edSvc; private Type entityType; public EntityPropertyListBox(Type entityType) { View = System.Windows.Forms.View.Details; Columns.Add(new ColumnHeader { Text = "", Width = 160 }); HeaderStyle = ColumnHeaderStyle.None; FullRowSelect = true; HideSelection = false; Height = 400; Click += EntityTypeListBox_Click; this.entityType = entityType; LoadProperties(); } void EntityTypeListBox_Click(object sender, EventArgs e) { Value = base.SelectedItems[0].Text; edSvc.CloseDropDown(); } /// <summary> /// 加载实体类型中的所有属性。 /// </summary> private void LoadProperties() { foreach (var property in PropertyUnity.GetPersistentProperties(entityType)) { var item = new ListViewItem(property.Name); Items.Add(item); } } public void Start(IWindowsFormsEditorService edSvc, object value) { SelectedItems.Clear(); this.edSvc = edSvc; Value = value; if (value == null) { return; } foreach (ListViewItem item in base.Items) { if (item.Text.Equals(value)) { item.Focused = true; item.Selected = true; item.EnsureVisible(); break; } } } public void End() { edSvc = null; } public object Value { get; set; } } } }
Fireasy.Data的PropertyUnity类提供了从实体类型中获取所有属性的方法,这个可以参考Fireasy的介绍。
另外,EntityPropertyExtend的CanExtend方法使用了一个辅助类对控件进行筛选。
ControlEntityMapHelper辅助类有一个工厂方法,用于根据不同的控件类型创建一个名叫IControlEntityMapper的实例对象。
public class ControlEntityMapHelper { public static bool IsSupported(Type controlType) { return typeof(TextBox).IsAssignableFrom(controlType) || typeof(DateTimePicker).IsAssignableFrom(controlType) || typeof(ComboBox).IsAssignableFrom(controlType); } public static IControlEntityMapper GetMapper(Type controlType) { if (typeof(TextBox).IsAssignableFrom(controlType)) { return new TextBoxMapper(); } if (typeof(ComboBox).IsAssignableFrom(controlType)) { return new ComboBoxMapper(); } return null; } }
IControlEntityMapper接口定义了一个控件与实体属性之间如何进行数据交换,最典型的就是如何将实体的属性填充到控件里,如何将控件的值填充到实体中,以及如何清除控件的值。
/// <summary> /// 提供控件与实体属性之间的数据交换方法。 /// </summary> public interface IControlEntityMapper { /// <summary> /// 从控件中获取值。 /// </summary> /// <param name="control"></param> /// <returns></returns> object GetValue(Control control); /// <summary> /// 将指定的值填充到控件中。 /// </summary> /// <param name="control"></param> /// <param name="value"></param> void SetValue(Control control, object value); /// <summary> /// 清除控件的值。 /// </summary> /// <param name="control"></param> void Clear(Control control); } public interface IControlEntityMapper<T> { object GetValue(T control); void SetValue(T control, object value); }
然后为TextBox、ComboBox等控件定义相应的子类,以实现GetValue和SetValue方法。
public abstract class ControlEntityMapperBase<T> : IControlEntityMapper, IControlEntityMapper<T> where T : Control { public object GetValue(Control control) { return GetValue((T)control); } public void SetValue(Control control, object value) { SetValue((T)control, value); } public void Clear(Control control) { Clear((T)control); } public abstract object GetValue(T control); public abstract void SetValue(T control, object value); public abstract void Clear(T control); } public class TextBoxMapper : ControlEntityMapperBase<TextBox> { public override object GetValue(TextBox control) { return control.Text; } public override void SetValue(TextBox control, object value) { control.Text = value.ToString(); } public override void Clear(TextBox control) { control.Text = ""; } } public class ComboBoxMapper : ControlEntityMapperBase<ComboBox> { public override object GetValue(ComboBox control) { return control.SelectedValue; } public override void SetValue(ComboBox control, object value) { control.SelectedValue = value; } public override void Clear(ComboBox control) { control.SelectedIndex = -1; } }
这样,准备工作就做好了。
三、业务实现
现在,新建一个窗体ProductEdit,继承自EditForm。选择EntityType下拉列表中的实体类。
拖一个EntityPropertyExtend控件到窗体上,然后每一个文本框控件被扩展了PropertyName属性。分别为每一个文本框指定对应的属性。
由于时间太晚了,本来还有如何读取数据填充到窗体上,如何将窗体数据保存到数据库,以及如何进行数据验证等等,只有明天补上了,望见谅。