代码之家  ›  专栏  ›  技术社区  ›  ElektroStudios

当集合<T>的所有属性均为只读时,类别不会显示在集合<T>的PropertyGrid中

  •  11
  • ElektroStudios  · 技术社区  · 7 年前

    下面的代码表示我的代码结构:

    C#:

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class TestClass1 {
    
        public TestClass2 TestProperty1 {get;} = new TestClass2();
    }
    
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public sealed class TestClass2 {
    
        [TypeConverter(typeof(CollectionConverter))]
        public ReadOnlyCollection<TestClass3> TestProperty2 {
            get {
                List<TestClass3> collection = new List<TestClass3>();
                for (int i = 0; i <= 10; i++) {
                    collection.Add(new TestClass3());
                }
                return collection.AsReadOnly();
            }
        }
    }
    
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public sealed class TestClass3 {
    
        [Category("Category 1")]
        public string TestProperty3 {get;} = "Test";
    }
    

    VB.NET:

    <TypeConverter(GetType(ExpandableObjectConverter))>
    Public Class TestClass1
    
        Public ReadOnly Property TestProperty1 As TestClass2 = New TestClass2()
    
    End Class
    
    <TypeConverter(GetType(ExpandableObjectConverter))>
    Public NotInheritable Class TestClass2
    
        <TypeConverter(GetType(CollectionConverter))>
        Public ReadOnly Property TestProperty2 As ReadOnlyCollection(Of TestClass3)
            Get
                Dim collection As New List(Of TestClass3)
                For i As Integer = 0 To 10
                    collection.Add(New TestClass3())
                Next
                Return collection.AsReadOnly()
            End Get
        End Property
    
    End Class
    
    <TypeConverter(GetType(ExpandableObjectConverter))>
    Public NotInheritable Class TestClass3
    
        <Category("Category 1")>
        Public ReadOnly Property TestProperty3 As String = "Test"
    
    End Class
    

    测试属性3 . 只读时,类别(“类别1”)不会显示在属性网格中。。。

    enter image description here

    但是如果我对属性进行编辑,那么类别将显示。。。

    C:#

    [Category("Category 1")]
    public string TestProperty3 {get; set;} = "Test";
    

    VB.NET:

    <Category("Category 1")>
    Public Property TestProperty3 As String = "Test"
    

    enter image description here

    测试类别3

    这种行为 人字格 这对我来说是非常烦人和意外的。我希望看到我的自定义类别,无论我的类中声明的属性是否带有setter。

    我必须显示类别的所有属性为只读的选项有哪些?。也许写一个习惯 或者集合编辑器可以修复这种恼人的视觉表现行为?。

    4 回复  |  直到 7 年前
        1
  •  2
  •   Marc Gravell    7 年前

    这的确是一种非常令人讨厌的行为。但是,我不相信您可以绕过它:不是属性描述符出了问题-它报告了正确的类别-您可以通过以下方式进行验证:

    var props = TypeDescriptor.GetProperties(new TestClass3());
    foreach(PropertyDescriptor prop in props)
    {
        Console.WriteLine($"{prop.Category}: {prop.Name}");
    }
    

    Category 1: TestProperty3 .

    所以这只是集合编辑器UI控件的一个怪癖。奇怪的是,如果你再加上一个 属性,它将开始显示两者的类别。但是如果你再加上一个 只读 属性:它不显示类别。这两种情况都适用 get -仅属性和标记的属性 [ReadOnly(true)] .

    所以:我不认为这里有一个好的解决方案,除了使用不同的属性网格实现,或者添加一个虚拟的可写属性——对不起!


    作为旁注/无关注意事项:使用 {get;set;} = "initial value"; [DefaultValue("initial value")] 到该属性,以便它获得 ShouldSerialize*() 行为正确 PropertyGrid 术语:因此它是粗体/不粗体),但是。。。抱歉,这无法解决您看到的问题。

        2
  •  2
  •   ChD Computers    7 年前

    向类中的虚拟可写但不可浏览属性问好。

    当然,这是属性网格bug(?)的一个解决方案,但是考虑到创建自定义集合编辑器表单和实现自定义UITypeEditor所需的开销,而自定义UITypeEditor反过来将使用自定义表单来克服此行为,它至少应该被命名为一个半优雅的解决方案。

    代码:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
            Dim tc1 As New TestClass1
            PropertyGrid1.SelectedObject = tc1
        End Sub
    
        <TypeConverter(GetType(ExpandableObjectConverter))>
        Public Class TestClass1
            Public ReadOnly Property TestProperty1 As TestClass2 = New TestClass2()
        End Class
    
        <TypeConverter(GetType(ExpandableObjectConverter))>
        Public NotInheritable Class TestClass2
            <TypeConverter(GetType(CollectionConverter))>
            Public ReadOnly Property TestProperty2 As ReadOnlyCollection(Of TestClass3)
                Get
                    Dim collection As New List(Of TestClass3)
                    For i As Integer = 0 To 10
                        collection.Add(New TestClass3())
                    Next
                    Return collection.AsReadOnly()
                End Get
            End Property
        End Class
    
        <TypeConverter(GetType(ExpandableObjectConverter))>
        Public NotInheritable Class TestClass3
            <Category("Category 1")>
            Public ReadOnly Property TestProperty1 As String = "Test 1"
            <Category("Category 1")>
            Public ReadOnly Property TestProperty2 As String = "Test 2"
            <Category("Category 1")>
            Public ReadOnly Property TestProperty3 As String = "Test 3"
            <Category("Category 2")>
            Public ReadOnly Property TestProperty21 As String = "Test 21"
            <Category("Category 2")>
            Public ReadOnly Property TestProperty22 As String = "Test 22"
            <Category("Category 2")>
            Public ReadOnly Property TestProperty23 As String = "Test 23"
            'We use the following dummy property to overcome the problem with the propertygrid
            'that it doesn't display the categories once all the properties in the category
            'are readonly...
            <Browsable(False)>
            Public Property DummyWriteableProperty As String
                Get
                    Return String.Empty
                End Get
                Set(value As String)
    
                End Set
            End Property
        End Class
    
    End Class
    

    enter image description here

    如果仍要为集合实现自定义编辑器,请在中签出接受的答案 this thread . 它并没有贯穿整个过程,但它是一个很好的起点。

        3
  •  2
  •   Simon Mourier    7 年前

    这不是一个bug,属性网格就是这样设计的。如果组件的所有属性都是只读的,则认为组件是“不可变的”。在本例中,它被包装到那个时髦的“Value”包装器属性中。

    一种解决方案是声明一个自定义 TypeDescriptionProvider 在引起问题的类(或实例)上。 custom type descriptor 实例,该实例将添加一个虚拟的不可浏览(对属性网格不可见)非只读属性,因此该类不再被视为“不可变”。

    这是您可以使用它的方式,例如:

    public Form1()
    {
        InitializeComponent();
    
        // add the custom type description provider
        var prov = new NeverImmutableProvider(typeof(TestClass3));
        TypeDescriptor.AddProvider(prov, typeof(TestClass3));
    
        // run the property grid
        var c2 = new TestClass2();
    
        propertyGrid1.SelectedObject = c2;
    }
    

    正如预期的那样,情况将是这样的:

    enter image description here

    public class NeverImmutableProvider : TypeDescriptionProvider
    {
        public NeverImmutableProvider(Type type)
            : base(TypeDescriptor.GetProvider(type))
        {
        }
    
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) => new MyTypeProvider(base.GetTypeDescriptor(objectType, instance));
    
        private class MyTypeProvider : CustomTypeDescriptor
        {
            public MyTypeProvider(ICustomTypeDescriptor parent)
                : base(parent)
            {
            }
    
            public override PropertyDescriptorCollection GetProperties() => GetProperties(null);
            public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
            {
                var props = new List<PropertyDescriptor>(base.GetProperties(attributes).Cast<PropertyDescriptor>());
                props.Add(new MyProp());
                return new PropertyDescriptorCollection(props.ToArray());
            }
        }
    
        private class MyProp : PropertyDescriptor
        {
            public MyProp()
                : base("dummy", new Attribute[] { new BrowsableAttribute(false) })
            {
            }
    
            // this is the important thing, it must not be readonly
            public override bool IsReadOnly => false;
    
            public override Type ComponentType => typeof(object);
            public override Type PropertyType => typeof(object);
            public override bool CanResetValue(object component) => true;
            public override object GetValue(object component) => null;
            public override void ResetValue(object component) { }
            public override void SetValue(object component, object value) { }
            public override bool ShouldSerializeValue(object component) => false;
        }
    }
    

    此解决方案的优点是不需要对原始类进行任何更改。但它可能在代码中有其他含义,因此您确实希望在您的上下文中测试它。另外,请注意,一旦网格关闭,您可以/应该删除提供程序。

        4
  •  2
  •   Reza Aghaei    7 年前

    这不是你的错 PropertyGrid ,它的特征(缺点?) CollectionForm CollectionEditor .

    TestClass3 直接到属性网格,您将看到属性网格按预期显示类别下的属性。但是什么时候 集合形式 测试类别3 在其属性网格中,由于它没有任何可设置的属性,并且它的集合转换器不支持创建项实例,因此它决定将该对象包装到另一个派生自定义类型描述符的对象中,显示与类名同名的类别下的所有属性。

    正如其他答案所建议的,您可以通过

    • 向类中添加虚拟的不可浏览的可写属性
    • 或者注册一个新的类型描述符,当要求它返回属性列表时,它返回一个虚拟的不可浏览的可写属性

    但是我不希望仅仅因为 过错

    因为问题出在 CollectiorEditor ,作为另一个选项,您可以通过创建从 集合编辑 CreateCollectorForm 方法,并在尝试在集合编辑器窗体中设置属性网格的选定对象时更改其行为:

    public class MyCollectionEditor<T> : CollectionEditor
    {
        public MyCollectionEditor() : base(typeof(T)) { }
        public override object EditValue(ITypeDescriptorContext context, 
            IServiceProvider provider, object value)
        {
            return base.EditValue(context, provider, value);
        }
        protected override CollectionForm CreateCollectionForm()
        {
            var f = base.CreateCollectionForm();
            var propertyBrowser = f.Controls.Find("propertyBrowser", true)
                .OfType<PropertyGrid>().FirstOrDefault();
            var listbox = f.Controls.Find("listbox", true)
               .OfType<ListBox>().FirstOrDefault();
            if (propertyBrowser != null && listbox !=null)
                propertyBrowser.SelectedObjectsChanged += (sender, e) =>
                {
                    var o = listbox.SelectedItem;
                    if (o != null)
                        propertyBrowser.SelectedObject =
                            o.GetType().GetProperty("Value").GetValue(o);
                };
            return f;
        }
    }
    

    TesProperty2 使用此属性:

    [Editor(typeof(MyCollectionEditor<TestClass3>), typeof(UITypeEditor))]