首页 > 代码库 > 通过ITypedList实现数据绑定扁平化

通过ITypedList实现数据绑定扁平化

关于这篇文章的标题我斟酌了很久,总觉得不管用哪个标题,都很难用不多的文字准确的描述其内容,最后还是觉得用这样一个标题吧。

先谈谈最近我们项目团队中遇到的两个需求:

1、有很多地方都需要在表格或者列表中实现多选,比如有一个人员列表,对应的数据源假设是List<EmployeeInfo>,要想实现多选,第一感觉就是在EmployeeInfo中增加Boolean Selected这样一个属性,这样固然可以解决问题,但在其他地方根本就用不到这个属性,而且如果后面还是其他类似的问题的话,EmployeeInfo这个类就会变得越来越大,越来越臃肿,于是乎就增加了下面的类:

public class SelectableInfo<T>{  public Boolean Selected { get; set; }
  public T Value { get; set; }}

  

似乎这样可以解决问题,但在与UI控件绑定的时候出现了问题,将类似"Value.Name"这样的值作为PropertyName时根本不被识别。

2、在项目中某个地方需要有一个日程表,比如纵轴是人员,横轴是日期,但横轴是不固定的,日期范围是可以由用户自由选择的,于是乎设计了类似下面的数据结构:

public class ScheduleItem<TMain, TItem>{  public ScheduleItem()  {    this.Items = new List<TItem>();  }  public TMain Main { get; set; }  public List<TItem> Items { get; private set; }}

 

似乎这样的话就可以直接用一个List<ScheduleItem<EmployeeInfo, EmployeeScheduleItemInfo>>作为UI控件的数据源,动态的将"Main.Name", "Items[0].Name", "Items[1].Name", ..., "Items[n].Name"作为绑定列就能解决问题了,但跟上面的问题一样,这些PropertyName在运行时根本不被识别。

当然,对上面的两个问题,如果直接将DataTable作为数据源,当然这些问题也就都不存在了,但DataTable本身只能算是是一个弱类型对象的List,至于弱类型对象与强类型对象之间的优缺点,就不在此篇文章的讨论范围内了,也无意就此说明或者解释些什么,就事论事,只是想解决类似的问题。

我也在网上找了一下相关的解决方案,似乎只有这一篇文章http://blogs.msdn.com/b/msdnts/archive/2007/01/19/how-to-bind-a-datagridview-column-to-a-second-level-property-of-a-data-source.aspx谈到了类似的问题,并且给出了解决方案,我也仔细拜读了这篇文章,并按他的方法尝试了一番,确实能解决部分问题,但总觉得其实现方式不慎完美,其一,他是通过TypeDescriptionProviderAttribute来实现的,而在.NET FrameWork中,对Attribute的使用是有一些限制的,比如Attribute不支持泛型声明,也不支持变量,灵活性总共不高,其二,总觉得这种方式其实某种程度上可以理解为“篡改”了元数据,很难说会不会对系统其他地方的运行带来问题,比如在反射该类型时等等诸如此类的地方还是有可能会出问题的。

后来在csdn上看到有人也问到类似的问题,有一位高人提到可以用ITypedList实现,但大概一般高人都习惯性的点到为止,并没有给出完整的解决方案。只好仔细的研究了一下ITypedList的相关内容,最终总算是折腾出了一个自己还比较满意的解决方案,代码不是很复杂,也就懒得去一一说明了。

下面是我的解决方案:

using System;using System.Collections;using System.Collections.Generic;using System.ComponentModel;using System.Linq;using System.Text.RegularExpressions;namespace DynamicBinding{    public class PropertyBindingList<T> : BindingList<T>, ITypedList    {        private List<String> bindProperties;        private Dictionary<String, PropertyDescriptor> propertyDescriptorDictionary;        public PropertyBindingList()        {            this.bindProperties = new List<String>();            this.innerPropertyDescriptorCollection = TypeDescriptor.GetProperties(typeof(T));            this.propertyDescriptorDictionary = new Dictionary<String, PropertyDescriptor>();        }        public void AddBindProperty(String propertyName)        {            if (this.bindProperties.Contains(propertyName))            {                throw new ArgumentException(String.Format(@"The property ""{0}"" is already exists.", propertyName), "propertyName");            }            this.bindProperties.Add(propertyName);        }        public void RemoveBindProperty(String propertyName)        {            this.bindProperties.Remove(propertyName);        }        private PropertyDescriptorCollection innerPropertyDescriptorCollection;        public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)        {            var array = new PropertyDescriptor[this.innerPropertyDescriptorCollection.Count + this.bindProperties.Count];            this.innerPropertyDescriptorCollection.CopyTo(array, 0);            for (var i = 0; i < this.bindProperties.Count; i++)            {                array[this.innerPropertyDescriptorCollection.Count + i] = this.GetPropertyDescriptor(this.bindProperties[i]);            }            return new PropertyDescriptorCollection(array);        }        private PropertyDescriptor GetPropertyDescriptor(String propertyPath)        {            if (String.IsNullOrEmpty(propertyPath))            {                throw new ArgumentNullException("propertyPath");            }            var array = propertyPath.Split(‘.‘);            var first = array.First();            var propertyDescriptor = this.GetPropertyDescriptor(this.innerPropertyDescriptorCollection, first);            for (var i = 1; i < array.Length; i++)            {                propertyDescriptor = this.CreatePropertyDescriptor(propertyDescriptor, array[i]);            }            return propertyDescriptor;        }        private PropertyDescriptor GetPropertyDescriptor(PropertyDescriptorCollection propertyDescriptorCollection, String name)        {            var regex = new Regex(@"(?<name>\w+)\[(?<index>\d+)\]");            var match = regex.Match(name);            if (match.Success)            {                var propertyName = match.Groups["name"].Value;                var indexText = match.Groups["index"].Value;                var index = Int32.Parse(indexText);                var arrayPropertyDescriptor = propertyDescriptorCollection[propertyName];                if (arrayPropertyDescriptor == null)                {                    throw new ArgumentOutOfRangeException(String.Format(@"Can not find property descriptor ""{0}"" in propertyDescriptorCollection.", propertyName));                }                var itemPropertyDescriptorName = String.Format("{0}[{1}]", arrayPropertyDescriptor.Name, index);                PropertyDescriptor itemPropertyDescriptor;                if (!this.propertyDescriptorDictionary.TryGetValue(itemPropertyDescriptorName, out itemPropertyDescriptor))                {                    itemPropertyDescriptor = new InnerItemPropertyDescriptor(                        itemPropertyDescriptorName,                        arrayPropertyDescriptor,                        index);                    this.propertyDescriptorDictionary.Add(itemPropertyDescriptorName, itemPropertyDescriptor);                }                return itemPropertyDescriptor;            }            else            {                var result = propertyDescriptorCollection[name];                if (result == null)                {                    throw new ArgumentOutOfRangeException(String.Format(@"Can not find property descriptor ""{0}"" in propertyDescriptorCollection.", name));                }                return result;            }        }        private PropertyDescriptor CreatePropertyDescriptor(PropertyDescriptor parentPropertyDescriptor, String name)        {            var regex = new Regex(@"(?<name>\w+)\[(?<index>\d+)\]");            var match = regex.Match(name);            if (match.Success)            {                var propertyName = match.Groups["name"].Value;                var indexText = match.Groups["index"].Value;                var index = Int32.Parse(indexText);                var propertyDescriptorName = parentPropertyDescriptor.Name + "." + propertyName;                PropertyDescriptor arrayPropertyDescriptor;                if (!this.propertyDescriptorDictionary.TryGetValue(propertyDescriptorName, out arrayPropertyDescriptor))                {                    var properties = TypeDescriptor.GetProperties(parentPropertyDescriptor.PropertyType);                    var valuePropertyDescriptor = properties[propertyName];                    if (valuePropertyDescriptor == null)                    {                        throw new ArgumentOutOfRangeException(String.Format(@"Can not find property descriptor ""{0}"" in type ""{1}"".", propertyName, parentPropertyDescriptor.PropertyType));                    }                    arrayPropertyDescriptor = new InnerPropertyDescriptor(propertyDescriptorName,                        parentPropertyDescriptor,                        valuePropertyDescriptor);                    this.propertyDescriptorDictionary.Add(propertyDescriptorName, arrayPropertyDescriptor);                }                var itemPropertyDescriptorName = String.Format("{0}[{1}]", arrayPropertyDescriptor.Name, index);                PropertyDescriptor itemPropertyDescriptor;                if (!this.propertyDescriptorDictionary.TryGetValue(itemPropertyDescriptorName, out itemPropertyDescriptor))                {                    itemPropertyDescriptor = new InnerItemPropertyDescriptor(                        itemPropertyDescriptorName,                        arrayPropertyDescriptor,                        index);                    this.propertyDescriptorDictionary.Add(itemPropertyDescriptorName, itemPropertyDescriptor);                }                return itemPropertyDescriptor;            }            else            {                var propertyDescriptorName = parentPropertyDescriptor.Name + "." + name;                PropertyDescriptor propertyDescriptor;                if (!this.propertyDescriptorDictionary.TryGetValue(propertyDescriptorName, out propertyDescriptor))                {                    var properties = TypeDescriptor.GetProperties(parentPropertyDescriptor.PropertyType);                    var valuePropertyDescriptor = properties[name];                    if (valuePropertyDescriptor == null)                    {                        throw new ArgumentOutOfRangeException(String.Format(@"Can not find property descriptor ""{0}"" in type ""{1}"".", name, parentPropertyDescriptor.PropertyType));                    }                    propertyDescriptor = new InnerPropertyDescriptor(                        propertyDescriptorName,                        parentPropertyDescriptor,                        valuePropertyDescriptor);                    this.propertyDescriptorDictionary.Add(propertyDescriptorName, propertyDescriptor);                }                return propertyDescriptor;            }        }        public String GetListName(PropertyDescriptor[] listAccessors)        {            return typeof(T).Name;        }        private abstract class BasePropertyDescriptor : PropertyDescriptor        {            public BasePropertyDescriptor(String name)                : base(name, null)            {            }            public override bool IsReadOnly { get { return false; } }            public override void ResetValue(object component) { }            public override bool CanResetValue(object component) { return false; }            public override bool ShouldSerializeValue(object component) { return true; }        }        private class InnerPropertyDescriptor : BasePropertyDescriptor        {            public InnerPropertyDescriptor(String name, PropertyDescriptor parentPropertyDescriptor, PropertyDescriptor valuePropertyDescriptor)                : base(name)            {                this.ParentPropertyDescriptor = parentPropertyDescriptor;                this.ValuePropertyDescriptor = valuePropertyDescriptor;            }            public PropertyDescriptor ParentPropertyDescriptor { get; private set; }            public PropertyDescriptor ValuePropertyDescriptor { get; private set; }            public override Type ComponentType { get { return this.ParentPropertyDescriptor.PropertyType; } }            public override Type PropertyType { get { return this.ValuePropertyDescriptor.PropertyType; } }            public override object GetValue(object component)            {                var parentPropertyValue = http://www.mamicode.com/this.ParentPropertyDescriptor.GetValue(component);>

以下是测试示例:

using System;using System.Collections.Generic;using System.Linq;using System.Windows.Forms;namespace DynamicBinding{    static class Program    {        [STAThread]        static void Main()        {            Application.EnableVisualStyles();            Application.SetCompatibleTextRenderingDefault(false);            Application.Run(new TestForm());        }    }    partial class TestForm    {        private System.ComponentModel.IContainer components = null;        protected override void Dispose(bool disposing)        {            if (disposing && (components != null))            {                components.Dispose();            }            base.Dispose(disposing);        }        #region Windows 窗体设计器生成的代码        private void InitializeComponent()        {            this.dataGridView1 = new System.Windows.Forms.DataGridView();            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();            this.SuspendLayout();            //             // dataGridView1            //             this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;            this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;            this.dataGridView1.Location = new System.Drawing.Point(0, 0);            this.dataGridView1.Name = "dataGridView1";            this.dataGridView1.RowTemplate.Height = 23;            this.dataGridView1.Size = new System.Drawing.Size(784, 562);            this.dataGridView1.TabIndex = 0;            //             // Form1            //             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;            this.ClientSize = new System.Drawing.Size(784, 562);            this.Controls.Add(this.dataGridView1);            this.Name = "TestForm";            this.Text = "TestForm";            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();            this.ResumeLayout(false);        }        #endregion        private System.Windows.Forms.DataGridView dataGridView1;    }    public partial class TestForm : Form    {        public TestForm()        {            InitializeComponent();        }        protected override void onl oad(EventArgs e)        {            this.dataGridView1.AutoGenerateColumns = true;            var list = new PropertyBindingList<TestA>();            list.AddBindProperty("List[0].BID");            list.AddBindProperty("List[0].List[0].CID");            list.AddBindProperty("List[0].List[0].CName");            list.AddBindProperty("List[0].List[1].CID");            list.AddBindProperty("List[0].List[1].CName");            list.AddBindProperty("List[1].BName");            list.AddBindProperty("List[1].List[0].CID");            list.AddBindProperty("List[1].List[0].CName");            list.AddBindProperty("List[1].List[1].CID");            list.AddBindProperty("List[1].List[1].CName");            list.Add(new TestA            {                AID = 1,                AName = "A001",                List = new TestB[]                {                     new TestB                    {                        BID = 11,                        BName = "B11",                        List = new TestC[]                        {                            new TestC                            {                                CID = 111,                                CName = "C111"                            },                            new TestC                            {                                CID = 112,                                CName = "C112"                            }                        }                    },                                        new TestB                    {                        BID = 12,                        BName = "B12",                        List = new TestC[]                        {                            new TestC                            {                                CID = 113,                                CName = "C113"                            },                            new TestC                            {                                CID = 114,                                CName = "C114"                            }                        }                    },                                                           new TestB                    {                        BID = 13,                        BName = "B13"                    }                }            });            list.Add(new TestA            {                AID = 1,                AName = "A001"            });            this.dataGridView1.DataSource = list;            base.OnLoad(e);        }    }    public class TestA    {        public Int32 AID        {            get;            set;        }        public String AName        {            get;            set;        }        public TestB[] List        {            get;            set;        }    }    public class TestB    {        public Int32 BID        {            get;            set;        }        public String BName        {            get;            set;        }        public TestC[] List        {            get;            set;        }    }    public class TestC    {        public Int32 CID        {            get;            set;        }        public String CName        {            get;            set;        }    }}

以下是测试结果:

通过ITypedList实现数据绑定扁平化