Monday, June 9, 2008

How to: Set the PropertyGrid as Read-Only

The default PropertyGrid control provided by the WinForms collection doesn’t support a read-only functionality. Setting the PropertyGrid as read-only might be useful if you want to allow your users to fully inspect a rich property object exposed by the PropertyGrid without the possibility to change something.

How useful is a Read-Only property

By a rich property object I mean an object with a lot of public and browsable properties that would activate the PropertyGrid’s scrollbar. By just disabling the PropertyGrid, the scrollbar would be also disabled and a full property inspection impossible. Not to mention that if a collection property is exposed, a disabled PropertyGrid wouldn’t be able to open the CollectionEditor for it.

How to implement a Read-Only property

The basic idea behind this is to mark each browsable property, which is about to be exposed by our PropertyGrid, with the ReadOnly attribute and to control the current state of this attribute by code using reflections (System.Reflection namespace).

If you want to expose collection properties, do not provide a ReadOnly attribute for them, otherwise the CollectionEditor will not be activated!

C# .NET

public class MyObject

{

    private string objectAttribute1;

    private string objectAttribute2;

    private string objectAttribute3;

 

    [ReadOnly(true)]

    public string ObjectAttribute1

    {

        get { return this.objectAttribute1; }

        set { this.objectAttribute1 = value; }

    }

 

    [ReadOnly(true)]

    public string ObjectAttribute2

    {

        get { return this.objectAttribute2; }

        set { this.objectAttribute2 = value; }

    }

 

    [ReadOnly(true)]

    public string ObjectAttribute3

    {

        get { return this.objectAttribute3; }

        set { this.objectAttribute3 = value; }

    }

 

    public MyObject(string attr1, string attr2, string attr3)

    {

        this.objectAttribute1 = attr1;

        this.objectAttribute2 = attr2;

        this.objectAttribute3 = attr3;

    }

}


In order to do this I will create a new UserControl object which inherits from the original PropertyGrid control.

All logic for updating the ReadOnly attribute states will be implemented in this method:

C# .NET

void SetBrowsablePropertiesAsReadOnly(object selectedObject, bool isReadOnly)


which takes as arguments the current object exposed by the PropertyGrid and the read-only state to be achived.



I will create for this new control a ReadOnly property that will provide full support at design time. Each time the value of this property is changed, the SetBrowsablePropertiesAsReadOnly() method will be called in order to update the PropertyGrid.

C# .NET

public partial class MyPropertyGrid : PropertyGrid

{

    private bool isReadOnly;

 

    public bool ReadOnly

    {

        get { return this.isReadOnly; }

 

        set

        {

            this.isReadOnly = value;

            this.SetBrowsablePropertiesAsReadOnly(this.SelectedObject, value);

        }

    }

 

    protected override void OnSelectedObjectsChanged(EventArgs e)

    {

        this.SetBrowsablePropertiesAsReadOnly(this.SelectedObject, this.isReadOnly);

        base.OnSelectedObjectsChanged(e);

    }

 

    public MyPropertyGrid() : base()

    {

        InitializeComponent();     

    }

 

    /// <summary>

    /// Chnages the state of the ReadOnly attribute regarding the isReadOnly flag value.

    /// </summary>

    /// <param name="selectedObject">The current object exposed by the PropertyGrid.</param>

    /// <param name="isReadOnly">The current read-only state of the PropertyGrid.</param>

    private void SetBrowsablePropertiesAsReadOnly(object selectedObject, bool isReadOnly)

    {

        if (selectedObject != null)

        {

            // Get all the properties of the selected object...

            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(selectedObject.GetType());

 

            foreach (PropertyDescriptor propDescript in props)

            {

                // Consider only the properties which are browsable and are not collections...

                if (propDescript.IsBrowsable && propDescript.PropertyType.GetInterface("ICollection", true) == null)

                {

                    ReadOnlyAttribute attr = propDescript.Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;

 

                    // If the current property has a ReadOnly attribute,

                    // update its state regarding the current ReadOnly state of the PropertyGrid.

                    if (attr != null)

                    {

                        FieldInfo field = attr.GetType().GetField("isReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);

                        field.SetValue(attr, isReadOnly, BindingFlags.NonPublic | BindingFlags.Instance, null, null);

                    }

                }

            }

        }

    }

}


How to extend the Read-Only functionality to CollectionEditors

Actually is the same basic idea. All you have to do is to implement the SetBrowsablePropertiesAsReadOnly() method into you customized CollectionEditor and make it work in the same way I showed you above.

If you want to know more about how to handle the CollectionEditor, please read this article: How to: Take control over the Collection Editor's PropertyGrid

kick it on DotNetKicks.com

10 comments:

Anonymous said...

Thanks for this article.

Anonymous said...

Great and exclusive solution!
It is what I looked for!

Eric said...

Thanks for sharing. Exactly what I need.

I'm using more than one PropertyGrid in my project. When I set MyPropertyGrid.ReadOnly to True, all of the PropertyGrid controls, including Windows form propertyGrid, become read-only. How do I get around this problem. Many thanks.

Campa said...

How to if i would like to dinamic change only some properties attribute instead the whole property grid?

ArGuit said...

Hi, this is a great solution.

But I'm wondering if there is another way how to achive the read-only effect wihtout the necessary of marking all properties in all classes as [ReadOnly()].

Any ideas?

Thank you.

Vittorio Paternostro said...

this is a good attempt but a bad and complex solution that can create issues elsewhere. for example, in our case it was creating problems with radio buttons. a simple solution like the following enables/disables a grid without messing and overloading any default method:

// does not let the user modify the grid content
private void propertyGrid_Enter(object sender, EventArgs e)
{
propertyGrid.Enabled = false;
}

// re-enable the grid and the scrollbars
private void transitTreeView_Click(object sender, EventArgs e)
{
propertyGrid.Enabled = true;
}

Alex said...

Actually, there's a much better and simpler solution:


TypeDescriptor.AddAttributes(this.SelectedObject, new Attribute[] { new ReadOnlyAttribute(true/false) });

Paul said...

Try this:

private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
//PaulB @ BSI -- don't allow user to reset any values (reset to old value)
propertyGrid1.SelectedGridItem.PropertyDescriptor.SetValue(propertyGrid1.SelectedObject, e.OldValue);

//**if we prefer stepping through objects...
//GridItem objGridItem = propertyGrid1.SelectedGridItem;
//PropertyDescriptor objPropertyDescriptor = propertyGrid1.SelectedGridItem.PropertyDescriptor;
//objPropertyDescriptor.SetValue(propertyGrid1.SelectedObject, e.OldValue);
}

Eliseo said...

Como hago para colocar solo un atributo en REadOnly y no a todo??.

camelia said...

I found this: http://codinglight.blogspot.com/2008/10/changing-attribute-parameters-at.html