2009-09-23 104 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); } 
     } 
    } 
}