当 <T> 的所有属性都是 read-only 时,collection<T> 的类别不会显示在 PropertyGrid 中
Categories are not shown in PropertyGrid for a collection<T>, when all the properties of <T> are read-only
正如标题所说,我注意到当所有的class "T" 的属性是 read-only.
下面的代码代表了我的代码结构:
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。当它是 read-only 时,类别 ("Category 1") 未显示在 属性 网格中...
但如果我执行 属性 可编辑,则显示类别...
C:#
[Category("Category 1")]
public string TestProperty3 {get; set;} = "Test";
VB.NET:
<Category("Category 1")>
Public Property TestProperty3 As String = "Test"
不仅如此,让我们假设在 TestClass3 中声明了 10 个属性(而不是本例中的 1 个),其中 9 个是 read-only,并且1是可编辑的,那么,在这种情况下,所有的类别都会显示出来。另一方面,如果所有 10 个属性都是 read-only,则不会显示类别。
PeopertyGrid 的这种行为对我来说非常烦人和意外。我想查看我的自定义类别,无论在我的 class 中声明的属性是否带有 setter。
我必须用什么替代方法来显示具有我的 class read-only 所有属性的类别?。也许写一个自定义 TypeConverter 或 collection 编辑器可以解决这个烦人的视觉表现行为?
这确实是一个非常烦人的行为。但是,我不相信您可以绕过它:错误的不是 属性-描述符 - 它报告了正确的类别 - 您可以通过以下方式验证:
var props = TypeDescriptor.GetProperties(new TestClass3());
foreach(PropertyDescriptor prop in props)
{
Console.WriteLine($"{prop.Category}: {prop.Name}");
}
输出 Category 1: TestProperty3
.
所以;这只是集合编辑器 UI 控件的一个怪癖。奇怪的是,如果您添加第二个 writable 属性,它会开始显示两者的类别。但是如果你添加第二个read-only 属性:它不显示类别。这适用于仅 get
属性和标记为 [ReadOnly(true)]
.
的属性
所以:我认为这里没有好的解决方案,除了可能使用不同的 属性-grid 实现,或者添加一个虚拟可写 属性 - 抱歉!
作为 side/unrelated 注意:当使用 {get;set;} = "initial value";
样式初始化(或构造函数初始化)时,最好也将 [DefaultValue("initial value")]
添加到 属性 ,以便它获得正确的 ShouldSerialize*()
行为(或 PropertyGrid
术语:使其适当地加粗/不加粗),但是......这不会解决你看到的问题, 对不起。
向你的 class.
中的虚拟可写但不 browse-able 属性 问好
当然,这是 属性 网格错误(?)的解决方法,但考虑到创建自定义 collection 编辑器表单和实现自定义 UITypeEditor 所需的开销,后者将使用您的自定义表单只是为了克服这种行为,它至少应该命名为 semi-elegant 解决方案。
代码:
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
这些是使用和不使用虚拟机的结果 属性:
如果您仍想为 collection 实施自定义编辑器,请查看 this thread 中已接受的答案。它没有经历整个过程,但它是一个很好的起点。
希望对您有所帮助。
这不是错误,属性 网格就是这样设计的。如果一个组件的所有属性都是只读的,则该组件被视为 "immutable"。在这种情况下,它被包裹在那个时髦的 "Value" 包装器 属性.
中
一个解决方案是在 class(或实例)上声明一个自定义 TypeDescriptionProvider,这会带来问题。
此提供程序将 return 一个 custom type descriptor 实例,该实例将添加一个虚拟不可浏览(对 属性 网格不可见)非只读 属性,因此 class不再被视为 "immutable"。
这是您可以使用它的方式,例如:
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;
}
这是预期的样子:
这是代码。
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;
}
}
此解决方案的优点是不需要对原始 class 进行任何更改。但它可能会对您的代码产生其他影响,因此您确实希望在您的上下文中对其进行测试。另外,请注意,您 can/should 在网格关闭后删除提供程序。
这不是 PropertyGrid
的错误,这是 CollectionEditor
的 CollectionForm
的功能(错误?)。
如果您将 TestClass3
的实例直接分配给 属性 网格,您将看到 属性 网格按预期显示类别下的属性。但是当 CollectionForm
试图在其 属性 网格中显示 TestClass3
的实例时,因为它没有任何可设置的 属性 并且其集合转换器不支持创建item 实例,然后它决定将该对象包装到另一个派生自定义类型描述符的对象中,显示与 class 名称同名的类别下的所有属性。
正如其他答案已经建议的那样,您可以通过
修复它
- 正在向您的 class
添加一个虚拟的不可浏览的可写 属性
- 或者通过注册一个新的类型描述符,当它被要求 return 属性列表 return 时,它是一个虚拟的不可浏览的可写 属性
但我不想仅仅因为 CollectionForm
错误而更改 class 或其类型描述符。
由于问题出在 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))]
正如标题所说,我注意到当所有的class "T" 的属性是 read-only.
下面的代码代表了我的代码结构:
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。当它是 read-only 时,类别 ("Category 1") 未显示在 属性 网格中...
但如果我执行 属性 可编辑,则显示类别...
C:#
[Category("Category 1")]
public string TestProperty3 {get; set;} = "Test";
VB.NET:
<Category("Category 1")>
Public Property TestProperty3 As String = "Test"
不仅如此,让我们假设在 TestClass3 中声明了 10 个属性(而不是本例中的 1 个),其中 9 个是 read-only,并且1是可编辑的,那么,在这种情况下,所有的类别都会显示出来。另一方面,如果所有 10 个属性都是 read-only,则不会显示类别。
PeopertyGrid 的这种行为对我来说非常烦人和意外。我想查看我的自定义类别,无论在我的 class 中声明的属性是否带有 setter。
我必须用什么替代方法来显示具有我的 class read-only 所有属性的类别?。也许写一个自定义 TypeConverter 或 collection 编辑器可以解决这个烦人的视觉表现行为?
这确实是一个非常烦人的行为。但是,我不相信您可以绕过它:错误的不是 属性-描述符 - 它报告了正确的类别 - 您可以通过以下方式验证:
var props = TypeDescriptor.GetProperties(new TestClass3());
foreach(PropertyDescriptor prop in props)
{
Console.WriteLine($"{prop.Category}: {prop.Name}");
}
输出 Category 1: TestProperty3
.
所以;这只是集合编辑器 UI 控件的一个怪癖。奇怪的是,如果您添加第二个 writable 属性,它会开始显示两者的类别。但是如果你添加第二个read-only 属性:它不显示类别。这适用于仅 get
属性和标记为 [ReadOnly(true)]
.
所以:我认为这里没有好的解决方案,除了可能使用不同的 属性-grid 实现,或者添加一个虚拟可写 属性 - 抱歉!
作为 side/unrelated 注意:当使用 {get;set;} = "initial value";
样式初始化(或构造函数初始化)时,最好也将 [DefaultValue("initial value")]
添加到 属性 ,以便它获得正确的 ShouldSerialize*()
行为(或 PropertyGrid
术语:使其适当地加粗/不加粗),但是......这不会解决你看到的问题, 对不起。
向你的 class.
中的虚拟可写但不 browse-able 属性 问好当然,这是 属性 网格错误(?)的解决方法,但考虑到创建自定义 collection 编辑器表单和实现自定义 UITypeEditor 所需的开销,后者将使用您的自定义表单只是为了克服这种行为,它至少应该命名为 semi-elegant 解决方案。
代码:
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
这些是使用和不使用虚拟机的结果 属性:
如果您仍想为 collection 实施自定义编辑器,请查看 this thread 中已接受的答案。它没有经历整个过程,但它是一个很好的起点。
希望对您有所帮助。
这不是错误,属性 网格就是这样设计的。如果一个组件的所有属性都是只读的,则该组件被视为 "immutable"。在这种情况下,它被包裹在那个时髦的 "Value" 包装器 属性.
中一个解决方案是在 class(或实例)上声明一个自定义 TypeDescriptionProvider,这会带来问题。 此提供程序将 return 一个 custom type descriptor 实例,该实例将添加一个虚拟不可浏览(对 属性 网格不可见)非只读 属性,因此 class不再被视为 "immutable"。
这是您可以使用它的方式,例如:
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;
}
这是预期的样子:
这是代码。
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;
}
}
此解决方案的优点是不需要对原始 class 进行任何更改。但它可能会对您的代码产生其他影响,因此您确实希望在您的上下文中对其进行测试。另外,请注意,您 can/should 在网格关闭后删除提供程序。
这不是 PropertyGrid
的错误,这是 CollectionEditor
的 CollectionForm
的功能(错误?)。
如果您将 TestClass3
的实例直接分配给 属性 网格,您将看到 属性 网格按预期显示类别下的属性。但是当 CollectionForm
试图在其 属性 网格中显示 TestClass3
的实例时,因为它没有任何可设置的 属性 并且其集合转换器不支持创建item 实例,然后它决定将该对象包装到另一个派生自定义类型描述符的对象中,显示与 class 名称同名的类别下的所有属性。
正如其他答案已经建议的那样,您可以通过
修复它- 正在向您的 class 添加一个虚拟的不可浏览的可写 属性
- 或者通过注册一个新的类型描述符,当它被要求 return 属性列表 return 时,它是一个虚拟的不可浏览的可写 属性
但我不想仅仅因为 CollectionForm
错误而更改 class 或其类型描述符。
由于问题出在 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))]