Remove Unwanted Properties and Events from UserControl
Use an ICustomTypeDescriptor implementation to remove all access to inapplicable properties and events from a UserControl.
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);
}
// The following 2 methods will get the EventDescriptor objects for all events declared in
// this user control, included those inherited from the UserControl object and its ancestors.
// We then call the FilterEvents method to return a new EventDescriptorCollection with the
// required events removed.
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);
}
// The following 2 methods will get the PropertyDescriptor objects for all properties declared in
// this user control, included those inherited from the UserControl object and its ancestors.
// We then call the FilterProperties method to return a new PropertyDescriptorCollection with the
// required properties removed.
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) {
// Create an enumerator containing only the properties that are not in the provided list of property names
// and fill an array with those selected properties
IEnumerable<PropertyDescriptor> selectedProperties = originalCollection.OfType<PropertyDescriptor>().Where(p => !_excludeBrowsableProperties.Contains(p.Name));
PropertyDescriptor[] descriptors = selectedProperties.ToArray();
// Return a PropertyDescriptorCollection containing only the filtered descriptors
PropertyDescriptorCollection newCollection = new PropertyDescriptorCollection(descriptors);
return newCollection;
}
private EventDescriptorCollection FilterEvents(EventDescriptorCollection origEvents) {
// Create an enumerator containing only the events that are not in the provided list of event names
// and fill an array with those selected events
IEnumerable<EventDescriptor> selectedEvents = origEvents.OfType<EventDescriptor>().Where(e => !_excludeBrowsableEvents.Contains(e.Name));
EventDescriptor[] descriptors = selectedEvents.ToArray();
// Return an EventDescriptorCollection containing only the filtered descriptors
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.