2009-09-23 102 views
6

我在DataGridView中显示对象的列表。一切工作正常。根据对象的属性,将列自动添加到DataGridViewDataGridView不显示实现ICustomTypeDescriptor的对象的合适对象

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

我猜这跟这样的事实有关:ICustomTypeDescriptor每个网格的每一行显示的每个实例可能会返回一组不同的属性。

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

为什么DataGridView没有看到我的ICustomTypeDescriptor方法?是否有另一种方法可以动态地将属性添加到将显示在DataGridView中的对象?

回答

21

DataGridView查看元数据的列表版本;这个规则是...络合物:

  1. 如果数据源实现IListSourceGetList()被评估并用作数据源(继续2)
  2. 如果数据源实现ITypedListGetProperties()用于获得元数据(出口)
  3. 如果一个类型(非object)索引可以发现(即public T this[int index]),然后T经由TypeDescriptor.GetProperties(type)用作源:
    1. 如果一个TypeDescriptionProvider被分配时,这是用于针对类型的元数据(退出)
    2. 否则反射被用于元数据(出口)
  4. 如果列表不为空,则第一对象用于经由TypeDescriptor.GetProperties(list[0])元数据:
    1. 如果ICustomTypeDescriptor被实现,那么它被使用(出口)[*]
    2. 如果TypeDescriptionProvider被分配时,这是用于针对类型(出口)的元数据[*]
    3. 否则,使用反射(出口)
  5. 其他元数据是不可用的(出口)

([*] =我不记得这两个左右的路要走......)

如果您使用的是List<T>(或类似的),那么您打到“最简单”(IMO)的情况 - #3。如果你想提供自定义的元数据,那么;你最好打赌写一个TypeDescriptionProvider并将其与该类型关联。我可以写一个例子,但它会需要一段时间(在火车上,可能)...

编辑:here's一个例子,使用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<PropertyBag> list = new BindingList<PropertyBag>() { 
      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<string, object> values; 
    public PropertyBag() 
    { // mainly want to enforce that we have a public parameterless ctor 
     values = new Dictionary<string, object>(); 
    }  
    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); } 
     } 
    } 
}