I have considered the title of this article for a long time. I always feel that no matter which title is used, it is difficult to use a small number of words to accurately describe its content. In the end, I still feel like using this title.
First, let's talk about the two requirements we recently met in our project team:
1. You need to select multiple items in a table or list in many places. For example, if there is a personnel list and the corresponding data source is assumed to be list <employeeinfo>, to select multiple items, the first thought is to add a Boolean selected attribute in the employeeinfo, which can solve the problem, but this attribute is not used in other places, and if there are other similar problems in the future, the employeeinfo class will become larger and bloated, so the following class will be added:
public class SelectableInfo<T>{ public Boolean Selected { get; set; }
public T Value { get; set; }}
It seems that this can solve the problem, but there is a problem when binding with the UI control, the value similar to "value. Name" as propertyname is not recognized at all.
2. A calendar is required somewhere in the project. For example, if the vertical axis is a person, the horizontal axis is a date, but the horizontal axis is not fixed, and the date range can be freely selected by users, therefore, a data structure similar to the following is designed:
public class ScheduleItem<TMain, TItem>{ public ScheduleItem() { this.Items = new List<TItem>(); } public TMain Main { get; set; } public List<TItem> Items { get; private set; }}
In this case, you can directly use a list <scheduleitem <employeeinfo, employeescheduleiteminfo> as the UI control data source. name "," items [0]. name "," items [1]. name ",..., "items [N]. name "can be used as the binding column to solve the problem, but as with the above problems, these propertynames are not recognized at runtime.
Of course, for the above two problems, if datatable is directly used as the data source, of course, these problems will not exist, but datatable itself can only be regarded as a list of weak type objects, as for the advantages and disadvantages between a weak object and a strong object, it is not covered in the scope of this article. I have no intention to explain or explain anything about it. I just want to solve similar problems.
I also found related solutions on the Internet. It seems that there are some restrictions on the use of attribute in the framework of this Article. For example, attribute does not support generic declarations, variables are not supported, and the flexibility is not high in total. Second, I always think that this method can be understood as "tampering" with metadata to some extent, it is hard to say whether it will cause problems to the operation of other places in the system, such as reflection. And so on.
Later, someone asked a similar question on csdn. One of the top people mentioned that itypedlist can be used for implementation. However, generally, senior people tend to habitually click here and do not provide a complete solution. I had to carefully study the related content of itypedlist, and finally I had a satisfactory solution. The code was not very complicated and I was too lazy to explain it one by one.
The following is my solution:
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); } } } }}
The following is a test example:
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) ;}# code generated by region windows Form Designer private void initializecomponent () {This. datagridview1 = new system. windows. forms. datagridview (); (system. componentmodel. isupportinitialize) (this. datagridview1 )). begininit (); this. suspendlayout (); // maid // 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 maid;} 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 ;}}}
The test results are as follows:
Flattening data binding through itypedlist