通过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 = this.ParentPropertyDescriptor.GetValue(component);

                if (parentPropertyValue != null)
                {
                    return this.ValuePropertyDescriptor.GetValue(parentPropertyValue);
                }

                return null;
            }

            public override void SetValue(object component, object value)
            {
                var parentPropertyValue = this.ParentPropertyDescriptor.GetValue(component);

                if (parentPropertyValue != null)
                {
                    this.ValuePropertyDescriptor.SetValue(parentPropertyValue, value);
                    this.OnValueChanged(component, EventArgs.Empty);
                }
            }
        }

        private class InnerItemPropertyDescriptor : BasePropertyDescriptor
        {
            public InnerItemPropertyDescriptor(String name, PropertyDescriptor parentPropertyDescriptor, Int32 index)
                : base(name)
            {
                this.ParentPropertyDescriptor = parentPropertyDescriptor;
                this.Index = index;
            }

            public PropertyDescriptor ParentPropertyDescriptor { get; private set; }

            public Int32 Index { get; private set; }

            public override Type ComponentType { get { return this.ParentPropertyDescriptor.PropertyType; } }

            public override Type PropertyType { get { return this.ParentPropertyDescriptor.PropertyType.GetElementType(); } }

            public override object GetValue(object component)
            {
                var parentPropertyValue = this.ParentPropertyDescriptor.GetValue(component) as IList;

                if (parentPropertyValue != null && parentPropertyValue.Count > this.Index)
                {
                    return parentPropertyValue[this.Index];
                }

                return null;
            }

            public override void SetValue(object component, object value)
            {
                var parentPropertyValue = this.ParentPropertyDescriptor.GetValue(component) as IList;

                if (parentPropertyValue != null && parentPropertyValue.Count > this.Index)
                {
                    parentPropertyValue[this.Index] = value;
                    this.OnValueChanged(component, EventArgs.Empty);
                }
            }
        }
    }
}

以下是测试示例:

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 OnLoad(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;
        }
    }
}

以下是测试结果:

时间: 2024-08-25 00:38:03

通过ITypedList实现数据绑定扁平化的相关文章

扁平化设计2.0

时至今日,扁平化已不再是流行一时的设计风潮,而是一种美学风格.扁平化大胆的用色,简洁明快的界面风格一度让大家耳目一新,当它对元素效果抛弃的如此彻底之际,它又将效果捡起来,改装成另一番模样,使得扁平化进化为扁平化2.0. 扁平化设计特质 对于扁平化的定义,依然没有一个固定范式,但概括起来有下面四个特征: 1)没有多余的效果,例如投影.凹凸或渐变等 2)使用简洁风格的元素和图标 3)大胆丰富且明亮的配色风格 4)尽量减少装饰的极简设计 扁平化所追随的细节依然不变,然而这些"规范"开始松懈了

扁平化设计的历史

[摘要]"扁平化设计"是一种设计风格术语,它抛弃任何能使得作品突显3D效果的特性. 如今一提到网站设计,就会不可避免地碰到"扁平化设计"(flat design)这个词.近几年扁平化设计趋势风生水起,也受到很多大公司的青睐. 但扁平化设计来源是哪里?为什么它风靡网站设计?在设计中,我们只有知道一种风格和技术的来源以及它背后的历史,我们在使用这种美学风格时才能更加得心应手.下面让我们追根溯源,了解扁平化设计的前世今生. 究竟什么是扁平化设计 对于没接触过的人来说,&q

1.扁平化

对象 - 对象映射的一个常见用法是获取一个复杂的对象模型,并将其展开成一个更简单的模型. 您可以采取复杂的模型,如: 1 public class Order 2 { 3 private readonly IList<OrderLineItem> _orderLineItems = new List<OrderLineItem>(); 4 5 public Customer Customer { get; set; } 6 7 public OrderLineItem[] GetO

你认为扁平化模式好还是树形结构好?

我认为对于小公司来说,扁平化的模式更好一些.由于小公司的规模比较小,公司内的员工不多,所需要的管理层次不多.扁平化模式的决策层和操作层之间的中间管理层次少,这可以使企业能够快速地将决策权延至企业生产.营销的最前线,从而为提高企业效率.而且由于公司规模小,管理部门之间的信息资源的交流.相互调用不会太多,这样可以是决策者集中更多的精力在企业的市场对策和企业发展的战略问题上. 对于大公司来说,树形结构会更好一些.大公司的规模比较大,所要做的项目比较多,如果一个人负责多个项目会造成项目完成效率不高.在树

怎样做出优秀的扁平化设计风格 PPT 或 Keynote 幻灯片演示文稿?

怎样做出优秀的扁平化设计风格 PPT 或 Keynote 幻灯片演示文稿? http://www.zhihu.com/question/21274267/answer/62857878

android扁平化ProgressBar--progressWheel

ProgressWheel是git是一个开源项目,为开发者提供一个扁平化的ProgressBar,并可对其进行深度定制 1,将ProgressWheel的源码拷贝到项目中 public class ProgressWheel extends View { // Sizes (with defaults) private int layout_height = 0; private int layout_width = 0; private int fullRadius = 100; privat

扁平化设计发展

[PConline 欣赏]现在说起网页.UI设计,都会谈论扁平化设计(Flat Design)这个概念,一些大型公司和组织在网页设计都将风格偏向了它,而iOS 7又推上了一个高峰. 在苹果与微软的青睐与推动下,扁平化设计日益盛行,但是很少有人知道扁平化设计这个概念由何而来?为什么将这个概念用在了网页设计上?对于设计的新概念来说,搞清楚一个风格和一项技术的历史背景很重要,因为在我们觉得是否采用这个新的设计概念的时候,对它来由的了解可以帮助我们做出更明智的决定. 到底什么是扁平化设计? 对于那些从来

扁平化设计

扁平化概念的核心意义是:去除冗余.厚重和繁杂的装饰效果.而具体表现在去掉了多余的透视.纹理.渐变以及能做出3D效果的元素,这样可以让“信息”本身重新作为核心被凸显出来.同时在设计元素上,则强调了抽象.极简和符号化. 例如:Windows.Mac OS.iOS.Android等操作系统的设计已经往“扁平化设计”发展.其设计语言主要有Material Design.Modern UI等. 扁平化的设计,尤其是手机的系统直接体现在:更少的按钮和选项,这样使得UI界面变得更加干净整齐,使用起来格外简洁,

[转]easyui tree 模仿ztree 使用扁平化加载json

原文地址:http://my.oschina.net/acitiviti/blog/349377 参考文章:http://www.jeasyuicn.com/demo/treeloadfilter.html 一.扩展原因 ztree使用了一种扁平化的数据加载方式,就是id(自身id),pid(父id)的方式,参考http://www.ztree.me/v3/demo.php#_102,于是扩展easyui tree 也使用这种亲民的方式: 二.基本方法 1,载入扩展文件 2,在JS中实例化TRE