Sometimes you want to make a property read-only in PropertyGrid based on some criteria at run-time. For example, let’s suppose you have a SampleClass containing two properties, Editable and StringProperty and you want to make StringProperty read-only if Editable is false.
We know that by adding a [ReadOnly(true)] attribute to a property we can make it read-only. But how to do it at run-time based on some criteria?
In fact we can not add Attribute to properties at run-time, but since PropertyGrid uses Type Descriptor we can provide some solutions using type descriptor mechanism.
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.
Regarding to above fact, here are solutions which we can provide to make a property read-only at run-time in PropertyGrid:
- Solution 1 – Provide a new
TypeDescriptorfor the class which returns aPropertyDescriptorhavingReadOnlyattribute for that specific property. -
Solution 2 – Add
ReadOnlyattribute toAttributeCollectionof theProperetyDescriptorby handlingPropertyChangedandSelectedObjcetsChangedevent ofPropertyGrid. -
Solution 3 – Add
ReadOnlyattribute toAttributeCollectionof theProperetyDescriptorin the class implementation based on some criteria.
Example
Let’s suppose we have a class containing two properties. Editable and StringProperty. If the Editable is true then StringProperty should be editable, otherwise it would be read-only and will be shown as gray in PropertyGrid.
This is the main goal: Making the property read-only in PropertyGrid.

Here is the class:
using System;
public class SampleClass
{
public bool Editable { get; set; }
string stringProperty;
public string StringProperty
{
get { return stringProperty; }
set { if (Editable) stringProperty = value; }
}
}
Solution 1 – Providing a new TypeDescriptor for the class
We can create a new TypeDescriptor for the class. The new type descriptor, is responsible to provide a list of PropertyDescriptor objects when PropertyGrid asks about list of properties. The new type descriptor, should return a suitable PropertyDescriptor for the property which we are going to make it readonly, to contain Read-Only attribute based on some criteria.
To support extensibility, the
TypeDescriptorclass has a companion class calledTypeDescriptionProviderand a companion attribute calledTypeDescriptionProviderAttribute. You can use aTypeDescriptionProviderAttributeon a class to introduce a completely different way of exposing metadata that meets your design goals.
Here is the implementation which is required for us to reach to the goal.
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 should be changed:
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;
}
}
}
[RefreshProperties(RefreshProperties.All)] informs the PropertyGrid that should refresh all properties appearance when Editable property changed.
Solution 2 – Handling PropertyGrid events and adding ReadOnlyAttribute
Regarding to the example, we want to have the StringProperty of our class be read-only in PropertyGrid. As an option, we can handle PropertyChanged and SelectedObjectsChanged event of the class and add ReadOnly attribute to AttributeCollection which is returned by Attributes property of PropertyDescriptor of the StringProperty. To do so, we need to use reflection to add an attribute to the AttributeCollection.
PropertyDescriptorExtensions
Here I created an extension method which allows us to add ReadOnly attribute to a PropertyDescriptor:
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
public static class PropertyDescriptorExtensions
{
public static void SetReadOnlyAttribute(this PropertyDescriptor p, bool value)
{
var attributes = p.Attributes.Cast<Attribute>()
.Where(x => !(x is ReadOnlyAttribute)).ToList();
attributes.Add(new ReadOnlyAttribute(value));
typeof(MemberDescriptor).GetProperty("AttributeArray",
BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue((MemberDescriptor)p, attributes.ToArray());
}
}
PropertyChanged and SelectedObjectsChanged
Handling PropertyChanged event is because we want to change the appearance of the property in PropertyGrid based on change in Editable property value. Also handling SelectedObjectsChanged is because we want to make sure that after we assigned an instance of the SampleClass to propertyGrid1.SelectedObject the StringProperty is read-only if required:
private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
var obj = this.propertyGrid1.SelectedObject as SampleClass;
if(obj!=null)
{
TypeDescriptor.GetProperties(this.propertyGrid1.SelectedObject)
["StringProperty"].SetReadOnlyAttribute(!obj.Editable);
propertyGrid1.Refresh();
}
}
private void propertyGrid1_SelectedObjectsChanged(object sender, EventArgs e)
{
var obj = this.propertyGrid1.SelectedObject as SampleClass;
if (obj != null)
{
TypeDescriptor.GetProperties(this.propertyGrid1.SelectedObject)
["StringProperty"].SetReadOnlyAttribute(!obj.Editable);
propertyGrid1.Refresh();
}
}
SampleClass
In this solution the SampleClass implementation will remain untouched:
using System;
public class SampleClass
{
public bool Editable { get; set; }
string stringProperty;
public string StringProperty
{
get { return stringProperty; }
set { if (Editable) stringProperty = value; }
}
}
Solution 3 – Add ReadOnly attribute on the fly in the class implementation based on some criteria
SampleClass
Implementation of the sample class should change:
public class SampleClass:Component
{
bool editable;
[RefreshProperties(RefreshProperties.All)]
public bool Editable
{
get { return editable; }
set
{
editable = value;
TypeDescriptor.GetProperties(this)["StringProperty"].SetReadOnlyAttribute(!editable);
}
}
string stringProperty;
[ReadOnly(true)]
public string StringProperty
{
get { return stringProperty; }
set
{
if (Editable)
stringProperty = value;
}
}
}
The initial [ReadOnly(true)] for the property is because we want it to be read-only by default and change the it at run-time based on the change in Editable property value.
Conclusion
The main difference between above 3 solutions is in changing or not changing the the existing code and also in designer support:
-
TypeDescrpitorSolution: The solution is attribute based and without changing the logic of the class, you can provide custom metadata for the class at run-time based on some criteria. The changes in the class code, is just because of adding some attributes and the logic of the class will not change. This solution has designer support. -
PropertyGrid Events: The solution can satisfy the requirement without changing the existing code. The changes are just in UI layer. This solution doesn’t have designer support and is based on
PropertyGridcontrol events at run-time. -
Adding
RaedOnlyattribute and changing the value at run-time: The solution works well, but you need to change the logic of the code to changeReadOnlyattribute on the fly. This solution has designer support.
Notes
-
The post is written to extend and support the answer which I posted for this question in stackoverflow: .Net how to set IsReadOnly for a property for a UserControl
-
In the first version of the post, for
SetReadOnlyAttributeI was changingisReadOnlyfield ofReadOnlyattribute. An important risk catched by Ivan Stoev. Using that solution, there is a risk to change the value of static fieldsReadOnlyAttribute.Yes,NoorDefault, because some property descriptors use those static values. I fixed the problem by removing the existingReadOnlyattribute and adding a new attribute instead.
Awesome awesome. thank you please please can you make a full tutorials for WinForms Designer and Controls Design.
Examples:
1.when to use NotifyPropertyChanged(), RefreshProperties(All,Repaint)
2.How to make a list of dynamic objects which came from SQL (DataSource, DataMember) properties of controls.
etc
Thank you. please keep going… 🙂
While this does work, it appears to be quite brittle; any changes to the Sample class results in an an InvalidCastException in the designer.