Winform快速開發組件的實現(一)

    很久很久沒有露面了,呵呵,對於寫文章都有點生疏了。數據庫

    在拿到任何一個項目,無論是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屬性。分別爲每個文本框指定對應的屬性。

 

    因爲時間太晚了,原本還有如何讀取數據填充到窗體上,如何將窗體數據保存到數據庫,以及如何進行數據驗證等等,只有明天補上了,望見諒。

相關文章
相關標籤/搜索