从UserControl中移除不需要的属性和事件
使用ICustomTypeDescriptor实现来移除UserControl中所有不适用的属性和事件的访问权限。
引言
你有没有创建过UserControl,并且想要移除那些不适用的属性?事件呢?逐个重写属性并将BrowsableAttribute设置为false很麻烦,特别是由于有些属性是虚函数,而另一些则不是(需要声明为'new')。这种技术通过简单地将它们的名称添加到列表中来移除不需要的属性和事件。
背景
我目前正在编写一个执行图像转换的控件,并计划为此编写一篇文章。在重载了基类中的许多属性,并添加了[Browsable(false)]
属性后,我开始思考是否有办法在代码本身中完成此操作。我使用这种技术从该控件中移除不需要的属性和事件,并且我认为所使用的方法值得一篇单独的文章。
Using the Code
VS设计器和编辑器使用对象的TypeDescriptor来获取其成员(方法、属性和事件)的列表,这些成员是可用的。通过在你的类上实现ICustomTypeDescriptor
,你可以选择哪些成员实际上可供对象的宿主使用。这使得过滤掉你不想被控件使用者使用的任何成员成为可能。
public partial class ucImageShow : UserControl , ICustomTypeDescriptor {
...
}
}
大部分实现使用TypeDescriptor对象的静态方法来从框架中返回所有信息。对于必需的方法,我们只是获取这些信息,并过滤掉我们不需要的内容。以下是我的图像转换控件的实现。
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;
}
过滤事件和属性就是创建一个新的集合,并添加不满足过滤条件的所有现有集合的成员。然后,这个新的集合将替换ICustomTypeDescriptor
方法返回的原始集合。
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;
}
上面的示例根据属性和事件的名称过滤它们。但是,根据其他标准进行过滤并不费力,例如属性的存在和/或值,或者属性类型。
关注点
这种技术不仅移除了VS设计器对属性的访问权限,还移除了编辑器和编译器的访问权限。这意味着它不会为这些属性生成任何设计器生成的代码。
缺点是,如果将控件放置在表单上,然后排除设计器已经生成代码的属性,那么整个表单将变得无效。要修复它,你需要进入form.designer.cs
模块并删除对违规属性的任何引用。我通过排除TabIndex属性,发现了这一点。