Dotnet专业组件开发揭密(二)
--TypeDescriptor内部机制及其应用(上)
本文代码下载
官方地址://Code/Csharp/BlogCode/TypeDescriptorDemo.rar
CSDN地址:http://download.csdn.net/user/BlueDog
本篇要讲述的是System.ComponentModel空间里最神奇的类,也是最重要的类,几乎所有的高级控件设计人员都必需掌握的类—TypeDescriptor。它的作用类似于反射,但是它是可以动态更改类信息的!虽然我们可能很少碰到直接使用它的情况,但是它的原理必须理解,这样你才不会对VS中一些机制产生疑问。
我们知道元数据可以说是Dotnet的基础,一旦编译后,就不能更改,而我们经常使用System.Reflection命名空间里的一些类来查看这些元数据,但是我们并没有办法去更改这些元数据。但是往往在设计阶段我们有这个需要,比如我们对某个控件要求在设计时确定一个属性的初使值,但是这个属性在运行时是不充许改变的,或者根本就不能公开,有没有什么好办法呢?再问你一个问题,以前的ActiveX组件在导入Dotnet环境后,你想过没有,原来的ActiveX控件是没有所谓的元数据的,哪为什么我们可以通过“属性窗口”来查看它的属性呢?答案还是TypeDescriptor起了作用。
说起TypeDescriptor,就不能不说到它最主要的“客户”—PropertyGrid,这个强大的微软居然遮遮掩掩不愿意直接公开的控件(呵呵,是不是很长的定语呀),以及一个非常有用的接口ICustomTypeDescriptor。嗯,我们先来说说PropertyGrid,从其名字就可以知道它可以显示一个类实例的属性,我们知道它只能显示一个类的属性(Property),但并不能显示公共字段。我们将在后面介绍如何实现让它显示公共字段。ICustomTypeDescriptor,则是为对象提供动态自定义类型信息的接口。不要小看了这句话,神奇的功能都是它实现的。
它们三者的关系是这样子的,当用PropertyGrid来查看一个类的属性信息时,它并不是我们一般认为的会通过Reflection空间里的类来进行反射,找到元数据,然后显示出来。实际上它利用TypeDescriptor来查看类实例的属性信息。而TypeDescriptor它的内部有几个逻辑判断,看看A)这个类是不是实现了ICustomTypeDescriptor接口,如果是它会用这个接口来返回属性信息,如果没有它会用默认的反射来返回;B)它会查看同一个逻辑容器里是不是有实现了IextenderProvider接口的类,如果有它会进行特殊处理;C)如果需要过滤一些属性,它会过滤掉。正因为它内部有如此复杂的机制,那么我们便可以通过第一步或第二步来影响返回的属性信息。实际上这里大家就知道了我们上一篇文章里为什么扩展机制如何起作用了。当然TypeDescriptor远不止我们上面所简述的这么简单,我们会在后面的篇章里深入探索它。
现在我们来回答关于ActiveX控件属性的问题。实际上,当我们拖入一个ActiveX组件时,VS会为我们产生一个包装类,它继承于System.Windows.Forms.AxHost这个纯虚的类,而这个类正好实现了ICustomTypeDescriptor,因此“属性窗口”通过TypeDescriptor来查看这个ActiveX组件时,它发现它实现了ICustomTypeDescriptor接口,因此它为用这个接口来取得组件的属性信息。从而实现了可以取得ActiveX组件的属性信息。下面的图就是ActiveX控件TreeView在VS环境下显示的属性情况。
我们现在来完成前面所说的如何在PropertyGrid里显示公共字段,因为我们将在以后的揭密里再对它进行更有趣的应用。
我们来看一个简单的职员类
public class Employee
{
public string Name;
public bool Sex;
public DateTime Born;
}
另外还有一个用属性实现的类EmployeeByProperty以及一个用ICustomTypeDescriptor接口实现的类EmployeeByICustomTypeDescriptor,它实际是继承于FieldToPropertyTypeDescriptor这个虚继类,由它来实现接口。另外还提供了一个可以动态包装Employee,可以使它同样达到EmployeeByICustomTypeDescriptor的包装代理类FieldToPropertyProxyTypeDescriptor,两个黑体显示的类,稍作一些修改,可以相当的有用。当然还有一个类FieldPropertyDescriptor也是必需要有的,它继承于PropertyDescriptor,用于描述具体的某个属性信息。
需要稍作说明的是FieldToPropertyTypeDescriptor这个类,当TypeDescriptor利用它返回属性集合时,首先它会用TypeDescriptor.GetProperties取得默认的元数据,然后再将每个字段信息转换成FieldPropertyDescriptor,然后加入属性集合里。下面便是这一小段代码的核心:
props = new PropertyDescriptorCollection(null);
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(this, attributes, true))
{
props.Add(prop);
}
foreach (FieldInfo field in GetType().GetFields())
{
FieldPropertyDescriptor fieldDesc = new FieldPropertyDescriptor(field);
if (!filtering || fieldDesc.Attributes.Contains(attributes)) props.Add(fieldDesc);
}
好了,其它还有疑问的地方我们可以通过MSDN进行查找。不过顺便说一句,这种做法并不是在2.0里的推荐做法,我们会在下一篇看到2.0里的做法。
参考资料:MSDN Magazine关于ICustomTypeDescriptor的文章(http://msdn.microsoft.com/msdnmag/issues/05/04/NETMatters/),有兴趣大家可以看一看。