I was able to do exactly what I need (object level assignment of the read-only attribute) using the library from this CodeProject article. What is nice is that it enables me to still use the .NET PropertyGrid
and just use the custom attributes to handle the dynamic settings.
Dynamically set readonly attribute of properties in a PropertyGrid
-
01-07-2022 - |
Question
I have a PropertyGrid
that I used to display the properties in a helper class. I assign the helper class to the PropertyGrid
like this:
myPropertyGrid.SelectedObject = mySettingsHelper;
In the helper class I assign the ReadOnlyAttribute
at design time like this:
[DisplayName("DisplayExA"),
Description("DescriptionExA"),
ReadOnlyAttribute(true)]
public string PropertyA { get; set; }
[DisplayName("DisplayExB"),
Description("DescriptionExB"),
ReadOnlyAttribute(false)]
public string PropertyB { get; set; }
[DisplayName("DisplayExC"),
Description("DescriptionExC"),
ReadOnlyAttribute(true)]
public string PropertyC { get; set; }
But now I need to be able to change this attribute on individual properties dynamically during runtime. Based on certain criteria some of these properties may need to be read-only or not read-only. How would I make the change dynamically at runtime?
EDIT:
I tried the following code but this sets the ReadOnly attribute for every instance of the object! I want to do it per object. Sometimes one object might have PropertyA read-only while a second object has PropertyA to not be read-only.
public static class PropertyReadOnlyHelper
{
public static void SetReadOnly(object container, string name, bool value)
{
try
{
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(container.GetType())[name];
ReadOnlyAttribute attribute = (ReadOnlyAttribute)descriptor.Attributes[typeof(ReadOnlyAttribute)];
FieldInfo fieldToChange = attribute.GetType().GetField("isReadOnly",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
fieldToChange.SetValue(attribute, value);
}
catch { }
}
}
Solution
OTHER TIPS
Use reflection to get the instance reference of the ReadOnlyAttribute
class, then toggle the IsReadOnly
property on that instance. Finally, re-select the item in the PropertyGrid if needed by settings its SelectedObjects to null and then resetting it. You might be able to do this using the PropertyGrid RefreshTabs
method too, I'm not sure.
EDIT:
Unfortunately the IsReadOnly property itself is read only... in this case we'd have to use reflection to change the value of the backing field for the IsReadOnly property.
Add Readonly
TextBoxID.Attributes.Add("readonly","true");
Remove readonly
TextBoxID.Attributes.Remove("readonly");
Dynamically setting browsable or readonly attribute of a property in a PropertyGrid is often needed together and also they are similiar jobs
After a few touches, the great answer of Reza Aghaei about "Hide some properties in PropertyGrid at run-time" is also applicable for manipulating the readonly attribute.
public class CustomObjectWrapper : CustomTypeDescriptor
{
public object WrappedObject { get; private set; }
public List<string> BrowsableProperties { get; private set; }
public List<string> ReadonlyProperties { get; private set; }
public CustomObjectWrapper(object o)
: base(TypeDescriptor.GetProvider(o).GetTypeDescriptor(o))
{
WrappedObject = o;
BrowsableProperties = new List<string>() { "Text", "BackColor" };
ReadonlyProperties = new List<string>() { "Font" };
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> result = new List<PropertyDescriptor>();
IEnumerable<PropertyDescriptor> properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
.Where(p => BrowsableProperties.Contains(p.Name));//unbrowsable filtering
foreach (var p in properties)
{
PropertyDescriptor resultPropertyDescriptor = null;
//handle being readonly
if (ReadonlyProperties.Contains(p.Name))
{
List<Attribute> atts = p.Attributes.Cast<Attribute>().ToList();
atts.RemoveAll(a => a.GetType().Equals(typeof(ReadOnlyAttribute)));//remove any readonly attribute
atts.Add(new ReadOnlyAttribute(true));//add "readonly=true" attribute
resultPropertyDescriptor = TypeDescriptor.CreateProperty(WrappedObject.GetType(), p, atts.ToArray());
}
else
{
resultPropertyDescriptor = TypeDescriptor.CreateProperty(WrappedObject.GetType(), p, p.Attributes.Cast<Attribute>().ToArray());
}
if (resultPropertyDescriptor != null)
result.Add(resultPropertyDescriptor);
}
return new PropertyDescriptorCollection(result.ToArray());
}
}
and the usage:
propertyGrid1.SelectedObject = new CustomObjectWrapper(myobject);
Please try the code below.
[CategoryAttribute("2. LINE"), DisplayNameAttribute("Spline Line Tension"),
DescriptionAttribute("Chart's Spline Line Tension "), ReadOnlyAttribute(false)]
public float _PG_SplineTension
{
get
{
bool lbReadyOnly = true;
SetPropertyReadOnly("_PG_SplineTension", lbReadyOnly);
return this.cfSplineTension;
}
set { this.cfSplineTension = value; }
}
private void SetPropertyReadOnly(string lsProperty, bool lbIsReadOnly)
{
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(this.GetType())[lsProperty];
ReadOnlyAttribute attribute = (ReadOnlyAttribute)
descriptor.Attributes[typeof(ReadOnlyAttribute)];
FieldInfo fieldToChange = attribute.GetType().GetField("isReadOnly",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
fieldToChange.SetValue(attribute, lbIsReadOnly);
}
Thanks a lot. Based on the answers I came with the following code that works just fine:
private void SetReadonly ( object o, bool value )
{
foreach ( PropertyInfo property in o.GetType().GetProperties() )
if ( property.GetCustomAttribute<ReadOnlyAttribute>() != null )
{
Attribute readOnly = TypeDescriptor.GetProperties( o.GetType() )[property.Name].Attributes[typeof( ReadOnlyAttribute )];
readOnly.GetType().GetField( nameof( ReadOnlyAttribute.IsReadOnly ), BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase ).SetValue( readOnly, value );
}
}