C#如何在属性网格中基于另一个属性的值公开一个属性?

3

我有一个包含多个属性的类。有时候,其中一个属性(A)可以在属性网格中进行编辑。但是,有时候属性A可能无法被编辑。这取决于另一个属性的值。

我该怎么做呢?

编辑: 对不起,我忘了提到我想在设计时实现这个功能。

3个回答

2
运行时属性模型是一个高级话题。对于PropertyGrid,最简单的方法是编写一个TypeConverter,继承自ExpandableObjectConverter。重写GetProperties,并将要更改的属性替换为自定义属性。
从头开始编写PropertyDescriptor是一项繁琐的工作;但在这种情况下,您主要只需要将所有方法链接(“装饰器”)到原始(反射)描述符上。并且只需覆盖IsReadOnly以返回所需的布尔值。
这绝不是微不足道的,但是可以实现。
using System;
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.Run(new Form { Text = "read only",
            Controls = {
                new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = false }}
            }
        });
        Application.Run(new Form { Text = "read write",
            Controls = {
                new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo { IsBarEditable = true }}
            }
        });
    }

}

[TypeConverter(typeof(Foo.FooConverter))]
class Foo
{
    [Browsable(false)]
    public bool IsBarEditable { get; set; }
    public string Bar { get; set; }
    private class FooConverter : ExpandableObjectConverter
    {
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
        {
            var props = base.GetProperties(context, value, attributes);
            if (!((Foo)value).IsBarEditable)
            {   // swap it
                PropertyDescriptor[] arr = new PropertyDescriptor[props.Count];
                props.CopyTo(arr, 0);
                for (int i = 0; i < arr.Length; i++)
                {
                    if (arr[i].Name == "Bar") arr[i] = new ReadOnlyPropertyDescriptor(arr[i]);
                }
                props = new PropertyDescriptorCollection(arr);
            }
            return props;
        }
    }
}
class ReadOnlyPropertyDescriptor : ChainedPropertyDescriptor
{
    public ReadOnlyPropertyDescriptor(PropertyDescriptor tail) : base(tail) { }
    public override bool IsReadOnly
    {
        get
        {
            return true;
        }
    }
    public override void SetValue(object component, object value)
    {
        throw new InvalidOperationException();
    }
}
abstract class ChainedPropertyDescriptor : PropertyDescriptor
{
    private readonly PropertyDescriptor tail;
    protected PropertyDescriptor Tail { get {return tail; } }
    public ChainedPropertyDescriptor(PropertyDescriptor tail) : base(tail)
    {
        if (tail == null) throw new ArgumentNullException("tail");
        this.tail = tail;
    }
    public override void AddValueChanged(object component, System.EventHandler handler)
    {
        tail.AddValueChanged(component, handler);
    }
    public override AttributeCollection Attributes
    {
        get
        {
            return tail.Attributes;
        }
    }
    public override bool CanResetValue(object component)
    {
        return tail.CanResetValue(component);
    }
    public override string Category
    {
        get
        {
            return tail.Category;
        }
    }
    public override Type ComponentType
    {
        get { return tail.ComponentType; }
    }
    public override TypeConverter Converter
    {
        get
        {
            return tail.Converter;
        }
    }
    public override string Description
    {
        get
        {
            return tail.Description;
        }
    }
    public override bool DesignTimeOnly
    {
        get
        {
            return tail.DesignTimeOnly;
        }
    }
    public override string DisplayName
    {
        get
        {
            return tail.DisplayName;
        }
    }
    public override PropertyDescriptorCollection GetChildProperties(object instance, Attribute[] filter)
    {
        return tail.GetChildProperties(instance, filter);
    }
    public override object GetEditor(Type editorBaseType)
    {
        return tail.GetEditor(editorBaseType);
    }
    public override object GetValue(object component)
    {
        return tail.GetValue(component);
    }
    public override bool IsBrowsable
    {
        get
        {
            return tail.IsBrowsable;
        }
    }
    public override bool IsLocalizable
    {
        get
        {
            return tail.IsLocalizable;
        }
    }
    public override bool IsReadOnly
    {
        get { return tail.IsReadOnly; }
    }
    public override string Name
    {
        get
        {
            return tail.Name;
        }
    }
    public override Type PropertyType
    {
        get { return tail.PropertyType; }
    }
    public override void RemoveValueChanged(object component, EventHandler handler)
    {
        tail.RemoveValueChanged(component, handler);
    }
    public override void ResetValue(object component)
    {
        tail.ResetValue(component);
    }
    public override void SetValue(object component, object value)
    {
        tail.SetValue(component, value);
    }
    public override bool ShouldSerializeValue(object component)
    {
        return tail.ShouldSerializeValue(component);
    }
    public override bool SupportsChangeEvents
    {
        get
        {
            return tail.SupportsChangeEvents;
        }
    }
}

这也适用于设计时间吗? - Martijn
我是否还需要编写一个 PropertyDescriptor?或者仅仅是你在第一段中解释的内容? - Martijn
异常从未被抛出。 - Martijn
@Marc:你什么时候有空? - Martijn
@Marc 抱歉打扰你,但是你会如何修改这个示例以在运行时动态更改“ReadOnly-ness”,如果IsBarEditable被更改了呢? - Robert Jeppesen
显示剩余8条评论

1

本答案假设您正在谈论WinForms。如果您想根据另一个属性更改一个属性的只读状态,您需要让您的对象实现ICustomTypeDescriptor。这不是一件简单的事情,但它将为您提供有关如何在属性网格中显示类的灵活性。


不是完全准确的; 在这里使用TypeConverter会更容易,你也可以使用TypeDescriptionProvider; 所以有3个选项。 - Marc Gravell

0
我过去曾经提供过类似的解决方案,可以通过这个堆栈解决方案实现。它利用了自定义属性,并有条件地忽略设计时与运行时的更改尝试,但我相信可以通过在SETter中应用您自己的“标准”来进行修改,以允许更改...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接