DataGridView不显示实现ICustomTypeDescriptor的对象的属性

时间:2009-09-23 22:22:28

标签: c# winforms datagridview icustomtypedescriptor

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

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

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

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

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

1 个答案:

答案 0 :(得分:22)

DataGridView查看元数据的列表版本;对此的规则是......复杂:

  1. 如果数据源实现IListSource,则评估GetList()并将其用作数据源(从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. 其他元数据不可用(退出)
  6. ([*] =我不记得这两个方向走哪条路......)

    如果您正在使用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); }
            }
        }
    }