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

Eri*_*tas 6 c# datagridview winforms icustomtypedescriptor

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

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

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

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

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

Mar*_*ell 22

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

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

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

如果您正在使用List<T>(或类似),那么您会遇到"最简单"(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<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); }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)