Introduction
Have you ever created a UserControl and wanted to remove all those properties that are not applicable? And what about the events? Going through and overriding the properties with the BrowsableAttribute set to false is awkward, especially since some of these properties are virtual, while others aren't (need to be declared as 'new'). This technique removes the unwanted properties and events by simply adding their names to a list.
Background
I am currently writing a control that performs image transitions, and am planning a separate article for that. After overloading a number of properties froim the base class, and adding the [Browsable(false)]
attribute to them, I started thinking there must be a way to do this within the code itself. I used this technique to remove unwanted properties and events from that control, and I felt that the technique used deserved its own article.
Using the Code
The VS designer and editor uses the TypeDescriptor of your object to get a list of its members (methods, properties and events) that are available. By implementing the ICustomTypeDescriptor
on your class, you get to choose which of these are actually available to the host of the object. This makes it possible to filter any of the menber you don't want used by the consumer of the control.
public partial class ucImageShow : UserControl , ICustomTypeDescriptor {
...
}
}
Most of the implentation uses the static methods of the TypeDescriptor
object to return all the information from the framework. For the required methods, we just obtain that information, and filter out whatever we don't want. Below is my implementation for the image transitioning control.
public AttributeCollection GetAttributes() {
return TypeDescriptor.GetAttributes(this, true);
}
public string GetClassName() {
return TypeDescriptor.GetClassName(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) {
EventDescriptorCollection orig = TypeDescriptor.GetEvents(this, attributes, true);
return FilterEvents(orig);
}
public EventDescriptorCollection GetEvents() {
EventDescriptorCollection orig = TypeDescriptor.GetEvents(this, true);
return FilterEvents(orig);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
PropertyDescriptorCollection orig = TypeDescriptor.GetProperties(this, attributes, true);
return FilterProperties(orig);
}
public PropertyDescriptorCollection GetProperties() {
PropertyDescriptorCollection orig = TypeDescriptor.GetProperties(this, true);
return FilterProperties(orig);
}
public object GetPropertyOwner(PropertyDescriptor pd) {
return this;
}
Filtering the events and properties is simply a creating a new collection, and adding all members of the existing collection that do not meet the filter criteria. This new collection then replaces the original collection for the return of the ICustomTypeDescriptor
method.
private string[] _excludeBrowsableProperties = {
"AutoScroll",
"AutoScrollOffset",
"AutoScrollMargin",
"AutoScrollMinSize",
"AutoSize",
"AutoSizeMode",
"AutoValidate",
"CausesValidation",
"ImeMode",
"RightToLeft",
"TabIndex",
"TabStop"
};
private string[] _excludeBrowsableEvents = {
"AutoSizeChanged",
"AutoValidateChanged",
"BindingContextChanged",
"CausesValidationChanged",
"ChangeUICues",
"ImeModeChanged",
"RightToLeftChanged",
"Scroll",
"TabIndexChanged",
"TabStopChanged",
"Validated",
"Validating"
};
private PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection originalCollection) {
IEnumerable<PropertyDescriptor> selectedProperties = originalCollection.OfType<PropertyDescriptor>().Where(p => !_excludeBrowsableProperties.Contains(p.Name));
PropertyDescriptor[] descriptors = selectedProperties.ToArray();
PropertyDescriptorCollection newCollection = new PropertyDescriptorCollection(descriptors);
return newCollection;
}
private EventDescriptorCollection FilterEvents(EventDescriptorCollection origEvents) {
IEnumerable<EventDescriptor> selectedEvents = origEvents.OfType<EventDescriptor>().Where(e => !_excludeBrowsableEvents.Contains(e.Name));
EventDescriptor[] descriptors = selectedEvents.ToArray();
EventDescriptorCollection newCollection = new EventDescriptorCollection(descriptors);
return newCollection;
}
The above example filters the properties and events based on their name. However, it would not take much effort to filter them on any other criteria, for example the existance and/or value of an attribute, or the property type.
Points of Interest
This technique not only removes access to the properties from the VS designer, but also from the editor and compiler. This means that it will not produce any designer generated code for these properties.
The downside of this is that if the control is placed on a form, and then a property is excluded that the designer has already generated code for, then the whole form will become invalid. To fix that, you need to go into the form.designer.cs
module and remove any references to the offending property. I discovered this the hard way by excluding the TabIndex property.