C#强制PropertyGrid不展开子类属性并在根级别显示它

时间:2018-11-09 08:53:13

标签: c# winforms propertygrid

我正遇到PropertyGrid的显示问题。 我有一个名为 Product 的对象,它具有 Fields 属性作为 List 嵌套对象。

我在许多在线文章中都使用了自定义TypeConverters和PropertyDescriptors,并且实现了以下行为: enter image description here

正如预期的那样,字段被很好地扩展了,但是我试图不将其扩展为一个单独的子类别,我只需要与根成员处于同一级别的字段成员即可。

现在,由于Product是可绑定的对象,因此我正在尝试使用转换器来实现此功能(即,不要循环并仅填充PG或创建一个新对象)。

我尝试了很多事情,是否可以欺骗TypeConverter来做到这一点?这是功能代码:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

        Product product = new Product
        {
            Symbol = "test",
            Details = new PartDetails
            {
                FileLineNo = 123,
                Orientation = "up",
                X = 555,
                Y = 888
            },

            Fields = new FieldList { 
                new Field { Name = "One", Value = "Value 1" },
                new Field { Name = "Two", Value = "Value 2" },
                new Field { Name = "Three", Value = 1234 }
            }

        };

        propertyGrid1.SelectedObject = product;
        propertyGrid1.ExpandAllGridItems();
    }
}


public class Product
{

    public string Symbol { get; set; }

    [TypeConverter(typeof(FieldListTypeConverter))]
    public FieldList Fields { get; set; }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public PartDetails Details { get; set; }

}

public class PartDetails
{
    public int FileLineNo { get; set; }
    public int X { get; set; }
    public int Y { get; set; }
    public string Orientation { get; set; }
}

public class Field
{
    public string Name { get; set; } 
    public object Value { get; set; } 

}



public class FieldList :
     List<Field>
//, ICustomTypeDescriptor
{

}

public class FieldListTypeConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType != typeof(string))
            return base.ConvertTo(context, culture, value, destinationType);

        return "";
    }

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object obj, Attribute[] attributes)
    {
        List<PropertyDescriptor> pdList = new List<PropertyDescriptor>();
        List<Field> fields = obj as List<Field>;
        if (fields != null)
        {
            foreach (Field field in fields)
            {
                FieldDescriptor fd = new FieldDescriptor(field);

                pdList.Add(fd);
            }

        }
        return new PropertyDescriptorCollection(pdList.ToArray());
    }

    private class FieldDescriptor : SimplePropertyDescriptor
    {
        public Field field { get; private set; } // instance

        public FieldDescriptor(Field field)
            // component type, property name, property type
            : base(field.GetType(), field.Name, field.Value.GetType())
        {
            this.field = field;
        }

        public override object GetValue(object obj)
        {
            return field.Value;
        }

        public override void SetValue(object obj, object value)
        {
            field.Value = value;
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

总的来说,您的想法正确,但是实现错误。

如果您希望字段显示为产品的属性,则产品必须为字段中的每个项目本身提供一个PropertyDescriptor。您可以使用应用于Product类的TypeConverter来实现此目的。

[TypeConverter(typeof(ProductTypeConverter))]
public class Product
{
    public string Symbol { get; set; }
    //[TypeConverter(typeof(FieldListTypeConverter))]
    public FieldList Fields { get; set; }
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public PartDetails Details { get; set; }
}

使用:

public class ProductTypeConverter : TypeConverter
{
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType != typeof(string))
        {
            return base.ConvertTo(context, culture, value, destinationType);
        }

        return "";
    }

    public override bool GetPropertiesSupported(ITypeDescriptorContext context)
    {
        return true;
    }

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object instance, Attribute[] attributes)
    {
        PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(instance, attributes, true);
        PropertyDescriptor fieldsDescriptor = pdc.Find("Fields", false);
        List<PropertyDescriptor> pdList = new List<PropertyDescriptor>();
        foreach (PropertyDescriptor pd in pdc)
        {
            if (pd == fieldsDescriptor)
            {

                List<Field> fields = ((Product)instance).Fields;
                if (fields != null)
                {
                    foreach (Field field in fields)
                    {
                        FieldDescriptor fd = new FieldDescriptor(field);
                        pdList.Add(fd);
                    }

                }
            }
            else
            {
                pdList.Add(pd);
            }
        }
        return new PropertyDescriptorCollection(pdList.ToArray());
    }

    private class FieldDescriptor : SimplePropertyDescriptor
    {
        private Field privatefield;
        public Field field
        {
            get
            {
                return privatefield;
            }
            private set
            {
                privatefield = value;
            }
        }

        public FieldDescriptor(Field field) : base(field.GetType(), field.Name, field.Value.GetType())
        {
            // component type, property name, property type
            this.field = field;
        }

        public override object GetValue(object obj)
        {
            return field.Value;
        }

        public override void SetValue(object obj, object value)
        {
            field.Value = value;
        }

        public override bool IsReadOnly
        {
            get
            {
                return false;
            }
        }
    }

}

请注意,从“字段”添加的属性不会分组在一起,并且会与其他未分类的属性(Symbol)一起按字母顺序显示。

答案 1 :(得分:1)

要自定义对象的属性列表,可以为对象使用自定义类型描述符。为此,可以使用以下任一选项:

  • 您的课程可以实现ICustomTypeDescriptor
  • 您的课程可以来自CustomTypeDescriptor
  • 您可以创建一个新的TypeDescriptor并将其注册为您的类或对象实例

示例

在此示例中,我创建了一个名为MyClass的类,该类具有自定义属性的列表。通过为该类实现ICustomTypeDescriptor,我将在属性网格中像普通属性一样显示List<CustomProperty>

使用此机制时,自定义属性也可以用于数据绑定。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class MyClass : ICustomTypeDescriptor
{
    public string OriginalProperty1 { get; set; }
    public string OriginalProperty2 { get; set; }
    public List<CustomProperty> CustomProperties { get; set; }

    #region ICustomTypeDescriptor
    public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
    public string GetClassName() => TypeDescriptor.GetClassName(this, true);
    public string GetComponentName() => TypeDescriptor.GetComponentName(this, true);
    public TypeConverter GetConverter() => TypeDescriptor.GetConverter(this, true);
    public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
    public PropertyDescriptor GetDefaultProperty() 
        => TypeDescriptor.GetDefaultProperty(this, true);
    public object GetEditor(Type editorBaseType) 
        => TypeDescriptor.GetEditor(this, editorBaseType, true);
    public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
    public EventDescriptorCollection GetEvents(Attribute[] attributes) 
        => TypeDescriptor.GetEvents(this, attributes, true);
    public PropertyDescriptorCollection GetProperties() => GetProperties(new Attribute[] { });
    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = TypeDescriptor.GetProperties(this, attributes, true)
            .Cast<PropertyDescriptor>()
            .Where(p => p.Name != nameof(this.CustomProperties))
            .Select(p => TypeDescriptor.CreateProperty(this.GetType(), p,
                p.Attributes.Cast<Attribute>().ToArray())).ToList();
        properties.AddRange(CustomProperties.Select(x => new CustomPropertyDescriptor(this, x)));
        return new PropertyDescriptorCollection(properties.ToArray());
    }
    public object GetPropertyOwner(PropertyDescriptor pd) => this;
    #endregion
}

自定义属性

此类模拟一个自定义属性:

public class CustomProperty
{
    public string Name { get; set; }
    public object Value { get; set; }
    public string DisplayName { get; set; }
    public string Description { get; set; }
    public string Category { get; set; } = "Custom Properties";
}

CustomPropertyDescriptor

此类是描述CustomProperty的自定义属性描述符:

public class CustomPropertyDescriptor : PropertyDescriptor
{
    object o;
    CustomProperty p;
    internal CustomPropertyDescriptor(object owner, CustomProperty property)
        : base(property.Name, null) { o = owner; p = property; }
    public override Type PropertyType => p.Value?.GetType() ?? typeof(object);
    public override void SetValue(object c, object v) => p.Value = v; 
    public override object GetValue(object c) => p.Value;
    public override bool IsReadOnly => false;
    public override Type ComponentType => o.GetType();
    public override bool CanResetValue(object c) => false;
    public override void ResetValue(object c) { }
    public override bool ShouldSerializeValue(object c) => false;
    public override string DisplayName => p.DisplayName ?? base.DisplayName;
    public override string Description => p.Description ?? base.Description;
    public override string Category => p.Category ?? base.Category;
}

用法

private void Form1_Load(object sender, EventArgs e)
{
    var o = new MyClass();
    o.CustomProperties = new List<CustomProperty>()
    {
        new CustomProperty
        {
            Name ="Property1",
            DisplayName ="First Property",
            Value ="Something",
            Description = "A custom description.",
        },
        new CustomProperty{ Name="Property2", Value= 100},
        new CustomProperty{ Name="Property3", Value= Color.Red},
    };
    propertyGrid1.SelectedObject = o;
}

enter image description here