很久很久沒有露面了,呵呵,對於寫文章都有點生疏了。數據庫
在拿到任何一個項目,無論是b/s的仍是c/s,我不會當即開始寫代碼,我通常會爲使這些項目可以快速開發制定一系列的支持組件,雖然可能前期會付出一些代價,但無論是應付當前的任務,仍是爲之後造成一種可持續改進的開發模式,都是有意義的。app
最近幾年都忙於應付b/s方面的項目,因此winform的一些東西已經不是怎麼拿得出手了,雖然之前也寫過一系列的組件,畢竟技術革新太快了,如今已經不太適應了。編輯器
今天介紹的只是一小部份,主要實現信息編輯窗體中各控件與數據屬性之間的綁定、取值與存值、數據驗證。ide
你們知道,這種小型的MIS項目最繁瑣的莫過於編輯頁面的佈局,數據顯示和數據保存,這每每會佔用一半的時間。佈局
1、窗體與實體類的映射性能
須要定義一個Form基類,並提供一個EntityType屬性,這個屬性用於綁定一個實體類,由於一個單一的窗體通常只會與一個實體相關聯。this
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特性了嗎,它提供一種編輯器,能夠從當前的程序集中枚舉出全部的實體類型,以供咱們選擇。spa
// ----------------------------------------------------------------------- // <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特性,在實體類所屬的程序集中進行修飾。code
2、控件與屬性的映射orm
基本的思想仍是使用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; } }
這樣,準備工做就作好了。
3、業務實現
如今,新建一個窗體ProductEdit,繼承自EditForm。選擇EntityType下拉列表中的實體類。
拖一個EntityPropertyExtend控件到窗體上,而後每個文本框控件被擴展了PropertyName屬性。分別爲每個文本框指定對應的屬性。
因爲時間太晚了,原本還有如何讀取數據填充到窗體上,如何將窗體數據保存到數據庫,以及如何進行數據驗證等等,只有明天補上了,望見諒。