3

I have class with multiple properties. Sometimes a property (A) may be edited in a propertygrid. But sometimes property A may not be edited. This depends on a value of another property.

How can I do this?

EDIT: I am sorry, I forget to mention that I want this in design-time.

Martijn
  • 24,441
  • 60
  • 174
  • 261

3 Answers3

2

Runtime property models are an advanced topic. For PropertyGrid the easiest route would be to write a TypeConverter, inheriting from ExpandableObjectConverter. Override GetProperties, and swap the property in question for a custom one.

Writing a PropertyDescriptor from scratch is a chore; but in this case you mainly just need to chain ("decorator") all the methods to the original (reflective) descriptor. And just override IsReadOnly to return the bool you want.

By no means trivial, but achievable.


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;
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Does this also count for design time? – Martijn Jan 14 '11 at 16:25
  • @Martijn pretty much; the problem, though, can be convincing the display to refresh the cached descriptors. But for IsReadOnly I suspect it'll work fine. – Marc Gravell Jan 14 '11 at 16:29
  • Do I also have to write a PropertyDescriptor? Or 'only' what you explained in the first paragraph? – Martijn Jan 14 '11 at 16:39
  • And for what do I have to write a TypeConverter? What does it convert? – Martijn Jan 14 '11 at 16:41
  • And on where do I place the attributes of the TypeConverter? On the properties or on the class? – Martijn Jan 14 '11 at 16:56
  • @Martijn - TypeConverter is simply the first thing PropertyGrid looks at to ask "what does this have?". The [TypeConverter(...)] attribute goes on the type (class) you want to tweak. – Marc Gravell Jan 14 '11 at 17:04
  • Nothing happens when I override the GetProperties method. I throw an exception in the body, but it is thrown. Do I miss something? Do I need to write a PropertyDescriptor? And if so, Do I have to write a PropertyDescriptor for the same class as the TypeCOnverter? – Martijn Jan 14 '11 at 17:25
  • *The exception is never thrown. – Martijn Jan 14 '11 at 17:31
  • @Martijn - I'm not available at the moment, but I'll try to put an example together. – Marc Gravell Jan 14 '11 at 19:23
  • @Marc: When are you available? – Martijn Jan 17 '11 at 10:16
  • @Martijn ah, sorry - slipped off the top of my head (lots of things on the go); one sec... – Marc Gravell Jan 17 '11 at 12:19
  • @Marc: Sorry for the late reaction but thank you for providing an example. – Martijn Jan 25 '11 at 09:20
  • @Marc Sorry to awake a sleeping Q but how would you modify this sample to dynamically change the 'ReadOnly-ness' if IsBarEditable is changed at runtime? – Robert Jeppesen Nov 19 '12 at 12:46
1

This answer assumes you are talking about WinForms. If you would like to change one property's readonly state based on another, you will need to have your object implement ICustomTypeDescriptor. This isn't a simple thing to do, but it will give you lots of flexibility about how your class is displayed in the propertygrid.

Jake Pearson
  • 27,069
  • 12
  • 75
  • 95
0

I've offered a similar solution in the past via this stack solution. It makes use of a custom property, and conditionally ignores an attempt to change at design-time vs run-time, but I'm sure could be altered in the SETter by applying your own "criteria" to allow it being changed...

Community
  • 1
  • 1
DRapp
  • 47,638
  • 12
  • 72
  • 142