zoukankan      html  css  js  c++  java
  • 复合控件和事件(4)——移花接木

    上一篇:复合控件和事件(3)——事件基础
    上一篇描述了如何创建一个自己的控件,一切看起来都更加地接近细节,写着写着我自己都觉得是不是应该换个标题,毕竟我们是讲复合控件。那么什么是复合控件呢?
    通俗地讲就是控件是以组合其他控件为基础的一种控件。更具体地讲就请看(ASP.NET 控件开发速成教程:生成复合控件)文章中的描述:

    顾名思义,复合控件是将多个其他控件聚集在某单一顶部和单一 API 下的控件。如果某个自定义控件由一个标签和一个文本框组成,就可以说该控件是一个复合控件。“复合”一词表明该控件本质上是由其他构成组件在运行时组合而成。复合控件所暴露的方法集和属性集通常(但不是必须)由构成组件的方法和属性提供,并加入一些新成员。复合控件也可以引发自定义事件,还可以处理并激起子控件所引起的事件。
    复合控件需要我们继承CompositeControl类(关于为什么不是WebControl等,请同样参考(ASP.NET 控件开发速成教程:生成复合控件))
        public class CompositeControl4 : CompositeControl
        
    {
    //CompositeControl4是我们自己的控件名
    //CompositeControl是指System.Web.UI.WebControls.CompoiteControl类
    }
    在第一篇复合控件与事件(1)——基础入门,组合也是一种封装和第二篇复合控件和事件(2)——属性,页面要回发,属性要保存中我们讨论了复合控件的属性,而这一篇我们将围绕事件话题进行展开。
    我们知道既然复合控件是通过组合其他控件进行的一种封装,那么我们在复合控件中实现原来一些控件的事件的时候我们是不是就应该把重点放在原来控件的事件上呢?(如果要有自己的原创事件,请参考本系列第三篇复合控件和事件(3)——事件基础 )因此我将这篇文章称作移花接木,希望能够引起更好的反响。
    以DropDownList为例来展示这一移花接木的招术。
    复合控件作为一种封装,有着自己对外对内的一些特性,对外(控件的使用者),复合控件要表现出它的属性、事件,因此它必须要有类似下面的句子:
    public event EventHandler EventName;

    对内(复合控件类的内部),要能够与子控件的一些属性和事件进行通信。
    如何进行通信就成了本文的重点,我们的方法就是移花接木:
    我们知道事件本身是需要订阅后才会在事件被触发的时候执行订阅的程序的。那么在复合控件的模型中,事件触发将会是什么过程呢?如果复合控件包含一个按钮,我们按下这个按钮的时候事件被触发了,如果是一个DropDownList,那么我们在下拉项选择发生改变的时候事件也触发了,如果是一个我们自己的事件,那么在原本事件应该触发的条件下事件也触发了。
    但是由于复合控件的子控件被我们给封装了,所以对外部来说,一切都是private的,我们在外部则无所适从了。我们定义了自己的事件,并希望我们的事件和子控件的事件能够异曲同工。我们暴露给外部的是我们自己定义的事件,而与所有事件一样,执行事件处理程序是需要事先订阅的,因此我们要给外部的是类似:

    this.CompositeControl4.EventName+= new EventHandler(CompositeControl4_××××EventHandler);
    protected void CompositeControl4_××××EventHandler(object sender , ×××EventArgs e)
    {

    }

    而事实上我们希望订阅的是子控件的相应事件。因此我们需要移花接木。
    因此问题的关键就是订阅事件。因此我们可以利用下面的方式进行订阅:

            public event EventHandler ControlTextChanged
            
    {
    //ControlTextChanged就是我们自己控件的事件,它将被转订阅给DropDownList控件的TextChanged。
                add
                
    {
                    EnsureChildControls();
                    
    this.ddl.TextChanged += value;
                }

                remove
                
    {
                    EnsureChildControls();
                    
    this.ddl.TextChanged -= value;
                }

            }

    至此,我们在使用完+=号后,事件就被订阅给了子控件的TextChanged事件,而TextChanged何时激发这样的问题就留给子控件自己去处理,而我们事实上已经将我们的事件订阅给它了,也就不用管它了。

    另外有一点需要说明的地方:
    根据运行时的需要,声明变量的过程不适合new一个对象,因为new出来之后就占内存了,但是用了移花接木这一招的时候,我们需要在事件转订阅的时候用到类似this.ddl.××××EventName的事件,而在类的内部,它必须保证是已经存在的对象,因此我们需要将其定义为类内部的全局对象。不知大家是否记得有个不成文的规定,就是在CreateChildControls()函数中new对象这一约定。而在现在这种情况似乎不适合。真的不适合么?之前我们在调用子控件属性的时候我们都用了EnsureChildControls();函数,它的真正意义就在于确保子控件的存在,子控件如果不存在则调用CreateChildControls();函数创建子控件对象。因此我们可以在CreateChildControls()中继续new我们的对象,而在类的开始(通常是这样)的时候只需要声明一下而无需实例化。这样可以更合理地利用内存,因为每次PostBack的时候我们的对象都会被重新生成,而如果我们用如上不成文的规定,我们就可以在PostBack的时候检测我们的控件是否已经存在而决定是否new一个新的对象,这有利于我们合理分配我们的宝贵的服务器资源。(因此我们在有用到我们子控件对象的时候,因为我们无法确保子控件是否已经存在,所以我们都需要显式调用EnsureChildControls()已确保我们的控件总是有对象存在的,否则将会意外地出现运行时错误。)【一种误会:多写那么多EnsureChildControls()是一种很没效率的事,事实上我们不应该以代码量为衡量的主要依据,而应该以实际上的效率为优先考虑。可以想象一个页面中有无数的控件,每次PostBack我们都需要重新去new它们,效率其实是不高的,做EnsureChildControls函数的代价应该会比较小(不然微软不会推荐这么做的,是吧?)至于代码的多少,如果仍然存在误会的话请参阅《Code complete 2(代码大全2)》中的相关说明】


    代码:
    CompositeControl4.cs

    CompositeControl4.aspx

    CompositeControl4.aspx.cs

    CompositeControl4.aspx.designer.cs



    (关于事件的一些其他做法由于与移花接木的标题不太符合因此我决定另起篇章进行描述,移花接木事实上已经解决了问题,因此会移花接木基本上就可以搞定复合控件的事件了。)

  • 相关阅读:
    图论
    数学
    P2222 外婆婆~
    P2083 找人
    P1215 [USACO1.4]母亲的牛奶 Mother's Milk
    New Rap
    P2298 Mzc和男家丁的游戏
    P2040 打开所有的灯
    P1135 奇怪的电梯
    UVA10474 Where is the Marble?
  • 原文地址:https://www.cnblogs.com/volnet/p/817682.html
Copyright © 2011-2022 走看看