Категории не отображаются в PropertyGrid для коллекции <T>, когда все свойства <T>доступны только для чтения - PullRequest
0 голосов
/ 18 декабря 2018

Как видно из заголовка, я заметил, что категории не отображаются в ** PropertyGrid * (в редакторе коллекции по умолчанию) для коллекции (Of T), когда все свойства класса "T" доступны только для чтения..

Код ниже представляет структуру кода, которую я имею:

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

Проблема заключается вс TestProperty3 .Когда это только для чтения, категория («Категория 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

Более того, давайте представим, что в TestClass3 объявлено 10 свойств (вместо 1, как в этом примере), 9 из них доступны только для чтения, а 1 - редактируемый.то в этом случае будут показаны все категории.С другой стороны, если все 10 свойств доступны только для чтения, категории не будут показаны.

Такое поведение PeopertyGrid очень раздражает и неожиданно для меня.Я хотел бы видеть свои пользовательские категории независимо от того, объявлены ли в моем классе свойства с установщиком или без него.

Какие альтернативы у меня есть, чтобы показать категории, имеющие все свойства моего класса только для чтения ?.Может быть, написание пользовательского TypeConverter или редактора коллекции могло бы исправить это раздражающее поведение визуального представления?.

Ответы [ 4 ]

0 голосов
/ 21 декабря 2018

Это не ошибка PropertyGrid, это особенность (ошибка?) CollectionForm из CollectionEditor.

Если вы назначите экземпляр TestClass3 непосредственно в сетку свойств, вы будетесм. в таблице свойств отображаются свойства категорий, как и ожидалось.Но когда CollectionForm пытается отобразить экземпляр TestClass3 в своей сетке свойств, поскольку у него нет настраиваемого свойства и конвертер коллекции не поддерживает создание экземпляра элемента, тогда он решает обернуть объект в другойобъект, производный дескриптор пользовательского типа, показывающий все свойства в категории с тем же именем, что и имя класса.

Как уже предлагалось в других ответах, вы можете исправить это путем

  • Добавление фиктивного не доступного для просмотра свойства записи в ваш класс
  • Или путем регистрации нового типадескриптор, который возвращает фиктивное не доступное для просмотра свойство для записи, когда его просят вернуть список свойств

Но я бы предпочел не изменять класс или его дескриптор типа только из-за ошибки CollectionForm.

Поскольку проблема связана с CollectionForm или CollectiorEditor, в качестве другого варианта вы можете решить проблему, создав редактор коллекций, производный от CollectionEditor, переопределив его метод 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))]
0 голосов
/ 20 декабря 2018

Скажите привет фиктивному, доступному для записи, но не доступному для просмотра свойству в вашем классе.

Конечно, это обходной путь ошибки сетки свойств (?), Но с учетом накладных расходов, необходимых для создания настраиваемого редактора коллекции.сформируйте и внедрите пользовательский 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

Если вы все еще хотите внедрить пользовательский редактор для своих коллекций, извлеките принятый ответ в эта тема .Это не проходит весь процесс, но это хорошее место для начала.

Надеюсь, это поможет.

0 голосов
/ 21 декабря 2018

Это не ошибка, сетка свойств спроектирована таким образом.Компонент считается неизменным, если все его свойства доступны только для чтения.В этом случае оно включается в это фанковое свойство-оболочку «Value».

Одно из решений - объявить пользовательский TypeDescriptionProvider для класса (или экземпляра), который создает проблему.Этот провайдер вернет экземпляр пользовательского дескриптора типа , который добавит фиктивное свойство, не предназначенное для просмотра (невидимое для сетки свойств), не предназначенное для чтения, поэтому класс больше не считается «неизменяемым».

Вот как вы можете его использовать, например:

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

Преимущество этого решения состоит в том, что не требуется никаких изменений исходного класса.Но это может иметь и другие последствия в вашем коде, поэтому вы действительно хотите проверить это в своем контексте.Также обратите внимание, что вы можете / должны удалить провайдера после закрытия сетки.

0 голосов
/ 18 декабря 2018

Это действительно очень раздражающее поведение.Однако я не верю, что вы можете обойти это: виноват не дескриптор свойства - он сообщает правильную категорию - как вы можете проверить с помощью:

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

, который выводитCategory 1: TestProperty3.

Итак;это просто причуда элемента управления редактора коллекции.Как ни странно, если вы добавите второе свойство доступное для записи , оно начнет отображать категорию для обоих.Но если вы добавите второе свойство только для чтения : оно не отображает категорию.Это относится как к get -только свойствам, так и к свойствам, помеченным [ReadOnly(true)].

Итак: я не думаю, что здесь есть хорошее решение, за исключением, возможно, использования другой реализации свойства-сетки илидобавить фиктивное свойство для записи - извините!


В качестве примечания со стороны / не связано: при использовании инициализации в стиле {get;set;} = "initial value"; (или инициализации конструктора) также неплохо добавить [DefaultValue("initial value")] вэто свойство, так что оно получает правильное поведение ShouldSerialize*() (или в терминах PropertyGrid: так, чтобы оно было выделено жирным шрифтом / не жирным шрифтом соответственно), но ... это не решит проблему, которую вы видите, извините.

...