Click here to Skip to main content
Email Password   helpLost your password?

Sample Image - FilteredPropertyGrid.jpg

Introduction

The wonderful PropertyGrid control, provided by Microsoft, is a very useful component for editing settings or parameters in your application. You can easily let the user customize any object at runtime. When you pass an object to the SelectedObject property of a PropertyDataGrid, the PropertyDataGrid will display all the properties of your object that has the BrowsableAttribute set to true. It is both very simple and efficient.

But sometimes, you only want to expose just some properties, not all of them. Suppose you want to allow the user of your application to edit the appearance of your main form. How would you do that ? In fact, this can be done very easily. Microsoft thought about this matter and gave us a very interesting property, named BrowsableAttributes. In this article, we'll first see how this property works, and how we can use it. Next, we'll see how it is not sufficient for most cases, and introduce a new approach, using inheritance.

BrowsableAttributes: the easy but insufficient way

With the PropertyGrid.BrowsableAttributes, you can choose to display only the properties that have the attributes contained in AttributeCollection. Suppose you want to only show the properties that are grouped in the "Appearance" category.

// You first create the attribute

// you want to filter with :

Attribute myfilterattribute = 
     new CategoryAttribute("Appearance");
// And you pass it to the PropertyGrid,

// via its BrowsableAttributes property :

mypropertygrid.BrowsableAttributes = new 
   AttributeCollection(new Attribute[] 
   { myfilterattribute });

You can choose several attributes, and of any sort:

mypropertygrid.BrowsableAttributes = 
  new AttributeCollection(new Attribute[] {
    new CategoryAttribute("Appearance"),
    new CategoryAttribute("Layout"),
    new DisplayNameAttribute("zzzz"),
    new BrowsableAttribute(false)
  });

Note that by default, all the browsable attributes are displayed. In other words, if you let the BrowsableAttributes empty, you'll display all the properties of the SelectedObject that have the BrowsableAttribute set to true.

This is simple and works fine. But how can we just filter by properties? Unfortunately, the PropertyGrid doesn't provide a BrowsableProperties. If you want to display a selection of properties, you have to write some code ... or read the paragraph below.

Inheritance for selected properties: the FilteredPropertyGrid

Now, we'll discuss about another problem. Suppose your SelectedObject is a TextBox, and you want to display in a PropertyGrid only the "Size" property of your TextBox. As the PropertyGrid control is, it is not possible. If you read what is written in the previous section, you may imagine passing to the PropertyGrid.BrowsableAttributes property a collection of attributes like "PropertyNameAttribute". But unfortunately, such attributes don't exist.

I propose another approach: using a wrapper object that contains the object you want to display in the PropertyGrid. This wrapper will be passed to the PropertyGrid, instead of the object itself. So, the PropertyGrid will not directly display the object's properties, but the wrapper's ones. In this case, you need to subclass the PropertyGrid with a new control that overrides the SelectedObject property. That's exactly what I did, while writing the FilteredPropertyGrid control. This control is inherited from PropertyGrid and works the same way. I just added these useful properties:

Here's how you can use it. To display only the "Size" property of your TextBox, write these lines of code:

// Create the TextBox.

TextBox mycontrol = new TextBox();
// Create the inherited PropertyGrid.

FilteredPropertyGrid mygrid = new FilteredPropertyGrid();
// Set the only property visible.

mygrid.BrowsableProperties = new string[] { "Size" };
// Attach the control to the PropertyGrid.

mygrid.SelectedObject = mycontrol;
// Force the PropertyGrid to redraw itself

mygrid.Refresh();

That's all! You can combine these four properties as you wish. Suppose you want to display all the categories, except the "Accessibility" and "Layout" ones. But you want the "AccessibilityRole" property, and not the "DataBinidngs" one. Write this code:

mygrid.HiddenPAttributes = 
   new AttibuteCollection(new Attribute[] {
       new CategoryAttribute("Accessibility"),
       new CategoryAttribute("Layout") });
mygrid.HiddenProperties = new string[] { "DataBinidngs" };
mygrid.BrowsableProperties = 
   new string[] { "AccessibilityRole" }

Note: these properties only work with SelectedObject. There is no warranty of them working with SelectedObjects.

How does it work?

Well, I will just describe the big pants of the FilteredPropertyGrid. For more information, you can download the source code above. When one of these four properties changes, the private method FilteredPropertyGrid.RefreshProperties() is called and builds a list containing all the PropertyDescriptors of the properties to display. This collection is passed to a wrapper, and the wrapper is linked to the PropertyGrid:

public class FilteredPropertyGrid : PropertyGrid
{
    private List<PropertyDescriptor> 
            m_PropertyDescriptors  = 
            new List<PropertyDescriptor>();
    private ObjectWrapper m_Wrapper = null;
    
    ( ... )

    public new object SelectedObject {
    get { return m_Wrapper != null ? 
          ((ObjectWrapper)
          base.SelectedObject).SelectedObject : 
          null; }
        set {
            if(m_Wrapper == null) {
                m_Wrapper = new ObjectWrapper(value);
                m_Wrapper.PropertyDescriptors = 
                         m_PropertyDescriptors;
                RefreshProperties();
            } else {
                // Link the wrapper to the parent PropertyGrid.

                base.SelectedObject = m_Wrapper;
            }
        }
    }
    
    ( ... )
}

The RefreshProperties() method strongly uses the TypeDescriptor class and its GetProperties() method, to get the properties of the SelectedObject and build the list.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralReuse of code in commercial product
Richard Bitango
5:45 9 Feb '09  
Hello bsargos,

Nice - exactly what I was looking for.
What's the story with its reuse in commercial products, is it allowed?
Are there any restrictions, copyright statements to be inserted, credit given etc?

Regards,
RBitango
GeneralCode License
eosjack
16:20 16 Sep '08  
There is no license or usage restriction stated in this article. Nor is there anything mentioned in the code.

Is this code free to use without restriction?
Questionhow about SelectedObjects
seanzcan
16:46 12 May '08  
First of all, really great work!

it works just fine if assign only one control to the SelectedObject property, but if I want to select multiple controls and pass it to SelectedObjects property, there is no filter any more.

Any suggestion?

Thanks

Sean
GeneralGood Reserch
manas
0:36 11 Dec '07  
thanks for the code and the document.It is realy help ful.
GeneralSmall Bug fixed
Hasson81
6:31 27 Aug '07  
Hi,
first of all thanks for the article ..
I found a small bug in the SelectedObject property and fixed it .. maybe it will help someone in this small world

the code should be like this:

public new object SelectedObject {
get { return m_Wrapper != null ? ((ObjectWrapper)base.SelectedObject).SelectedObject : null; }
set {
if (value != null) // this code was added by Hassan Assalih ( Bug Discovered here and fixed )
{
// Set the new object to the wrapper and create one if necessary.
if (m_Wrapper == null)
{
m_Wrapper = new ObjectWrapper(value);
RefreshProperties();
}
else if (m_Wrapper.SelectedObject != value)
{
bool needrefresh = value.GetType() != m_Wrapper.SelectedObject.GetType();
m_Wrapper.SelectedObject = value;
if (needrefresh) RefreshProperties();
}
// Set the list of properties to the wrapper.
m_Wrapper.PropertyDescriptors = m_PropertyDescriptors;
// Link the wrapper to the parent PropertyGrid.
base.SelectedObject = m_Wrapper;
}
else
{
m_Wrapper = null;
base.SelectedObject = null;
}
}
}

Bye ....
GeneralTabbing from Item To Item
theMouse
10:25 13 Jul '07  
Anyone know how to active tab navigation between properties.
Currently the tab key moves to the next control.
Would like to have it move down the list of properties instead.

Thanks,


theMouse
QuestionDataBindings dropdown selection only shows "None"
Peter Findt
0:00 1 Jul '07  
Hi,

Nice article!

However I have a problem with the DataBindings property. When I select its dropdown combobox it only shows "None" and not any DataSet, tables, fields, etc. which is present. The PropertyGrid is on another Form than the Form of the control itself for which the properties are shown. My question is if anyone knows how to fill that DataBindings dropdown combobox? I know it uses a UITypeEditor of type System.Windows.Forms.Design.DataMemberFieldEditor. How is it automatically filled?

If you have any idea, please let me know. Thank you very much.


Peter
GeneralDo not use BrowsableAttributes?!
sahami
11:41 18 Jun '07  
Hi

Great article. My exprience is that the BrowsableAttribute should not be used, it has some undesired side effects.
Use TypeDescriptor shown in this article instead.

/arash


GeneralThank you
ruben ruvalcaba
14:59 8 May '07  
Thank you very much...just what I was looking for, much better than the other projects you reference
GeneralWhat about expandable properties, dynamic components?
wcoenen
6:47 27 Feb '07  
1) Your approach works for the "root-level" component properties,
but when you expand one of those then its child properties will
not be filtered.

To get around this limitation, you need to change
ObjectWrapper.GetProperties. The returned PropertyDescriptor
objects should be wrapped in FilteredPropertyDescriptor
objects.

FilteredPropertyDescriptor would delegate most of its work
to the wrapped PropertyDescriptor. The implementation of FilteredPropertyDescriptor.GetValue however (which is used
by the PropertyGrid) would wrap the returned property value
in an ObjectWrapper. This also requires that you implement
ObjectWrapper.ToString(), otherwise the properties are not
going to be displayed properly.

2) It would make more sense to do the actual
filtering in the ObjectWrapper, rather then filtering once
and storing the resulting list of PropertyDescriptor instances
in the ObjectWrapper. Your implementation does not support
components which dynamically add and remove PropertyDescriptors
during their lifetime, something which is perfectly possible in
components which are ICustomTypeDescriptor implementations.

GeneralRe: Need a sample implementation of the same
jackie_1984
19:43 6 Aug '07  
Can you provide me a sample implementation for how to filter even the expandable properties.

With Regards
Jackie
GeneralRe: Need a sample implementation of the same
connectpalm
3:01 29 Feb '08  
Yes, i am also looking for a simliar solution. did anyone acheive this. if yes can you lets us know how to do this.

thanks in advance

Vishnu

GeneralGreat Article [modified]
Todd Wilder
18:38 28 Aug '06  
If i get the take home message, it is that you need to create an intermediate object that is the PropertyGrids selectedobject but returns a different objects Properties. for the sake of brutal simplicity, i'm dumping the simplest code that I got to work here. sorry for the code dump. sorry I don't know what to do with all the other Interface methods

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsApplication12
{
public partial class Form1 : Form
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.propertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
this.propertyGrid1.Location = new System.Drawing.Point(339, 12);
this.propertyGrid1.Name = "propertyGrid1";
this.propertyGrid1.Size = new System.Drawing.Size(174, 383);
this.propertyGrid1.TabIndex = 0;
this.button1.Location = new System.Drawing.Point(45, 39);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 1;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(525, 407);
this.Controls.Add(this.button1);
this.Controls.Add(this.propertyGrid1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}

#endregion
private System.Windows.Forms.PropertyGrid propertyGrid1;
private System.Windows.Forms.Button button1;
public Form1()
{
InitializeComponent();
IntermediateObject i = new IntermediateObject();
i.OriginalObject = button1;
propertyGrid1.SelectedObject = i;
}
}
class IntermediateObject: ICustomTypeDescriptor
{
public object OriginalObject;
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes()
{
return null;
}
public string GetClassName()
{
return null;
}
public string GetComponentName()
{
return null;
}
public TypeConverter GetConverter()
{
return null;
}
public EventDescriptor GetDefaultEvent()
{
return null;
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return null;
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return null;
}
public EventDescriptorCollection GetEvents()
{
return null;
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
List l = new List();
PropertyDescriptorCollection p = TypeDescriptor.GetProperties(OriginalObject);
foreach (PropertyDescriptor pd in p)
{
if (pd.Name.Contains("Color"))
{
l.Add(pd);
}
}
return new PropertyDescriptorCollection(l.ToArray());
}
public PropertyDescriptorCollection GetProperties()
{
return null;
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return OriginalObject;
}
#endregion
}


static class Program
{
/// /// The main entry point for the application.
///
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}



-- modified at 23:39 Monday 28th August, 2006
Questiondisplay property in PropertyGrid
poonam_iitr
13:13 25 Jul '06  
Hi,

Is it possible to display properties in our own order.
For example, I want to display properties in Property grid as

Standard1,
Standard2,
Standard3,
.......
.....
...
..
Standard10
Standard11

But the problem is, if I set PropertySort property as "Albhabetical" it displays as

Standard1,
Standard10,
Standard11,
Standard3,
....
...
..

I have tried other PropertySort type too.

Can anybody please tell me the way to do so.
Thanks,
P.A.
GeneralICustomTypeDescriptor
visualhint
6:24 2 May '06  
Hi,

While your solution is interesting (we can all learn from any new technique or vision) isn't it what ICustomTypeDescriptor is supposed to do ? Its method GetProperties can return a bag of completely customized properties (or a filter of the original ones)... And if you can't modify the original class, you can now (new in .net 2.0) implement a companion class that inherits from TypeDescriptionProvider and register it with the original class. Or I missed something in the goal of your custom technique ?

Thank you

Nicolas Cadilhac
VisualHint (http://www.visualhint.com)

GeneralRe: ICustomTypeDescriptor
philippe dykmans
6:47 8 Jun '08  
For as far as i can see, the method presented in this article allows to filter dynamically (i.e. at runtime). You could have different propertygrids showing the same object in different ways. I think that this is not possible with ICustomTypeDescriptor because this is a static attribute. Or am i wrong?

Grtz,
Phil

Philippe Dykmans
Software developpement
Advanced Bionics Corp.

GeneralRe: ICustomTypeDescriptor
visualhint
7:13 8 Jun '08  
Philippe,

ICustomTypeDescriptor is an interface and TypeDescriptionProvider is a class, so in both cases, everything happens at runtime.

Best regards,

Nicolas Cadilhac @ VisualHint
Smart PropertyGrid.Net Microsoft PropertyGrid Resource List Free PropertyGrid for MFC Smart FieldPackEditor.Net / DateTimePicker
GeneralRe: ICustomTypeDescriptor
philippe dykmans
8:40 9 Jun '08  
You're absolutely right. I should have known better than to start a discussion with the writer of Smart Propertygrid huh Wink

Regards,
Phil

Philippe Dykmans
Software developpement
Advanced Bionics Corp.

GeneralRe: ICustomTypeDescriptor
visualhint
9:08 9 Jun '08  
no worries, Philippe. Not everyone is playing in these classes every day...

Best regards,

Nicolas Cadilhac @ VisualHint
Smart PropertyGrid.Net Microsoft PropertyGrid Resource List Free PropertyGrid for MFC Smart FieldPackEditor.Net / DateTimePicker
GeneralCan't get more than one category to display!
saidrad007
21:52 31 Mar '06  
Hi, very nice!
When I add more than one CategoryAttribute to AttributeCollection the PropertyGrid does not display any properties. The following is the code that I use.
MypropertyGrid.BrowsableAttributes = new AttributeCollection(new Attribute[] {
new CategoryAttribute("Appearance"),
new CategoryAttribute("Layout")});
But when I remove one of the CategoryAttibutes the PropertyGrid does display the properties for that category. Any idea why?

Thanks!
GeneralRe: Can't get more than one category to display!
suamikim
23:39 4 Apr '06  
Unfortunately i can't help you any further, cause i'm struggling with the same problem and can't find a solution for it!

Maybe someone has got an answer?

thanks
Generalproblem with your demo
nexo54700
11:24 25 Mar '06  
Hi, thx for your article
I have a problem with your demo...
If I change the selected object, it doesn't refresh the property Grid and I have still the first selected object.
Can you help me?
GeneralRe: problem with your demo
bsargos
10:00 26 Mar '06  
Hi, nexo54700. You're right, the demo version is bugged. I'm sorry, I didn't send the right version of the demo file !
To correct this problem, I just sent the right file to The Code Project webmaster. You might be able to download it soon. If this version doesn't work, please, tell it to me.


Thank you very much for your remark.
QuestionRe: problem with your demo
nexo54700
22:22 28 Mar '06  
Hi, your demo work perfectly.
Unfortunaly, I have the same problem, but now using the FilteredPropertyGrid.dll in my project.
I have a control list in a panel. When each control is clicked, it calls the method:
void Control_Click(object sender, EventArgs e)
{
this.filterPropertyGrid.SelectedObject = sender;
this.filterPropertyGrid.Refresh();
}
this code works for the first control, but if I switch to another..., the property Grid doesn't refresh selected control properties.



Indeed, I cannot compile your source code.
I have the following message:

The type name 'FilteredPropertyGrid' does not exist in the type
'Azuria.Common.Controls.FilteredPropertyGrid'.
So, I click this error to browse in the source and I see the line 35 in Form1.Designer.cs:

this.filteredPropertyGrid1 = new Azuria.Common.Controls.FilteredPropertyGrid.FilteredPropertyGrid();
I transfom this line into:
this.filteredPropertyGrid1 = new Azuria.Common.Controls.FilteredPropertyGrid();
But the complier isn't happy and give the following message:

Assembly 'C:\Documents and Settings\Propriétaire\Bureau\Nouveau dossier\FilteredPropertyGrid_src\FilteredPropertyGrid\obj\Release\FilteredPropertyGrid.dll' doesn't contain any UserControl types.

Please, Bsargos, can you resolve my problems ?
Thx in advance.

-- modified at 3:23 Wednesday 29th March, 2006
AnswerRe: problem with your demo
JoeSimons
12:21 30 Jun '06  
I ran into the exact same problem. Did you find a solution?


Last Updated 27 Mar 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010