DataGridView没有显示实现ICustomTypeDescriptor的对象的正确性

我在DataGridView中显示对象列表。 一切都很好。 根据对象的属性将列自动添加到DataGridView中

现在我改变了我在网格中显示的类来实现ICustomTypeDescriptor 。 但是现在,当我将其DataSource设置为我的自定义对象列表时,网格现在不再显示任何列或行。

我猜这与ICustomTypeDescriptor的事实有关,每个网格的每一行中显示的每个实例都可以返回一组不同的属性。

我正在实现ICustomTypeDescriptor,以便我可以允许用户在运行时动态地向对象添加自定义属性。 这些自定义属性应该是可见的,并可通过DataGridView进行编辑。

为什么DataGridView看不到我的ICustomTypeDescriptor方法? 有没有其他方法可以动态地将属性添加到将在DataGridView中显示的对象?

DataGridView查看元数据的列表版本; 这个规则是……复杂的:

  1. 如果数据源实现IListSource ,则评估GetList()并将其用作数据源(从2开始)
  2. 如果数据源实现ITypedList ,则GetProperties()用于获取元数据(退出)
  3. 如果可以找到类型化(非object )索引器(即public T this[int index] ),则通过TypeDescriptor.GetProperties(type)T用作源:
    1. 如果分配了TypeDescriptionProvider ,则将其用于针对类型的元数据(退出)
    2. 否则reflection用于元数据(退出)
  4. 如果列表非空,则第一个对象通过TypeDescriptor.GetProperties(list[0])用于元数据:
    1. 如果实现了ICustomTypeDescriptor ,则使用它(退出)[*]
    2. 如果分配了TypeDescriptionProvider ,则将其用于针对类型的元数据(退出)[*]
    3. 否则使用reflection(退出)
  5. 其他元数据不可用(退出)

([*] =我不记得这两个走哪条路……)

如果您使用List (或类似),那么您点击“最简单”(IMO)案例 – #3。 如果要提供自定义元数据,那么; 您最好的选择是编写TypeDescriptionProvider并将其与类型相关联。 我可以写一个例子但它需要一段时间(可能在火车上)……

编辑: 这是一个使用ITypedList的例子; 我会尝试调整它以使用TypeDescriptionProvider代替……

第二次编辑:使用TypeDescriptionProvider的完整(但最小)示例如下; 长码警告……

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; // example static class Program { [STAThread] static void Main() { PropertyBag.AddProperty("UserName", typeof(string), new DisplayNameAttribute("User Name")); PropertyBag.AddProperty("DateOfBirth", typeof(DateTime), new DisplayNameAttribute("Date of Birth")); BindingList list = new BindingList() { new PropertyBag().With("UserName", "Fred").With("DateOfBirth", new DateTime(1998,12,1)), new PropertyBag().With("UserName", "William").With("DateOfBirth", new DateTime(1997,4,23)) }; Application.Run(new Form { Controls = { new DataGridView { // prove it works for complex bindings Dock = DockStyle.Fill, DataSource = list, ReadOnly = false, AllowUserToAddRows = true } }, DataBindings = { {"Text", list, "UserName"} // prove it works for simple bindings } }); } } // PropertyBag file 1; the core bag partial class PropertyBag : INotifyPropertyChanged { private static PropertyDescriptorCollection props; public event PropertyChangedEventHandler PropertyChanged; void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } static PropertyBag() { props = new PropertyDescriptorCollection(new PropertyDescriptor[0], true); // init the provider; I'm avoiding TypeDescriptionProviderAttribute so that we // can exploit the default implementation for fun and profit TypeDescriptionProvider defaultProvider = TypeDescriptor.GetProvider(typeof(PropertyBag)), customProvider = new PropertyBagTypeDescriptionProvider(defaultProvider); TypeDescriptor.AddProvider(customProvider, typeof(PropertyBag)); } private static readonly object syncLock = new object(); public static void AddProperty(string name, Type type, params Attribute[] attributes) { lock (syncLock) { // append the new prop, into a *new* collection, so that downstream // callers don't have to worry about the complexities PropertyDescriptor[] newProps = new PropertyDescriptor[props.Count + 1]; props.CopyTo(newProps, 0); newProps[newProps.Length - 1] = new PropertyBagPropertyDescriptor(name, type, attributes); props = new PropertyDescriptorCollection(newProps, true); } } private readonly Dictionary values; public PropertyBag() { // mainly want to enforce that we have a public parameterless ctor values = new Dictionary(); } public object this[string key] { get { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); object value; values.TryGetValue(key, out value); return value; } set { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); var prop = props[key]; if (prop == null) throw new ArgumentException("Invalid property: " + key, "key"); values[key] = value; OnPropertyChanged(key); } } internal void Reset(string key) { values.Remove(key); } internal bool ShouldSerialize(string key) { return values.ContainsKey(key); } } static class PropertyBagExt { // cheeky fluent API to make the example code easier: public static PropertyBag With(this PropertyBag obj, string name, object value) { obj[name] = value; return obj; } } // PropertyBag file 2: provider / type-descriptor partial class PropertyBag { class PropertyBagTypeDescriptionProvider : TypeDescriptionProvider, ICustomTypeDescriptor { readonly ICustomTypeDescriptor defaultDescriptor; public PropertyBagTypeDescriptionProvider(TypeDescriptionProvider parent) : base(parent) { this.defaultDescriptor = parent.GetTypeDescriptor(typeof(PropertyBag)); } public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) { return this; } AttributeCollection ICustomTypeDescriptor.GetAttributes() { return defaultDescriptor.GetAttributes(); } string ICustomTypeDescriptor.GetClassName() { return defaultDescriptor.GetClassName(); } string ICustomTypeDescriptor.GetComponentName() { return defaultDescriptor.GetComponentName(); } TypeConverter ICustomTypeDescriptor.GetConverter() { return defaultDescriptor.GetConverter(); } EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return defaultDescriptor.GetDefaultEvent(); } PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return defaultDescriptor.GetDefaultProperty(); } object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return defaultDescriptor.GetEditor(editorBaseType); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return defaultDescriptor.GetEvents(attributes); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return defaultDescriptor.GetEvents(); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) { return PropertyBag.props; // should really be filtered, but meh! } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return PropertyBag.props; } object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) { return defaultDescriptor.GetPropertyOwner(pd); } } } // PropertyBag file 3: property descriptor partial class PropertyBag { class PropertyBagPropertyDescriptor : PropertyDescriptor { private readonly Type type; public PropertyBagPropertyDescriptor(string name, Type type, Attribute[] attributes) : base(name, attributes) { this.type = type; } public override object GetValue(object component) { return ((PropertyBag)component)[Name]; } public override void SetValue(object component, object value) { ((PropertyBag)component)[Name] = value; } public override void ResetValue(object component) { ((PropertyBag)component).Reset(Name); } public override bool CanResetValue(object component) { return true; } public override bool ShouldSerializeValue(object component) { return ((PropertyBag)component).ShouldSerialize(Name); } public override Type PropertyType { get { return type; } } public override bool IsReadOnly { get { return false; } } public override Type ComponentType { get { return typeof(PropertyBag); } } } }