如何使用 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,并且
  • 您创建了一个知道如何编辑 GetValueSetValue
  • 中的字段的自定义 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; }
}