【问题标题】:C# force PropertyGrid to not expand a sub-class property and display it at root levelC# 强制 PropertyGrid 不展开子类属性并将其显示在根级别
【发布时间】:2018-11-12 00:40:57
【问题描述】:

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

我使用了自定义 TypeConverters 和 PropertyDescriptors,就像在许多在线文章中一样,我实现了这种行为:

正如预期的那样,字段扩展得很好,但我试图不将它们扩展为单独的子类别,我只需要与根成员处于同一级别的字段成员。

现在,由于 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; }
        }
    }
}

【问题讨论】:

  • 嗯..我已经花了一整天的时间,但仍然不知道是否可行。

标签: c# winforms propertygrid


【解决方案1】:

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

  • 你的类可以实现ICustomTypeDescriptor
  • 你的类可以派生自CustomTypeDescriptor
  • 您可以创建一个新的TypeDescriptor 并为您的类或对象实例注册它

示例

在这个例子中,我创建了一个名为MyClass 的类,它有一个自定义属性列表。通过为类实现ICustomTypeDescriptor,我将在属性网格中像普通属性一样显示List&lt;CustomProperty&gt;

当您使用这种机制时,自定义属性也可以用于数据绑定。

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;
}

【讨论】:

  • 哇.. Reza,非常感谢您抽出宝贵时间!我读了很多你过去关于 PG 的帖子,我会为我的开源项目研究你的代码。
【解决方案2】:

一般来说你的想法是正确的,但你的实现是错误的。

如果您希望字段显示为产品的属性,产品必须为字段中的每个项目提供一个 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) 一起按字母顺序显示。

【讨论】:

  • 谢谢。我昨天刚刚找到了一个解决方案,就像你说的那样:) 我应该将 TypeConverter 应用于主类。
猜你喜欢
  • 2011-05-04
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 2017-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-26
相关资源
最近更新 更多