Dotnet专业组件开发揭密

很多人会以为写组件是件容易的事,也许某些方面是这样的,比如你可以很简单地实现一个自定义的功能。但是写出来的组件好象跟专业厂家开发的组件程序是不是有些差距呢?那么如何写一个专业组件呢,写专业组件要掌握哪些知识呢,它们的内部机理是什么呢?如果你对此感兴趣的话,那么这个系列实在是很值得你来看一看的。

本篇是要介绍如何扩展一个已有的组件功能。比如用户提出来要在TextBox控件上实现一个取得焦点后实现背景色变成蓝色,前景色变成黄色的功能。如果按我们一般的想法,很简单,我只要写一个从TextBox继承下来的AutoColorTextBox类,然后重载OnGotFocus就好了。的确,这是一种好方法,在很多场景里。好,下面我们的客户又提出一个要求Combox也要是这样子,因为他们的录入人员眼睛不是很好,于是你又写了一个AutoColorCombox。这时候你的心情很好,正打算去晚饭犒赏一下自已辛苦的一天,不过你的ProjectManager打电话给你,说用户提出来ListBox也要改一改,还有原来的TextBox要加上只充许录入数字的功能。现在你想说什么,我猜是Fuck you!当你看着这么多乱七糟的扩展类是不是很头痛呢?嗯,MS也考虑到这个问题了,他们提出了一种新的方法或许可以减轻你的痛苦。

我们先来看一个我们常用到的组件,但也许并没有引起你的注意,它就是ToolTip,我们先来看它的效果,左边的图是没有在窗体上放ToolTip时,TextBox所显示的属性,右边就是放了之后的属性,很容易我们知道右边的图多了一个toolTip1上ToolTip属性,不过你想过其中的机制是什么?为什么可以动态地影响一个控件的属性呢?

如果学过设计模式,我们很容易想到Decorate(装饰模式)。实际上CLR就提供了一些辅助类可以让你实现这个目的。那么如何来创立象这样的功能呢?

A)创建一个实现IextenderProvider的类,它实际上只需要有一个CanExtend的函数,你判断是否可以扩展的类型就好了。

B)用ProviderPropertyAttribute修饰该类,它需要两个东东,一个是增加的属性名称,另一个是修饰的对象类型,比如我们要给TextBox增加一个取得焦点时的背景颜色功能,其属性为AutoBackColor。那么这个特性修饰符应该是[ProviderProperty(“AutoBackColor”,typeof(TextBox))]

C)创建两个方法,以GetXXX和SetXXX来命名,里面就是你的处理过程,其中XXX是你的扩展属性名,比如这个类就应该是GetAutoBackColor。这两个方法有两个功能,一方面是取得对应的属性值,另一方面是注册事件,我的例子中是注册的是MouseEnter和MouseLeave两个事件,这样当鼠标移到控件上时,背景色就会改变。因为每个控件可以设置不同的背景色,因此,你需要在类里包括一个哈希表,这样以保存每个控件不同的用户背景色。

D)如果你想要在工具箱上显示你的这个扩展功能控件,你还需要使这个类从Component继承,如果你从Control继承,会有一点问题,因为VS会认为是一个可显示的控件,当你拖到窗体上时,它不会显示在下面额外的一栏里。

E)还有一点小问题,如何将属性归类到“外观”这个归类里。也很简单,你只要在GetXXX里加上[Category(“Appearance”)]这一句就好了。

最后我们来看一下我写的代码的效果图

好了,也许你要问,这一切是如何实现的,背后的原理是什么,请等下一篇,我将告诉你MS是如何处理这一切的。

参考资 料:MSDN上的文章《Custom Provider Controls》(http://msdn.microsoft.com/msdnmag/issues/03/11/CuttingEdge/),虽然是英文的,不过蛮好懂,也就懒得翻了。

    文中源码请参考:http://files.cnblogs.com/mywebname/改变控件颜色的扩展控件.rar

 

 

 

 

Dotnet专业组件开发揭密(二)

--TypeDescriptor内部机制及其应用(上)


  本篇要讲述的是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/),有兴趣大家可以看一看。

        前面说明了如何利用ICustomTypeDescriptor来使PropertyGrid所看到的类属性动态变化,并且实现了一个将原来不能在PropertyGrid显示的类字段当作类属性显示出来的一个小程序。我们也讲到那是1.0里的做法,2.0里并不推荐使用,那么2.0里的方法应该是如何的。

首先我们来看1.0里的做法有些什么样的问题,首先是职责不清,不符合设计模式的观点,一个类既要实现它本有的功能,又要实现如何在PropertyGrid里实示,从软件设计的角度来看是相当不合理的(不过上一篇提出的代理类的方法倒是可以解决这个问题);其次是性能问题,为了实现这个目的,你的功能类要多出很多对象,从而提高性能,但是这样子做,无形中你的功能类负担太重了,而且更重要是因为每次需要通过反射和相关的处理来取得类信息,因此性能也不是很好。正是基于这两个原因,MS在2.0里提出了新的解决办法。

2.0里你还可以通地上一篇介绍的方法使用来达到你的目的,不过更好的是使用一个新类TypeDescriptionProvider以及一个辅助属性类TypeDescriptionProviderAttribute,它们可以解决上面的两个问题。

同样的你还是需要实现一个属性描述符类,我们借用上一篇的FieldPropertyDescriptor类,还需要实现一个用户自定的TypeDescriptor,2.0里你不需要从IcustomTypeDescirptor继承了,建议从CustomTypeDescriptor虚基类继承,你可以少写很多代码,我们实现了一个跟上一篇类似的FieldToPropertyTypeDescriptor类,但是它也有几点不同,首先它不再是一个虚基类,用户的功能类也不需要从此类继承,更多的,它象上一篇里的FieldToPropertyProxyTypeDescriptor这个类,最后你只要简单实现一个从TypeDescriptionProvider继承的类,它的作用就是返回一个提供者就可以了。

上面的阐述可能你会有点晕,我们还是来看看这张图,它显示了2.0里PropertyGrid如何来显示一个类的属性

下面的图是是DEMO代码的运行界面,它除了一个PropertyGrid外,还多了两个按钮,可以删除和增加提供者,同时你可以看到PropertyGrid的变化。

最后需要提醒大家一句的是,真正用到TypeDescriptionProvider的地方并不多,也只是在PropertyGrid里显示时用一下。很多控件的通用显示还是通过查看类是否实现了ICustomTypeDescriptor接口来决定显示类的信息,比如我们2.0里常用的DataGridView控件,它支持显示实现的ICustomTypeDescriptor接口的类实例。这个大家可以按上一篇介绍的方法自已试一试。

在下一篇里我们将通过Reflector来探索TypeDescriptor的内部机制。

 

参考文档:MSDN Magazine关于ICustomTypeDescriptor的文章(http://msdn.microsoft.com/msdnmag/issues/05/05/NETMatters/)。



     相关代码参考:http://files.cnblogs.com/mywebname/TypeDescriptionProvider.rar

 

 

 

 

Dotnet专业组件开发揭密(三)

--TypeDescriptor内部机制及其应用(下)

前面文章里我们TypeDescriptor是用来返回一个类的属性或事件描述符的,VS中使用的PropertyGrid就是通过它来取得一个类的属性的。而不是我们以为的是通过反射来做的。那么它的内部机制是如何的?

TypeDescriptor它会进行几个判别,一是查看类是否实现了ICustomTypeDescriptor接口,如果没实现,它会通过反射来取得属性,如果实现,则用接口来取得。具体原理图如下:

 

    二是它会查看这个类有没有相应的类信息描述提供者,它是通过一个内部哈希表,每个元素指向一个TypeDescriptionProvider。有意思的是这个内部哈希表是一个弱引用哈希表,它的内部名称叫(WeakHashTable),它会根据系统内存的情况来调整内存使用,它的键可以是具体的类实例,也可以是类本身。TypeDescriptor就是通过查看这个哈希表来取得对应的提供者,然后由提供者来对得属性。具体的原理图见下:

 

 

下面是TypeDescriptor的GetProperties方法其内部流程的伪代码(根据Reflector反汇编代码改成),大家可以仔细看看下面的注释,更加有利于了解TypeDescriptor的运作机制

public static PropertyDescriptorCollection GetProperties(object component,bool noCustomTypeDesc)

{

    ICollection c1; 

   

/*此行代码它有两个功能

第一步查看TypeDescriptor内部的一个的WeakHashtable表,它是一个哈希表,每个元素是一个TypeDescriptorNode的类实例,这个类继承于TypeDescriptorProvider。对于传入的component,会用它作哈希表的KEY来检测对应的TypeDescriptorProvider,如果传入的是Type类型,则先检测它是否一个Com接口类,如果是就取得默认的COM类的提供者,否则的话就用Type作为KEY来检测这个哈希表。

第二步如果此表登记有自定义的Provider,则利用这个Provider来取得一个用户自定义的CustomTypeDescriptor,否则提供一个由反射来取得类信息的默认的Descripor。

*/

ICustomTypeDescriptor descriptor = GetDescriptor(component, noCustomTypeDesc);

   

    //判断是否类本身实现了IcustomTypeDescriptor接口

if (component is ICustomTypeDescriptor)

    {

        //通过类型描述符取类的属性

c1 = descriptor.GetProperties() ;

        if (noCustomTypeDesc) //是否返回类本身自定义的属性

        {

            /*取扩展类型描述符

            这里同样有很复杂的实现,俺是转了半天才看懂,它大概的原理是在CLR内部实现了一个DefaultExtendTypeDesriptor的类,它同样是实现了ICustomTypeDescriptor接口的

*/

            ICustomTypeDescriptor exDescriptor = GetExtendDescriptor(component);

            if (extendedDescriptor != null)

            {

                /*

                这里CLR的实现很关键,这里就实现了如何使实现了IextenderProvider的类可以动态地给已有类加上属性。它实际上内部有一个叫ReflectTypeDescriptionProvider的提供者,GetProperties这个方法首先会查看这个类是否有Icomponent接口,如果它就找到Site,然后通过Site找到Container,也就是组件的逻辑容器,然后查看逻辑容器里的组件是否有实现IextenderProvider接口的,如果有就找出来,把它扩展的属性加到这个类上。

*/

                ICollection c2 = extendedDescriptor.GetProperties() ;

                //合并两种情况所取得的属性集合

                c1 = PipelineMerge(c1, c2, component);

            }

        }

    }

    else

    {

        //返回每个类的相关信息的Cache

        IDictionary cache = GetCache(component);

        c1 = descriptor.GetProperties();

        c1 = PipelineInitialize(1, c1, cache); //用新的属性信息来填充这个Cache

        ICustomTypeDescriptor descriptor3 = GetExtendedDescriptor(component);

        if (descriptor3 != null)

        {

            ICollection c3 = descriptor3.GetProperties();

            c1 = PipelineMerge(1, c1, c3, component, cache);

        }

    }

   

    return c1 as PropertyDescriptorCollection;

   

}

作为System.Component命名空间下最重要的类之一,TypeDescriptor其内部实现相当复杂,虽然VS本身使用相当它频繁,但是一般开发人员很少用到它,正因如此无论网上或是书本都少提及它。但如果你要理解VS环境很多奇怪的机制和现象,却不得不去了解它,这连续几篇文章均只是浅浅地探索了它的一部分功能和奥秘,更多的宝藏需要你自已深入挖掘。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值