DataGridView not showing properites of objects which implement ICustomTypeDescriptor

前端 未结 1 622
忘了有多久
忘了有多久 2020-12-03 12:35

I\'m displaying a list of objects in a DataGridView. Everything was working fine. Columns were automagicaly added to the DataGridView based

相关标签:
1条回答
  • 2020-12-03 13:15

    DataGridView looks at the list version of metadata; the rules for this are... complex:

    1. if the data-source implements IListSource, GetList() is evaluated and used as the data-source (continue at 2)
    2. if the data-source implements ITypedList, GetProperties() is used to obtain metadata (exit)
    3. if a typed (non-object) indexer can be found (i.e. public T this[int index]), then T is used as the source via TypeDescriptor.GetProperties(type):
      1. if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit)
      2. otherwise reflection is used for metadata (exit)
    4. if the list is non-empty, the first object is used for metadata via TypeDescriptor.GetProperties(list[0]):
      1. if ICustomTypeDescriptor is implemented, then it is used (exit) [*]
      2. if a TypeDescriptionProvider is assigned, the this is used for metadata against the type (exit) [*]
      3. otherwise reflection is used (exit)
    5. else metadata is unavailable (exit)

    ([*]=I can't remember which way around these two go...)

    If you are using List<T> (or similar), then you hit the "simplest" (IMO) case - #3. If you want to provide custom metadata, then; you best bet is to write a TypeDescriptionProvider and associate it with the type. I can write an example but it'll take a while (on the train, probably)...

    Edit: here's an example that uses ITypedList; I'll try to tweak it to use TypeDescriptionProvider instead...

    Second edit: a full (yet minimal) example using TypeDescriptionProvider follows; long code warning...

    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); }
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题