Hiding properties in classes with Properties control?

  • Thread starter Thread starter Jon Pope
  • Start date Start date
J

Jon Pope

I've got a class which contains properties. I can supply this class to the
Properties control, and it will properly display the properties and allow
the user to edit them, etc. I would like to be able to programmatically
control at runtime which properties are displayed.

I've looked at the System.Windows.Forms.Design.ControlDesigner, and
specifically looked at the PreFilterProperties() override, but that seems to
only work for controls. Is there a way to programmatically change a
property's attributes at at runtime?


Cheers, Jon
 
I can supply this class to the Properties control
I assume you mean PropertyGrid? If not, then let me know ;-p
I would like to be able to programmatically
control at runtime which properties are displayed.
To get past the default filters, you can actually create a custom tab
for the PropertyGrid (and remove the 2 that it provides) - there is a
full example of this on MSDN. You can then override GetProperties() to
do whatever you want and apply whatever filters you like to the
properties. I can't find the MSDN example, but there is one on
CodeProject that covers lots of other things too:

http://www.codeproject.com/cs/miscctrl/PropertyGridExWinForms.asp
Is there a way to programmatically change a
property's attributes at at runtime?
Do you really mean a properties attributes? i.e. the "my description"
below:
[Description("my description")]
public string Name {get {return "Fred";}}

If so, then yes and no... although you can often tweak the attributes
on a PropertyDescriptor, the default reflective view on the component-
model will not respect this and you are likely to get the originals
back the next time you ask for them (since the original are burnt into
the class model). To do what you want (with full control - i.e. add/
remove attributes, work with any attribute) you probably need to
implement a custom PropertyDescriptor with bespoke (static?) storage
of the attributes. This would involve TypeDescriptionProvider (best
option) or ICustomTypeDescriptor (not quite as powerful). However,
this approach is not for the faint hearted, and you *really* need to
understand the System.ComponentModel before setting out...

If you only want to adjust a handful of attribute *values*, then
*sometimes* you can use a few tricks - i.e. I'm confident I could get
[Description], [DisplayName], [Category] all working [for edit, not
add/remove] by subclassing the attribute and providing bespoke storage
- similar to how MS manage i18n on these attributes.

Of course, to display and edit them in the PropertyGrid would need
another tab, with some custom virtual PropertyDescriptor entries that
write back to the entries... yet again, not for the faint hearted, but
it can be done.

Marc
 
I just thought of another (simpler) approach; an easier option would
be to write a wrapper object that accepts any instance and a list of
names, and applies ICustomTypeDescriptor to filter; you then use the
regular PropertyGrid but give it the wrapper rather than your object,
i.e.

myGrid.SelectedObject = PropertyFilter(myObject,"Name", "Age",
"ShoeSize");

I reckon I could get that working in about 5 minutes if you want me to
have a bash at it ;-p

Marc
 
Seems to work:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;

static class Program
{
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using(Form f = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
f.Controls.Add(grid);
grid.SelectedObject = new MemberFilter(new Person(),
"Forename", "DateOfBirth", "DoesNotExist");
Application.Run(f);
}
}
}

public class Person
{
string forename, surname;
public string Forename { get { return forename; } set
{ forename = value; } }
public string Surname { get { return surname; } set { surname
= value; } }
DateTime dob;
public DateTime DateOfBirth { get { return dob; } set { dob =
value; } }
}

public sealed class MemberFilter : ICustomTypeDescriptor
{
private readonly IDictionary<string, string> members;
private readonly object component;

public MemberFilter(object component, params string[]
members) :
this(component, false, members)
{ }
public MemberFilter(object component, bool caseSensitive,
params string[] members)
{
if (component == null) throw new
ArgumentNullException("component");
if (members == null) throw new
ArgumentNullException("members");

this.component = component;
// use invariant for property names (makes sense to me at
least...)
this.members = new Dictionary<string,
string>(caseSensitive ?
StringComparer.InvariantCulture :
StringComparer.InvariantCultureIgnoreCase);
foreach (string member in members)
{
this.members[member] = member;
}
}
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return TypeDescriptor.GetAttributes(component);
}

string ICustomTypeDescriptor.GetClassName()
{
return TypeDescriptor.GetClassName(component);
}

string ICustomTypeDescriptor.GetComponentName()
{
return TypeDescriptor.GetComponentName(component);
}

TypeConverter ICustomTypeDescriptor.GetConverter()
{
return TypeDescriptor.GetConverter(component);
}

EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
EventDescriptor result =
TypeDescriptor.GetDefaultEvent(component);
return (result != null &&
members.ContainsKey(result.Name)) ? result : null;

}

PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
PropertyDescriptor result =
TypeDescriptor.GetDefaultProperty(component);
return (result != null &&
members.ContainsKey(result.Name)) ? result : null;
}

object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(component,
editorBaseType);
}

EventDescriptorCollection
ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return GetEvents(); // don't filter on attribute; we're
calling the shots...
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return GetEvents();
}
private EventDescriptorCollection GetEvents()
{
EventDescriptorCollection master =
TypeDescriptor.GetEvents(component);
List<EventDescriptor> list = new
List<EventDescriptor>(master.Count);
foreach (EventDescriptor evt in master)
{
if (members.ContainsKey(evt.Name)) list.Add(evt);
}
return new EventDescriptorCollection(list.ToArray());
}


PropertyDescriptorCollection
ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
return GetProperties(); // don't filter on attribute;
we're calling the shots...
}

PropertyDescriptorCollection
ICustomTypeDescriptor.GetProperties()
{
return GetProperties();
}
private PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection master =
TypeDescriptor.GetProperties(component);
List<PropertyDescriptor> list = new
List<PropertyDescriptor>(master.Count);
foreach (PropertyDescriptor prop in master)
{
if (members.ContainsKey(prop.Name)) list.Add(prop);
}
return new PropertyDescriptorCollection(list.ToArray());
}

object
ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return component;
}
}
 
[additional] actually, the last few lines should probably be:

object
ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
ICustomTypeDescriptor ctd = component as
ICustomTypeDescriptor;
return ctd == null ? component : ctd.GetPropertyOwner(pd);
}

In case component itself is forwarding - i.e. perhaps we have a filter
on a filter on a component.
The other alternative would be to return (instead of the vanilla
members) wrappers around the PropertyDescriptor etc that impersonate
the original, but forward to component internally... again, it gets
messy...
 
You rock. I definitely have a lot to think about and research. Thanks for
your help.

Jon
 
Back
Top