如何使用 PropertyGrid 允许在没有 setter 的情况下编辑属性?
How to use PropertyGrid to allow editing properties without a setter?
默认情况下,PropertyGrid 只允许使用 public setter 编辑属性。我想允许在没有 setter.
的情况下编辑属性
例如:
class A {
public int X {get;set}
public int Y {get;}
}
在上面的示例中,只有 X 是可编辑的。 Y 将显示但变灰。如何使 Y 可编辑?
注意:创建一个私人支持字段就可以了。例如:
class A {
public int X {get;set}
private int y;
public int Y {get => y; }
}
是一种方法,但荒谬复杂;
- 您创建自定义
TypeDescriptionProvider
并将其 link 为类型,或在类型中实现 ICustomTypeDescriptor
,并且
- 您创建了一个知道如何编辑
GetValue
和 SetValue
中的字段的自定义 PropertyDescriptor
- 你创建了一个自定义
TypeDescriptor
来回应大多数事情的标准 属性 描述符,在这种情况下你的新 属性 描述符
不过,说真的;请不要这样做!首先让 属性 可访问。如果它不是可设置的 属性,您不应该尝试设置它。
根据评论,听起来您 实际上 需要的是 "popsicle immutability";考虑:
class Foo {
private bool _frozen;
public void Freeze() => _frozen = true;
protected void ThrowIfFrozen() {
if (_frozen) throw new InvalidOperationException(
"The object cannot be changed once Freeze has been called");
}
private int _x, _y;
public int X {
get => _x;
set {
if (value != _x) {
ThrowIfFrozen();
_x = value;
}
}
}
public int Y {
get => _y;
set {
if (value != _y) {
ThrowIfFrozen();
_y = value;
}
}
}
}
您可以基于 ICustomTypeDescriptor Interface 构建一个 wrapper/proxy class,允许您在运行时调整属性。
您可以这样使用它:
var a = new A();
// build a proxy
var proxy = new Proxy(a);
// tweak any properties
proxy.Properties["Y"].IsReadOnly = false;
// you can also tweak attributes
proxy.Properties["Y"].Attributes.Add(new CategoryAttribute("R/O -> R/W"));
proxy.Properties["Y"].Attributes.Add(new DescriptionAttribute("This works"));
// handle property change
propertyGrid1.PropertyValueChanged += (s, e) =>
{
if (e.ChangedItem.PropertyDescriptor.Name == "Y")
{
a.Y = (int)e.ChangedItem.Value;
}
};
// select the proxy instead of the original instance
propertyGrid1.SelectedObject = proxy;
这是结果
...
class A
{
public int X { get; set; }
public int Y { get; internal set; }
}
...
public class Proxy : ICustomTypeDescriptor
{
public Proxy(object instance)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
Instance = instance;
Properties = TypeDescriptor.GetProperties(instance).OfType<PropertyDescriptor>().Select(d => new ProxyProperty(instance, d)).ToDictionary(p => p.Name);
}
public object Instance { get; }
public IDictionary<string, ProxyProperty> Properties { get; }
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(Instance);
public string GetClassName() => TypeDescriptor.GetClassName(Instance);
public string GetComponentName() => TypeDescriptor.GetComponentName(Instance);
public TypeConverter GetConverter() => TypeDescriptor.GetConverter(Instance);
public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(Instance);
public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(Instance, editorBaseType);
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(Instance);
public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(Instance, attributes);
public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(Instance);
public PropertyDescriptorCollection GetProperties() => new PropertyDescriptorCollection(Properties.Values.Select(p => new Desc(this, p)).ToArray());
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => GetProperties();
public object GetPropertyOwner(PropertyDescriptor pd) => Instance;
private class Desc : PropertyDescriptor
{
public Desc(Proxy proxy, ProxyProperty property)
: base(property.Name, property.Attributes.ToArray())
{
Proxy = proxy;
Property = property;
}
public Proxy Proxy { get; }
public ProxyProperty Property { get; }
public override Type ComponentType => Proxy.GetType();
public override Type PropertyType => Property.PropertyType ?? typeof(object);
public override bool IsReadOnly => Property.IsReadOnly;
public override bool CanResetValue(object component) => Property.HasDefaultValue;
public override object GetValue(object component) => Property.Value;
public override void ResetValue(object component) { if (Property.HasDefaultValue) Property.Value = Property.DefaultValue; }
public override void SetValue(object component, object value) => Property.Value = value;
public override bool ShouldSerializeValue(object component) => Property.ShouldSerializeValue;
}
}
public class ProxyProperty
{
public ProxyProperty(string name, object value)
{
if (name == null)
throw new ArgumentNullException(nameof(value));
Name = name;
Value = value;
Attributes = new List<Attribute>();
}
public ProxyProperty(object instance, PropertyDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
Name = descriptor.Name;
Value = descriptor.GetValue(instance);
var def = descriptor.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
if (def != null)
{
HasDefaultValue = true;
DefaultValue = def.Value;
}
IsReadOnly = (descriptor.Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault()?.IsReadOnly).GetValueOrDefault();
ShouldSerializeValue = descriptor.ShouldSerializeValue(instance);
Attributes = descriptor.Attributes.Cast<Attribute>().ToList();
PropertyType = descriptor.PropertyType;
}
public string Name { get; }
public object Value { get; set; }
public object DefaultValue { get; set; }
public bool HasDefaultValue { get; set; }
public bool IsReadOnly { get; set; }
public bool ShouldSerializeValue { get; set; }
public Type PropertyType { get; set; }
public IList<Attribute> Attributes { get; }
}
默认情况下,PropertyGrid 只允许使用 public setter 编辑属性。我想允许在没有 setter.
的情况下编辑属性例如:
class A {
public int X {get;set}
public int Y {get;}
}
在上面的示例中,只有 X 是可编辑的。 Y 将显示但变灰。如何使 Y 可编辑?
注意:创建一个私人支持字段就可以了。例如:
class A {
public int X {get;set}
private int y;
public int Y {get => y; }
}
是一种方法,但荒谬复杂;
- 您创建自定义
TypeDescriptionProvider
并将其 link 为类型,或在类型中实现ICustomTypeDescriptor
,并且 - 您创建了一个知道如何编辑
GetValue
和SetValue
中的字段的自定义 - 你创建了一个自定义
TypeDescriptor
来回应大多数事情的标准 属性 描述符,在这种情况下你的新 属性 描述符
PropertyDescriptor
不过,说真的;请不要这样做!首先让 属性 可访问。如果它不是可设置的 属性,您不应该尝试设置它。
根据评论,听起来您 实际上 需要的是 "popsicle immutability";考虑:
class Foo {
private bool _frozen;
public void Freeze() => _frozen = true;
protected void ThrowIfFrozen() {
if (_frozen) throw new InvalidOperationException(
"The object cannot be changed once Freeze has been called");
}
private int _x, _y;
public int X {
get => _x;
set {
if (value != _x) {
ThrowIfFrozen();
_x = value;
}
}
}
public int Y {
get => _y;
set {
if (value != _y) {
ThrowIfFrozen();
_y = value;
}
}
}
}
您可以基于 ICustomTypeDescriptor Interface 构建一个 wrapper/proxy class,允许您在运行时调整属性。
您可以这样使用它:
var a = new A();
// build a proxy
var proxy = new Proxy(a);
// tweak any properties
proxy.Properties["Y"].IsReadOnly = false;
// you can also tweak attributes
proxy.Properties["Y"].Attributes.Add(new CategoryAttribute("R/O -> R/W"));
proxy.Properties["Y"].Attributes.Add(new DescriptionAttribute("This works"));
// handle property change
propertyGrid1.PropertyValueChanged += (s, e) =>
{
if (e.ChangedItem.PropertyDescriptor.Name == "Y")
{
a.Y = (int)e.ChangedItem.Value;
}
};
// select the proxy instead of the original instance
propertyGrid1.SelectedObject = proxy;
这是结果
...
class A
{
public int X { get; set; }
public int Y { get; internal set; }
}
...
public class Proxy : ICustomTypeDescriptor
{
public Proxy(object instance)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
Instance = instance;
Properties = TypeDescriptor.GetProperties(instance).OfType<PropertyDescriptor>().Select(d => new ProxyProperty(instance, d)).ToDictionary(p => p.Name);
}
public object Instance { get; }
public IDictionary<string, ProxyProperty> Properties { get; }
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(Instance);
public string GetClassName() => TypeDescriptor.GetClassName(Instance);
public string GetComponentName() => TypeDescriptor.GetComponentName(Instance);
public TypeConverter GetConverter() => TypeDescriptor.GetConverter(Instance);
public EventDescriptor GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(Instance);
public object GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(Instance, editorBaseType);
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(Instance);
public EventDescriptorCollection GetEvents(Attribute[] attributes) => TypeDescriptor.GetEvents(Instance, attributes);
public PropertyDescriptor GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(Instance);
public PropertyDescriptorCollection GetProperties() => new PropertyDescriptorCollection(Properties.Values.Select(p => new Desc(this, p)).ToArray());
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) => GetProperties();
public object GetPropertyOwner(PropertyDescriptor pd) => Instance;
private class Desc : PropertyDescriptor
{
public Desc(Proxy proxy, ProxyProperty property)
: base(property.Name, property.Attributes.ToArray())
{
Proxy = proxy;
Property = property;
}
public Proxy Proxy { get; }
public ProxyProperty Property { get; }
public override Type ComponentType => Proxy.GetType();
public override Type PropertyType => Property.PropertyType ?? typeof(object);
public override bool IsReadOnly => Property.IsReadOnly;
public override bool CanResetValue(object component) => Property.HasDefaultValue;
public override object GetValue(object component) => Property.Value;
public override void ResetValue(object component) { if (Property.HasDefaultValue) Property.Value = Property.DefaultValue; }
public override void SetValue(object component, object value) => Property.Value = value;
public override bool ShouldSerializeValue(object component) => Property.ShouldSerializeValue;
}
}
public class ProxyProperty
{
public ProxyProperty(string name, object value)
{
if (name == null)
throw new ArgumentNullException(nameof(value));
Name = name;
Value = value;
Attributes = new List<Attribute>();
}
public ProxyProperty(object instance, PropertyDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
Name = descriptor.Name;
Value = descriptor.GetValue(instance);
var def = descriptor.Attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
if (def != null)
{
HasDefaultValue = true;
DefaultValue = def.Value;
}
IsReadOnly = (descriptor.Attributes.OfType<ReadOnlyAttribute>().FirstOrDefault()?.IsReadOnly).GetValueOrDefault();
ShouldSerializeValue = descriptor.ShouldSerializeValue(instance);
Attributes = descriptor.Attributes.Cast<Attribute>().ToList();
PropertyType = descriptor.PropertyType;
}
public string Name { get; }
public object Value { get; set; }
public object DefaultValue { get; set; }
public bool HasDefaultValue { get; set; }
public bool IsReadOnly { get; set; }
public bool ShouldSerializeValue { get; set; }
public Type PropertyType { get; set; }
public IList<Attribute> Attributes { get; }
}