Click here to Skip to main content
12,946,816 members (49,873 online)
Click here to Skip to main content
Add your own
alternative version


375 bookmarked
Posted 30 Jun 2003

Customized display of collection data in a PropertyGrid

, 30 Jun 2003
Rate this:
Please Sign up or sign in to vote.
The PropertyGrid is widely used to display an object's properties and values.

fig. 1: Using an Array

fig. 2: Using a customized collection


The PropertyGrid is widely used to display an object's properties and values. In previous articles I have shown how to customize and localize the displayed property names and property descriptions (Globalized Property Grid and Globalized Property Grid - Revisited).

This time I focus myself on Collections and its data. I want to show how you can customize the way a collection displays the objects it contains in a PropertyGrid.

If you assign an array to the PropertyGrid, you'll see that the PropertyGrid displays all the objects contained within the array (see above, fig. 1 ).

The first question here, how is it possible that an array provides its contained objects to the PropertyGrid as if they were properties? Furthermore, the names of the objects contained in the array are displayed with its sequence numbers ( [0],[1],...). This raises the second question. How can we customize so that more meaningful data is displayed instead of sequence numbers?

In this article, I will explore an answer to these questions. We develop a solution that will look like figure 2 (see above).

The basics

An object may provide custom information about itself by implementing an interface ICustomTypeDescriptor. This interface can be used to provide dynamic type information. This is the case for a Collection object to return its content appearing as properties in a PropertyGrid.

Information returned by a type descriptor contains information about a type regarding its properties, events, type conversion, design time editor, .... .

If ICustomTypeDescriptor is not used, then a static TypeDescriptor will be used at runtime, which provides type information based on the meta data obtained via reflection.

So, we already get half the way to the answer: We need to implement ICustomTypeDescriptor to get access to custom property information.

The property information will be returned by the interface method GetProperties(). This method returns an object of type PropertyDescriptorCollection. This object is a type safe collection of (you might guess it) PropertyDescriptor objects. PropertyDescriptor is an abstract base class. By providing your own class derived from PropertyDescriptor, you can provide customized property information as needed, to return the content of a collection and display something meaningful for each item (display name and description), as shown above (see figure 2 again).

It is more easy than it sounds. I will demonstrate it on a sample project provided for download.

A sample project

For demonstration purpose I will use a sample project. This project is simple but it shows the overall concept. It is a Windows Forms based application. Here is the scenario for the sample.


In a PropertyGrid, I want to display the data of a list of employees in a company. The employees should be shown with their full names. I want to add or remove employees, so the list should be editable. An employee item in the list should be expandable to show its complete data. Figure 2 (see above) shows the desired result.


An instance of Organization represents the company. It knows its employees which are Persons. The Organization object will be assigned to a PropertyGrid to display its data. The main form of the application holds the PropertyGrid. Fig. 2 shows this initial structure in a class diagram.

Fig. 3: Class diagram of Sample


The domain is represented by the classes Organization, Employee, Person. An instance of the class Organization represents the company. It holds a collection of Employee objects. An Employee object represents an employee of the company containing his name, age, department, etc. An employee is a person so it is derived from Person.

The Organization object needs to hold a list of Employee objects. Using an array doesn't meet our requirements, because it is not editable and uses sequence numbers instead of a required employee full name.

As mentioned in the basics section, we have to implement ICustomTypeDescriptor to return our custom property descriptors. So we choose to define a custom type safe collection class, called EmployeeCollection. This collection class will implement ICustomTypeDescriptor.

To return custom information about properties, the EmployeeCollection object will create a custom property descriptor of type PropertyDescriptor, for each of its items. So, the final class diagram for our sample will look like:

Fig. 4: Final design class diagram

Let's do the implementation.

Implementation - Part I

Implementation of Person and Employee

The implementation of the Person and Employee classes is very simple. They only act as entities holding domain data, to display in the PropertyGrid. I don't provide them here. Have a look at the sample code (Employee.cs and Person.cs) instead.

Implementation of EmployeeCollection

The implementation of EmployeeCollection is more interesting. The most relevant parts are bold.

public class EmployeeCollection : CollectionBase, ICustomTypeDescriptor

Inheriting from CollectionBase provides basic collection behavior. Only, methods for adding, removing and querying items will be added. The EmployeeCollection class implements the interface ICustomTypeDescriptor to provide custom type information.

First, we add collection methods.

// Collection methods

implementation public void Add( Employee emp )
    this.List.Add( emp );
public void Remove( Employee emp )
    this.List.Remove( emp);
public Employee this[ int index ]
        return (Employee)this.List[index];

Then, we implement the interface ICustomTypeDescriptor. Though the interface has a lot of methods, most of the methods are trivial to implement because we can delegate the call to a corresponding method of the static TypeDescriptor object to provide standard behavior.

// Implementation of ICustomTypeDescriptor:

public String GetClassName()
    return TypeDescriptor.GetClassName(this,true);

public AttributeCollection GetAttributes()
    return TypeDescriptor.GetAttributes(this,true);

public String GetComponentName()
    return TypeDescriptor.GetComponentName(this, true);

public TypeConverter GetConverter()
    return TypeDescriptor.GetConverter(this, true);

public EventDescriptor GetDefaultEvent()
    return TypeDescriptor.GetDefaultEvent(this, true);

public PropertyDescriptor GetDefaultProperty()
    return TypeDescriptor.GetDefaultProperty(this, true);

public object GetEditor(Type editorBaseType)
    return TypeDescriptor.GetEditor(this, editorBaseType, true);

public EventDescriptorCollection GetEvents(Attribute[] attributes)
    return TypeDescriptor.GetEvents(this, attributes, true);

public EventDescriptorCollection GetEvents()
    return TypeDescriptor.GetEvents(this, true);

public object GetPropertyOwner(PropertyDescriptor pd)
    return this;

The only methods we implement in a custom way are the overloaded GetProperties()-methods. These are used to return a collection of PropertyDescriptor objects used to describe the properties, in a custom way. This property descriptor objects will be used by the PropertyGrid later.

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        return GetProperties();

    public PropertyDescriptorCollection GetProperties() 
        // Create a new collection object PropertyDescriptorCollection
        pds = new PropertyDescriptorCollection(null);

        // Iterate the list of employees
        for( int i=0; i<this.List.Count; i++ )
            // For each employee create a property descriptor 
            // and add it to the 
            // PropertyDescriptorCollection instance
            CollectionPropertyDescriptor pd = new 
        return pds;

The implementation of GetProperties() is quite straight forward. First, we create a PropertyDescriptor collection object. This collection will hold all the PropertyDescriptors that are returned to the client. Here we can decide what kind of information we want to return. In our case, we create a PropertyDescriptor object of type EmployeeCollectionPropertyDescriptor for each item in the employee list and add it to the PropertyDescriptor collection.

Note: Only the employee list members will be visible to the PropertyGrid then. If you want to provide descriptions about other properties, i.e. the Count property, then get the standard PropertyDescriptor collection from the TypeDescriptor object, retrieve the PropertyDescriptor for Count and add it to the PropertyDescriptor collection as well.

EmployeeCollectionPropertyDescriptor is our custom property descriptor class derived from the abstract base class PropertyDescriptor. This class is used to format display name and description the way we want to. My implementation associates the PropertyDescriptor with the EmployeeCollection and an index to the appropriate item. Both will be provided during construction (an alternative implementation would be to let the PropertyDescriptor reference the Employee object directly).

public class EmployeeCollectionPropertyDescriptor : PropertyDescriptor
    private EmployeeCollection collection = null;
    private int index = -1;

    public CollectionPropertyDescriptor(EmployeeCollection coll, 
                       int idx) : base( "#"+idx.ToString(), null )
        this.collection = coll;
        this.index = idx;

    public override AttributeCollection Attributes
            return new AttributeCollection(null);

    public override bool CanResetValue(object component)
        return true;

    public override Type ComponentType
            return this.collection.GetType();

    public override string DisplayName
            Employee emp = this.collection[index];
            return emp.FirstName + " " + emp.LastName;

    public override string Description
            Employee emp = this.collection[index];
            StringBuilder sb = new StringBuilder();
            sb.Append(" years old, working for ");
            sb.Append(" as ");

            return sb.ToString();

    public override object GetValue(object component)
        return this.collection[index];

    public override bool IsReadOnly
        get { return true;  }

    public override string Name
        get { return "#"+index.ToString(); }

    public override Type PropertyType
        get { return this.collection[index].GetType(); }

    public override void ResetValue(object component) {}

    public override bool ShouldSerializeValue(object component)
        return true;

    public override void SetValue(object component, object value)
        // this.collection[index] = value;

Have a look at the implementation of DisplayName and Description: DisplayName will be formatted to return the full name of an employee and Description returns a more descriptive string for an employee.

Implementation of Organization

The implementation of Organization is simple. It just creates some sample objects of type Employee and adds them to the Employee Collection.

public class Organization
    EmployeeCollection employees = new EmployeeCollection(); 

    public Organization()
        // Instantiate test data objects and fill employee collection

        Employee emp1 = new Employee();
        emp1.FirstName = "Max";
        emp1.LastName = "Headroom";
        emp1.Age = 42;
        emp1.Department = "Sales";
        emp1.Role = "Manager";

        Employee emp2 = new Employee();
        emp2.FirstName = "Lara";
        emp2.LastName = "Croft";
        emp2.Age = 24;
        emp2.Department = "Accounting";
        emp2.Role = "Manager";

        emps[0] = emp1;
        emps[1] = emp2;

    [TypeConverter(typeof(ExpandableObjectConverter ))]
    public EmployeeCollection Employees
            get { return employees; }

Organization has only the employee collection as a member, which will be returned in a property Employees. Note the use of a TypeConverterAttribute attached to Employees property. This will be needed to make the Employees property expandable, that we can see the collection content at all.

An instance of Organization will be created in the constructor of the Form1 class and assigned to the PropertyGrid:

public Form1()

    organization = new Organization();
    PropertyGrid1.SelectedObject = organization;

The result of the implementation so far is shown in figure 5. The items are not displayed by sequence number anymore. The employees in the list are displayed by their full names.

The collection is editable. If you select the value side of the employee node in the PropertyGrid, you will see a button to call the standard editor (customizing the editor fits into this context but will be a topic for the next article) to modify the employee collection (see figure 6):

Fig. 5: Edit collection content

Fig. 6: Standard collection editor

You can add, remove or modify collection items.

Implementation - Part II

In figure 5 you see the class name (PropertyGridSample.Employee) in the value field of the PropertyGrid. That doesn't look nice. Furthermore we can not view or edit the employee data inline. This is what we improve now.

Type converter

A PropertyGrid uses a type converter attached to an object or property to customize the view of an item in the PropertyGrid. Type converter objects are of type TypeConverter and its most common use is to convert to and from a text representation. A custom type converter derives from TypeConverter.

A type converter can be attached to a property or a class by using the TypeConverterAttribute.

We already used one type converter in the Organization class to make the Employees property expandable:

[__b__][TypeConverter(typeof(ExpandableObjectConverter ))]
public EmployeeCollection Employees
    get { return employees; }

ExpandableObjectConverter is one of the type converter defined by the .NET Framework. .NET provides standard type converters for basic and most common types.

ExpandableObjectConverter may also be used for expanding our employee object to see its properties. This time we attached that type converter with a class ( Employee):

public class Employee : Person

Now the Employee object is expandable (see fig. 8).

Fig. 8: Expandable Employee

Nice, but the ExpandableObjectConverter displays the class name in the value field of an employee item. We should change this. We provide our own type converter. It is a good idea to derive our custom type converter from ExpandableObjectConverter, because we still want it to be expandable.

internal class EmployeeConverter : ExpandableObjectConverter

The class EmployeeConverter is defined as internal so that it is not visible to clients. For our purpose we only need to override ConvertTo(). This method converts an Employee object into any other object. We define it as follows:

    public override object ConvertTo(ITypeDescriptorContext context, 
                             System.Globalization.CultureInfo culture, 
                             object value, Type destType )
        if( destType == typeof(string) && value is Employee )
            Employee emp = (Employee)value;
            return emp.Department + "," + emp.Role;
        return base.ConvertTo(context,culture,value,destType);

We make sure that the value to be converted is of type Employee and that the destination type of conversion is a string. We can return any text that we'd like to. I have chosen that the department followed by the employee department's role should be displayed.

To apply our type converter, we modify the previous use of the TypeConverterAttribute a bit.

public class Employee : Person

Now our custom type converter EmployeeConverter will be used. That's it!

Our final result looks like figure 9 (I also added a custom type converter to EmployeeConverter that simply displays a static string "Company's employee data").

Fig. 9: Final look

We have now:

  • An employee list which displays the full name of an employee in the left column instead of simple sequence numbers.
  • Employee items in the employee list which are expandable for inline editing.
  • Meaningful data in the value field (right column) of an employee item instead of a displayed class name.
  • Selecting the Employees collection, let us call the standard editor to modify the collection by adding or removing employee objects.


Wow, seems like a lot of stuff. But is really not as much if you have understood the concept. Type descriptors and member descriptors are central to provide dynamic information. You can customize the dynamic information by providing your own implementation.

Here are the steps to customize display and description of collection content in a PropertyGrid:

  • Provide a custom property descriptor by deriving a class form the abstract base class PropertyDescriptor.
  • Override abstract methods and properties. Provide a proper implementation for the DisplayName and description properties.
  • Let your collection class implement the ICustomTypeDescriptor interface.
  • Return a collection of custom property descriptor by the GetProperties() method.
  • Optionally use TypeConverter derived objects provided by .NET or implement your own classes to customize the textual representation of your domain classes. Assign them to the appropriate classes or properties by using the TypeConverterAttribute class.

To globalize the PropertyGrid data, property descriptors may be chained together (See also Globalized property grid).



This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Gerd Klevesaat
Web Developer
Germany Germany
For ten years I worked as a senior consultant, coach, lead architect and project lead for several consulting companies.
Currently I work as a system architect for Zuehlke Engineering GmbH based in Frankfurt.
You can find a detailed resume here.

You may also be interested in...


Comments and Discussions

QuestionSorting by values ? Pin
Member 1239909116-Nov-16 4:24
memberMember 1239909116-Nov-16 4:24 
QuestionIs it possible to set the collection index number for each new employee added? Pin
Wayne King13-Oct-16 3:34
memberWayne King13-Oct-16 3:34 
QuestionWPF PropertyGrid Pin
Member 127737153-Oct-16 7:46
memberMember 127737153-Oct-16 7:46 
QuestionThank you Pin
Ferhat Özkanun22-Aug-16 6:45
memberFerhat Özkanun22-Aug-16 6:45 
QuestionMulti Select Objects Description Not Shown Pin
TheBerk1-Oct-15 9:34
memberTheBerk1-Oct-15 9:34 
GeneralMy vote of 5 Pin
MrShadowGames21-Sep-15 22:34
professionalMrShadowGames21-Sep-15 22:34 
QuestionNotes for DevExpress PropertyGridControl Pin
Member 1165623328-May-15 7:25
memberMember 1165623328-May-15 7:25 
QuestionRemove the category name "Misc" Pin
SYJ113020-Mar-15 21:26
memberSYJ113020-Mar-15 21:26 
AnswerRe: Remove the category name "Misc" Pin
Andrew Wirger19-Sep-15 5:50
memberAndrew Wirger19-Sep-15 5:50 
QuestionTypeConverter doesn't work for wpf property grid Pin
pramit_p11-Mar-15 14:36
memberpramit_p11-Mar-15 14:36 
QuestionVB conversion, Add Button just creating an Object. Pin
dannyb121-Nov-14 5:50
memberdannyb121-Nov-14 5:50 
Questionenable editing objects in root Pin
digrizzz26-Jan-14 22:27
memberdigrizzz26-Jan-14 22:27 
QuestionUp/down arrows functionality on the Collection Editor Pin
Husain Das9-Oct-13 16:45
memberHusain Das9-Oct-13 16:45 
QuestionHow to refresh the PropertyGrid automatically when binding to a CustomList<CustomList<int>> Pin
Ruben Fuchs6-Oct-13 7:57
memberRuben Fuchs6-Oct-13 7:57 
GeneralMy vote of 5 Pin
Member 100235374-Jul-13 2:51
memberMember 100235374-Jul-13 2:51 
QuestionDo we need the Organization class? Pin
Rodrigo Castilho26-May-13 16:06
memberRodrigo Castilho26-May-13 16:06 
GeneralMy vote of 5 Pin
Pranav_Parashar7-Mar-13 23:30
memberPranav_Parashar7-Mar-13 23:30 
QuestionGood code and license question Pin
Pablo Yabo14-Aug-12 4:52
memberPablo Yabo14-Aug-12 4:52 
Generalgood gob! Pin
jun_buhibuhi2-Jul-12 22:51
memberjun_buhibuhi2-Jul-12 22:51 
GeneralGreat Job! Pin
Scott-S31-Jan-12 11:37
memberScott-S31-Jan-12 11:37 
AnswerRefresh property grid! Pin
Raziele13-Dec-11 1:52
memberRaziele13-Dec-11 1:52 
One simple way to refresh property grid to see changes in collection and to prevent "out of bound".
As mentioned before every thing we need is force refresh property grid after each collection modification to force control call GetProperties(). The question is how to make if easier?
Using static events and custom TypeEditor seems to complex. So if we look closer at our collection class we can see the number interesting methods we can override and find those being called after each modification.
OnValidate looks like what we need.
Everything i've made is add event to my collection class

public class ItemCollection : CollectionBase, ICustomTypeDescriptor
  public event EventHandler ItemCollectionValidate;

and rise it in OnValidete

protected override void OnValidate(object value)
    if (ItemCollectionValidate != null)
        ItemCollectionValidate(this, new EventArgs());

And of course we need to subscribe to event

propertyGrid1.SelectedObject = ItemsCollectionInstance;
ItemsCollectionInstance.ItemCollectionValidate += (sender, args) => { propertyGrid1.Refresh(); };

Maybe logic looks ugly: to refresh after validation, but this everything CollectionBase gives to us.
The clearest way is to provide collection changing notification, like BindingList does, but this is to complex and requires a lot of efforts to rewrite collection implementing only interfaces and don't use CollectionBase.
Hope it will helpful for smb.
GeneralRe: Refresh property grid! Pin
Striblezz18-May-12 13:38
memberStriblezz18-May-12 13:38 
Generalneat but.... Pin
cinamon3-Apr-11 11:26
membercinamon3-Apr-11 11:26 
GeneralPropertyGrid and Generic List<T> Pin
robert_pawelec@op.pl31-Mar-11 0:29
memberrobert_pawelec@op.pl31-Mar-11 0:29 
GeneralMy vote of 5 Pin
JulioNobre24-Feb-11 14:28
memberJulioNobre24-Feb-11 14:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170518.1 | Last Updated 1 Jul 2003
Article Copyright 2003 by Gerd Klevesaat
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid