如何在属性网格中显示动态对象?

4

我有一个自定义对象类型,需要在 PropertyGrid 中进行编辑:

public class CustomObjectType
{
    public string Name { get; set; }        
    public List<CustomProperty> Properties {get; set;}
}

以下是自定义属性列表:

public class CustomProperty
{
    public string Name { get; set; }
    public string Desc { get; set; }
    public Object DefaultValue { get; set; }    
    Type type;

    public Type Type
    {
        get
        {
            return type;
        }
        set
        {
                type = value;
                DefaultValue = Activator.CreateInstance(value);
        }              
    }
}

这里的主要问题是,PropertyGrid控件不允许编辑或使用适当的编辑工具来处理预先通过设置CustomProperty的字段Type的值而实例化的属性DefaultValueDefaultValue的类型只有在运行时才知道。
此外,我需要为CustomProperty的属性Type提供自定义TypeConverter,以显示支持类型的下拉列表(例如,IntStringColorMyOwnClass)。
我该怎么做呢?
3个回答

18
为了实现这种方法,您需要为每个属性创建一个自定义的PropertyDescriptor。 然后,通过自定义TypeConverter或(替代方案)ICustomTypeDescriptor / TypeDescriptionProvider来公开它。示例:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
[TypeConverter(typeof(CustomObjectType.CustomObjectConverter))]
public class CustomObjectType
{
    [Category("Standard")]
    public string Name { get; set; }
    private readonly List<CustomProperty> props = new List<CustomProperty>();
    [Browsable(false)]
    public List<CustomProperty> Properties { get { return props; } }

    private Dictionary<string, object> values = new Dictionary<string, object>();

    public object this[string name]
    {
        get { object val; values.TryGetValue(name, out val); return val; }
        set { values.Remove(name); }
    }

    private class CustomObjectConverter : ExpandableObjectConverter
    {
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
        {
            var stdProps = base.GetProperties(context, value, attributes);
            CustomObjectType obj = value as CustomObjectType;
            List<CustomProperty> customProps = obj == null ? null : obj.Properties;
            PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + (customProps == null ? 0 : customProps.Count)];
            stdProps.CopyTo(props, 0);
            if (customProps != null)
            {
                int index = stdProps.Count;
                foreach (CustomProperty prop in customProps)
                {
                    props[index++] = new CustomPropertyDescriptor(prop);
                }
            }
            return new PropertyDescriptorCollection(props);
        }
    }
    private class CustomPropertyDescriptor : PropertyDescriptor
    {
        private readonly CustomProperty prop;
        public CustomPropertyDescriptor(CustomProperty prop) : base(prop.Name, null)
        {
            this.prop = prop;
        }
        public override string Category { get { return "Dynamic"; } }
        public override string Description { get { return prop.Desc; } }
        public override string Name { get { return prop.Name; } }
        public override bool ShouldSerializeValue(object component) { return ((CustomObjectType)component)[prop.Name] != null; }
        public override void ResetValue(object component) { ((CustomObjectType)component)[prop.Name] = null; }
        public override bool IsReadOnly { get { return false; } }
        public override Type PropertyType { get { return prop.Type; } }
        public override bool CanResetValue(object component) { return true; }
        public override Type ComponentType { get { return typeof(CustomObjectType); } }
        public override void SetValue(object component, object value) { ((CustomObjectType)component)[prop.Name] = value; }
        public override object GetValue(object component) { return ((CustomObjectType)component)[prop.Name] ?? prop.DefaultValue; }
    }
}


public class CustomProperty
{
    public string Name { get; set; }
    public string Desc { get; set; }
    public object DefaultValue { get; set; }
    Type type;

    public Type Type
    {
        get
        {
            return type;
        }
        set
        {
                type = value;
                DefaultValue = Activator.CreateInstance(value);
        }              
    }
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        var obj = new CustomObjectType
        {
            Name = "Foo",
            Properties =
            {
                new CustomProperty { Name = "Bar", Type = typeof(int), Desc = "I'm a bar"},
                new CustomProperty { Name = "When", Type = typeof(DateTime), Desc = "When it happened"},
            }
        };
        Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = obj, Dock = DockStyle.Fill } } });
    }
}

4
走这条路 - 是否有更优雅的方法来达成这个目标? - JBeurer
2
@Marc Gravell,“CustomObjectType.this[string].set {}”如果要修改属性,则应为“values[name] = value;”。 顺便说一句,代码很棒。 - Agnel Kurian
typeConverter似乎使对象与JsonConvert不兼容,有没有办法修复它或根据需要打开/关闭它?另外你真是救星,代码太棒了。 - The Lemon
1
@TheLemon 嗯,你可以使用 TypeDescriptor.AddProvider/RemoveProvider,但那有点棘手,我甚至不想开始调试它! - Marc Gravell
太棒了,还有一个问题(抱歉一直打扰你),您的代码对于枚举工作得很好,但我无法让属性网格显示默认/预填充值。它会保持空白,直到用户选择一个选项。 - The Lemon

3

我认为Marc Gravell可能有点误解了上下文。

我试图编辑CustomObjectTypes的属性,而不是“CustomObjects”本身。

这是修改后的Marc代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

public class CustomObjectType
{
    [Category("Standard")]
    public string Name { get; set; }
    [Category("Standard")]
    public List<CustomProperty> Properties {get;set;}

    public CustomObjectType()
    {
        Properties = new List<CustomProperty>();
    }
}

[TypeConverter(typeof(ExpandableObjectConverter))]
public class Person
{
    public string Name {get;set;}
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
}

[TypeConverter(typeof(CustomProperty.CustomPropertyConverter))]
public class CustomProperty
{
    public CustomProperty()
    {
        Type = typeof(int);
        Name = "SomeProperty";    
    }

    private class CustomPropertyConverter : ExpandableObjectConverter
    {
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
        {
            var stdProps = base.GetProperties(context, value, attributes);
            CustomProperty obj = value as CustomProperty;            
            PropertyDescriptor[] props = new PropertyDescriptor[stdProps.Count + 1];
            stdProps.CopyTo(props, 0);
            props[stdProps.Count] = new ObjectDescriptor(obj);

            return new PropertyDescriptorCollection(props);
        }
    }
    private class ObjectDescriptor : PropertyDescriptor
    {
        private readonly CustomProperty prop;
        public ObjectDescriptor(CustomProperty prop)
            : base(prop.Name, null)
        {
            this.prop = prop;
        }
        public override string Category { get { return "Standard"; } }
        public override string Description { get { return "DefaultValue"; } }
        public override string Name { get { return "DefaultValue"; } }
        public override string DisplayName { get { return "DefaultValue"; } }
        public override bool ShouldSerializeValue(object component) { return ((CustomProperty)component).DefaultValue != null; }
        public override void ResetValue(object component) { ((CustomProperty)component).DefaultValue = null; }
        public override bool IsReadOnly { get { return false; } }
        public override Type PropertyType { get { return prop.Type; } }
        public override bool CanResetValue(object component) { return true; }
        public override Type ComponentType { get { return typeof(CustomProperty); } }
        public override void SetValue(object component, object value) { ((CustomProperty)component).DefaultValue = value; }
        public override object GetValue(object component) { return ((CustomProperty)component).DefaultValue; }
    }

    private class CustomTypeConverter: TypeConverter
    {
        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
                return true;

            return base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value.GetType() == typeof(string))
            {
                Type t = Type.GetType((string)value);

                return t;
            }

            return base.ConvertFrom(context, culture, value);

        }

        public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            var types = new Type[] { 
                typeof(bool), 
                typeof(int), 
                typeof(string), 
                typeof(float),
                typeof(Person),
                typeof(DateTime)};

            TypeConverter.StandardValuesCollection svc =
                new TypeConverter.StandardValuesCollection(types);
            return svc;
        }
    }

    [Category("Standard")]
    public string Name { get; set; }
    [Category("Standard")]
    public string Desc { get; set; }

    [Browsable(false)]

    public object DefaultValue { get; set; }

    Type type;

    [Category("Standard")]
    [TypeConverter(typeof(CustomTypeConverter))]       
    public Type Type
    {
        get
        {
            return type;
        }
        set
        {
            type = value;
            if (value == typeof(string))
                DefaultValue = "";
            else
                DefaultValue = Activator.CreateInstance(value);
        }
    }
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        var obj = new CustomObjectType
        {
            Name = "Foo",
            Properties =
            {
                new CustomProperty { Name = "Bar", Type = typeof(int), Desc = "I'm a bar"},
                new CustomProperty { Name = "When", Type = typeof(DateTime), Desc = "When it happened"},
            }
        };
        Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = obj, Dock = DockStyle.Fill } } });
    }
}

它可以工作,但我认为这是一个相当笨拙的解决方案。 因为我为对象提供了PropertyDescriptor,为自定义属性提供了CustomPropertyConverter,但两者都实际上没有做任何有意义的事情。 但我也不能将它们移除。

是否有一种优雅的方式,根据对象的运行时信息使用适当的编辑器,允许编辑类型为Object(例如DefaultValue)的属性?


0
public override void SetValue(object component, object value)           
{
    //((CustomObjectType)component)[prop.Name] = value;

    CustomObjectType cot = (CustomObjectType)component;

    CustomProperty cp = cot.Properties.FirstOrDefault(r => r.Name.Equals(prop.Name));
    if (cp == null) return;

    cp.DefaultValue = value;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接