.Net how to set IsReadOnly for a property for a usercontrol
Asked Answered
F

3

1

I have a user control in .NET with 2 new property

Prop1: Boolean
Prop2: String

I want to make Prop2 READONLY in the property grid when the user set Prop1 to false.

enter image description here

Fiora answered 20/1, 2018 at 19:8 Comment(6)
Define only the getter.Bergamot
When prop1 is true i need getter and setter.Fiora
You've obfuscated that, but what you really might need is a class with those two members or why cant you infer Prop1 based on whether Prop2 has a string length?Overbid
Check the private field of Prop1 before setting Prop2, and if it's false, don't set it. Unless you mean you want to show it "dimmed", "grayed".Bergamot
You can make the property gray in PropertyGrid using a custom type descriptor.Mohr
@Reza Aghaei Good suggestion. Sameh, see this Customizing the PropertyGrid Control - MSDNBergamot
M
7

If you want to make the property appearance read-only (gray) at run-time based on some criteria, you need to assign a CustomTypeDescriptor to your class which provides metadata about your class for property grid.

PropertyGrid control uses the type descriptor of the object to extract information about its properties to show. The type descriptor, returns a list of PropertyDescriptor objects as list of properties. Each PropertyDescriptor contains some methods and properties to return display name, description, and other information about the property. IsReadOnly property of the PropertyDescriptor is responsible to inform the PropertyGrid if the property should be read only.

Example

In the following example, I created a class containing two properties. Editable and StringProperty. If the Editable is true then StringProperty is editable, otherwise it would be read-only and will be shown as gray in PropertyGrid.

MyPropertyDescriptor

It's responsible to provide metadata for property. When implementing this class, for most properties, we will use the trivial implementation which uses original property's implementations, but for IsReadOnly we will decide based on the value of Editable property of the owner object:

using System;
using System.ComponentModel;
using System.Linq;
public class MyPropertyDescriptor : PropertyDescriptor
{
    PropertyDescriptor p;
    SampleClass o;
    public MyPropertyDescriptor(PropertyDescriptor originalProperty, SampleClass owenr)
        : base(originalProperty) { p = originalProperty; o = owenr; }
    public override bool CanResetValue(object component)
    { return p.CanResetValue(component); }
    public override object GetValue(object component) { return p.GetValue(component); }
    public override void ResetValue(object component) { p.ResetValue(component); }
    public override void SetValue(object component, object value)
    { p.SetValue(component, value); }
    public override bool ShouldSerializeValue(object component)
    { return p.ShouldSerializeValue(component); }
    public override AttributeCollection Attributes { get { return p.Attributes; } }
    public override Type ComponentType { get { return p.ComponentType; } }
    public override bool IsReadOnly { get { return !o.Editable; } }
    public override Type PropertyType { get { return p.PropertyType; } }
}

MyTypeDescriptor

It's responsible to provide a list of properties for the object. For StringProperty which we are going to change its behavior at run-time, we will return a MyPropertyDescriptor:

using System;
using System.ComponentModel;
using System.Linq;
public class MyTypeDescriptor : CustomTypeDescriptor
{
    ICustomTypeDescriptor d;
    SampleClass o;
    public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor, SampleClass owner)
        : base(originalDescriptor) { d = originalDescriptor; o = owner; }
    public override PropertyDescriptorCollection GetProperties()
    { return this.GetProperties(new Attribute[] { }); }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
            .Select(p => p.Name == "StringProperty" ? new MyPropertyDescriptor(p, o) : p)
            .ToArray();
        return new PropertyDescriptorCollection(properties);
    }
}

MyTypeDescriptionProvider

It's responsible to return a type descriptor for your object, when someone (like property grid) request type description:

using System;
using System.ComponentModel;
public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
    public MyTypeDescriptionProvider()
        : base(TypeDescriptor.GetProvider(typeof(object))) { }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type type, object o)
    {
        ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(type, o);
        return new MyTypeDescriptor(baseDescriptor, (SampleClass)o);
    }
}

SampleClass

At last, the implementation of the class:

using System;
using System.ComponentModel;
[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class SampleClass
{
    [RefreshProperties(RefreshProperties.All)]
    public bool Editable { get; set; }
    string sp;
    public string StringProperty
    {
        get { return sp; }
        set
        {
            if (Editable)
                sp = value;
        }
    }
}

Result

enter image description here

Further reading

You can read about some other solutions in the following post:

Mohr answered 20/1, 2018 at 21:19 Comment(4)
Solution 2 does not work at design time (in VS properties grid). Solution 3 is really dangerous (one can modify the internal state of the static readonly Yes and No fields of ReadOnlyAttribute) - I strongly recommend removing it from the post (and the whole "extension" SetReadOnlyAttribute method)Canzone
Thanks for the feedback @IvanStoev Yes, solution 2 is just written for PropertyGrid control, not supporting Windows Forms Designer. In fact solution 2 is like handling a control's event at run-time and doing something, it's UI code. About the extension method, the risk will happen only if someone returns ReadOnlyAttribute.Yes/No/Default fields as part of Attributes collection. I understand the risk and I'll try to handle it.Mohr
My first attempt was removing existing ReadOnly attribute from Attributes collection of the property descriptor and adding a new one. (It doesn't have any risk) but surprisingly, the property disappeared. Then I decided to update the value. If for any reason I couldn't fix the problem of removing/adding the ReadOnly attribute, probably I'll change the Post and instead of using PropertyDescriptor, will decorate the property with ReadOnly attribute and will change it with reflection (which doesn't have any risk.)Mohr
@IvanStoev Updated the SetReadOnlyAttribute extension method to remove the existing ReadOnly attribute from AttributesArray and add a new ReadOnly attribute. So there is no more risk about static values. Thanks for sharing the concern!Mohr
F
0

Many thanks @reza-aghaei, Based your your detailed answer, and @Ivan comments, the follow snippet solved my problem:

Dim captionFld = TypeDescriptor.GetProperties(Me)("Caption")
Dim roaCaption = captionFld.Attributes.OfType(Of ReadOnlyAttribute)().FirstOrDefault
roaCaption.GetType().GetField("isReadOnly", BindingFlags.NonPublic Or BindingFlags.Instance).SetValue(roaCaption, True)

Or C#

var captionFld = TypeDescriptor.GetProperties(this)["Caption"];
var roaCaption = captionFld.Attributes.OfType(Of, ReadOnlyAttribute)[].FirstOrDefault;
roaCaption.GetType().GetField("isReadOnly", (BindingFlags.NonPublic | BindingFlags.Instance)).SetValue(roaCaption, true);

You can change the .SetValue(roaCaption, true|false);

Many thanks.

Fiora answered 21/1, 2018 at 17:32 Comment(1)
NB: this code should be added in the UserControl_Load Event.Fiora
B
0

Please make sure that you have following attribute on the property named "Editable"

[RefreshProperties(System.ComponentModel.RefreshProperties.All)]

Add following method, and call it from the setter of "Editable" property and pass the name of the property to set read only.

    public void EnableDisableProperty(string PropertyName,bool IsReadOnly)
    {
        PropertyDescriptor _propDescriptor = TypeDescriptor.GetProperties(this.GetType())[PropertyName];
        ReadOnlyAttribute _readOnlyAttribute = (ReadOnlyAttribute)
                                      _propDescriptor.Attributes[typeof(ReadOnlyAttribute)];

        FieldInfo _fieldToChange = _readOnlyAttribute.GetType().GetField("isReadOnly",
                                         System.Reflection.BindingFlags.NonPublic |
                                         System.Reflection.BindingFlags.Instance);
        _fieldToChange.SetValue(_readOnlyAttribute, IsReadOnly);
    }
Brendis answered 27/2, 2018 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.