【转】Asp.net控件开发学习笔记整理篇

最近一直在做MVC项目,对于WEBFORM 好像快忘记了。周末无聊,顺带看看他人的笔记。再次温习下。

复习大纲:

导航、页面生命周期及其它导论

一、服务器控件生命周期

二、控件开发基础

三、Asp.net服务端状态管理

四、Asp.net客户端状态管理

五、数据回传

六、WebControl基类

七、服务器控件事件

事件和委托之间的暧昧关系往往是大多Web Developer在学习.net中的一个里程碑,当明白事件和委托的关系后,.net水平往往就上了一个新的台阶。

下面说到服务器控件的事件模型.

在任何一个服务器编程开发框架中,事件都是解耦功能和具体实现的一剂良方,Asp.net当然也不例外。比如说吧,页面上的button的click事件表示它的功能,而具体的实现将会被分离交给Developer来进行具体实现。

传统的编程模型和基于事件的编程模型可以用下图进行简约概括:

   
 

我们可以看出事件极大的简化了编程工作,客户端程序只需要注册到事件并且和事件的签名保持一致(即参数个数和类型相同)即可。在事件发生后,客户端程序会被通知并执行相应实现(.net framework的事件正是观察者模式的最好例子:-)

Asp.net通过ViewStateHttp Post协议巧妙的实现了让开发人员感觉貌似控件能像WinForm程序中那样记住自己的状态。这使Asp.net可以在不使用客户端javascript的情况下,而实现数据回传。

    

    上面的图例展示出TextBox通过暴露相应的事件来通知被注册的函数.还记得前面所说的IPostDataHandler接口嘛,大多数服务器控件的事件都是通过ViewState来将数据以Http Post协议传回服务器,服务器根据回传数据的不同来引发相应事件。因此我们可以看出Button控件生成的<input type=”submit” />是有往服务器提交的功能的,而其他控件比如DropDownList或者是Checkbox是没有像服务器提交的功能。因此引发服务器事件便无从谈起。Asp.net通过在客户端设置javascript事件来引发向服务器的Http Post回传。而这一切仅仅需要将AutoPostBack属性设置为true.

  .net FrameWork 事件模型

   .net framework提供了基于委托的使不同类之间进行异步交互的机制。下面先简单说一下事件的核心-------委托

Delegate

委托在一定程度上有点像接口,是在发布者和订阅者之间的一个协议。接口是制订类的数据成员以及成员函数的签名。而委托,是制订单个函数的签名.

创建一个委托的实例是通过创建一个和委托相匹配的函数。

MSDN里把委托比喻成类型安全的函数指针。但是委托不仅限于此,因为.net framework大大的扩展了这个“类型安全的指针”,在CLR via C#这本书里说委托的实质上是一个类。因此委托可以按照次序依次调用多个匹配的方法,无论是静态方法还是实例方法。

委托有两个部分,委托的声明和委托实例的声明。

委托的声明代码会像:

public delegate void foo(string A);

而委托实例的声明会像

foo f=new foo(MethodName)

下面通过一个简单的Demo说明一下:

Demo 委托

    先写一个简单的类:  

public class DelegateDemo

    {

        public delegate void DeleDemo(string a);

        public static void FunctionA(string a)

        {

            HttpContext.Current.Response.Write("静态方法,传入的参数是:" + a + "<br />");

        }

        public void FunctionB(string a)

        {

            HttpContext.Current.Response.Write("实例方法,传入的参数是:" + a + "<br />");

        }

}

下面是客户端代码:        

DelegateDemo dd = new DelegateDemo();

DelegateDemo.DeleDemo d = new DelegateDemo.DeleDemo(dd.FunctionB);

d += DelegateDemo.FunctionA;

d("参数");

最后的输出结果为:

实例方法,传入的参数是:参数
静态方法,传入的参数是:参数

    通过上面小Demo可以发现委托不仅仅是“类型安全的指针”,并且委托是按照次序调用实例方法和静态方法.

事件

   C#里有专门用于声明事件的event关键字,一个典型的事件生命会像:

public event EventHandler click; 

事件关键字后面是委托,再后面是事件名称,命名事件的名称最好是动词,表名某些事情发生了。比如click,Init,Load,TextChanged这样。由此可以看出事件其实就是特殊的委托。因为所有的事件都是继承于System.EventHandler.

EventHandler 委托

所有asp.net内置控件事件处理函数的签名都和EventHandler或者继承于它的子类保持一致。它的原型是:

[SerializableAttribute]

[ComVisibleAttribute(true)]

public delegate void EventHandler(Object sender,EventArgs e)

第一个参数表示引发事件的对象,第二个参数表示引发事件后所要传给处理程序的参数。

在一般情况下,开发人员最好是按照这种签名格式来声明函数事件的委托.

在控件内部声明事件后,你必须在需要的情况下引发事件,直接引发事件是非常不好的做法。而在asp.net预定义的控件中都使用了如下方法:

 声明一个virtual protected void的方法,命名方式为On+事件名称.下面是一个例子:

      

protected virtual void OnClick(EventArgs e)

        {

            if (Click != null)

                Click(this, e);

       }

这个方法首先做的是先检查客户端方法是否注册,如果有客户端方法进行了注册,则引发事件。

EventCollection

如果在单个控件中有多个事件,那么使用System.ComponentModel.EventHandlerList对事件进行保存将会在内存占用上有不错的提高。EventHandlerList对一个类内发布多个事件提供了一个列表容器。下面是多个事件和使用EventHandlerList的对比示意:

  

第一步是实例化一个EventHandlerList的实例:

protected EventHandlerList eventList = new EventHandlerList();

第二步是声明一个容器用于保存事件的key

private static readonly object ClickEvent = new object();

最后一步是像往常一样声明一个事件,但有所不同的是就像属性的get和set程序块一样,对于事件C#提供了add和remove关键字:

public event EventHandler Click

        {

            add

            {

                Events.AddHandler(ClickEvent, value);

            }

            remove

            {

                Events.RemoveHandler(ClickEvent, value);

            }

      }

而在这时的事件调用方法就会像下面代码:

protected virtual void OnClick(EventArgs e)

        {

            EventHandler clickEventDelegate = (EventHandler)Events[ClickEvent];

            if (clickEventDelegate != null)

             {

                clickEventDelegate(this, e);

            }

     }

上面代码首先从事件列表中通过索引器以第一步中保存事件的key为参数提取出事件并检查客户端是否注册到此事件,如果是,则激发事件。

 Command事件和事件冒泡

    Command事件是System.Web.UI.WebControls命名空间里的强大模式。这个最好的例子是GridView


    

 在GridView的Row里嵌套的button点击会触发Command事件,后台可以根据CommandArgument的不同来决定是执行edit操作还是delete操作等。而事件冒泡有些像javascript里的事件冒泡,但有所不同的是这里的事件冒泡到能够处理这个事件的地方停止,比如上图中command事件会冒泡到DataGrid里的ItemCommand里停止,因为ItemCommand事件可以对command事件进行处理.

 在定义Command事件时会和前面大同小异,不同之处在于首先需要一个继承与System.EventArgs的CommandEventArgs类来进行参数传递,代码如下

public class CommandEventArgs : EventArgs
        {
            public CommandEventArgs(string _commandName, string _commandArgument)
            {
                CommandName = _commandName;
                CommandArgument = _commandArgument;
            }

            private string commandname;
            private string commandArgument;
            public virtual string CommandName
            {
                get
                {
                    return commandname;
                }
                set
                {
                    commandname = value;
                }
            }
            public virtual string CommandArgument
            {
                get
                {
                    return commandArgument;
                }
                set
                {
                    commandArgument = value;
                }
            }
        }

  然后在需要定义的控件里定义这两个属性,代码如下:

         

public virtual string CommandName
        {
            get
            {
                object name = ViewState["CommandName"];
                if (name == null)
                    return string.Empty;
                else
                    return (string)name;
            }
            set
            {
                ViewState["CommandName"] = value;
            }
        }
        public virtual string CommandArgument
        {
            get
            {
                object arg = ViewState["CommandArgument"];
                if (arg == null)
                    return string.Empty;
                else
                    return (string)arg;
            }
            set
            {
                ViewState["CommandArgument"] = value;
            }
        }

然后重复前面的步骤,在控件内部定义命令事件:

private static readonly object CommandKey = new object();

        public event CommandEventHandler Command
        {
            add
            {
                Events.AddHandler(CommandKey, value);
            }
            remove
            {
                Events.RemoveHandler(CommandKey, value);
            }
        }

最后一步和前面说的引发事件的OnXXX的实现都略有不同,这里在控件内部实现的代码如下:       

protected virtual void OnCommand(CommandEventArgs ce)
        {
            CommandEventHandler commandEventDelegate = (CommandEventHandler)Events[CommandKey];
            if (commandEventDelegate != null)
            {
                commandEventDelegate(this, ce);
            }
            RaiseBubbleEvent(this, ce);
        }

注意最后一句,RaiseBubbleEvent方法.这个方法可以将控件的事件传递到它的父容器上。

 到这里很多人都会好奇,那CommandName和CommandArgument两个参数是如何传入到CommandEventArgs里去的呢?

 其实是在引发事件时传入的,代码如下:

  OnCommand(new CommandEventArgs(CommandName, CommandArgument));

DEMO 带Command事件的Button

    其实这个Demo就是把上面的代码全部拼装起来,代码可能会有点长,代码如下:    

namespace DemoButton
{
    using System;

    [ToolboxData("<{0}:superbutton runat=server></{0}:superbutton>")]
    public class ButtonDemo : Control, IPostBackEventHandler
    {
        public delegate void CommandEventHandler(object sender, CommandEventArgs e);
        public virtual string Text
        {
            get
            {
                object text = ViewState["Text"];
                if (text == null)
                    return string.Empty;
                else
                    return (string)text;
            }
            set
            {
                ViewState["Text"] = value;
            }
        }
        private static readonly object ClickKey = new object();
        public event EventHandler Click
        {
            add
            {
                Events.AddHandler(ClickKey, value);
            }
            remove
            {
                Events.RemoveHandler(ClickKey, value);
            }
        }
        protected virtual void OnClick(EventArgs e)
        {
            EventHandler clickEventDelegate = (EventHandler)Events[ClickKey];
            if (clickEventDelegate != null)
            {
                clickEventDelegate(this, e);
            }
        }
        private static readonly object CommandKey = new object();
        public event CommandEventHandler Command
        {
            add
            {
                Events.AddHandler(CommandKey, value);
            }
            remove
            {
                Events.RemoveHandler(CommandKey, value);
            }
        }
        public virtual string CommandName
        {
            get
            {
                object name = ViewState["CommandName"];
                if (name == null)
                    return string.Empty;
                else
                    return (string)name;
            }
            set
            {
                ViewState["CommandName"] = value;
            }
        }
        public virtual string CommandArgument
        {
            get
            {
                object arg = ViewState["CommandArgument"];
                if (arg == null)
                    return string.Empty;
                else
                    return (string)arg;
            }
            set
            {
                ViewState["CommandArgument"] = value;
            }
        }
        protected virtual void OnCommand(CommandEventArgs ce)
        {
            CommandEventHandler commandEventDelegate = (CommandEventHandler)Events[CommandKey];
            if (commandEventDelegate != null)
            {
                commandEventDelegate(this, ce);
            }
            RaiseBubbleEvent(this, ce);
        }

        public void RaisePostBackEvent(string argument)
        {
            OnCommand(new CommandEventArgs(CommandName, CommandArgument));
            //OnClick(EventArgs.Empty);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            base.Render(writer);
            Page.VerifyRenderingInServerForm(this);
            writer.Write("<INPUT type=""submit""");
            writer.Write(" name=""" + this.UniqueID + """");
            writer.Write(" id=""" + this.UniqueID + """");
            writer.Write(" value=""" + Text + """");
            writer.Write(" />");
        }
    }

    public class CommandEventArgs : EventArgs
    {
        public CommandEventArgs(string _commandName, string _commandArgument)
        {
            CommandName = _commandName;
            CommandArgument = _commandArgument;
        }
        private string commandname;
        private string commandArgument;
        public virtual string CommandName
        {
            get
            {
                return commandname;
            }
            set
            {
                commandname = value;
            }
        }
        public virtual string CommandArgument
        {
            get
            {
                return commandArgument;
            }
            set
            {
                commandArgument = value;
            }
        }
    }
}

  

前台代码:

首先注册页面控件:

 <%@ Register Namespace="DemoButton" TagPrefix="cc" %>

前台代码:

   <cc:ButtonDemo runat="server" Text="第一个按钮" ID="bt1" CommandName="bt1"

        CommandArgument="第一个button的参数" oncommand="bt1_Command" ></cc:ButtonDemo>

      

 <cc:ButtonDemo runat="server" Text="第二个按钮" ID="bt2" CommandName="bt1"

        CommandArgument="第二个button的参数" oncommand="bt1_Command" ></cc:ButtonDemo>

事件处理程序:

    protected void bt1_Command(object sender, DemoButton.CommandEventArgs e)

    {

        if (e.CommandName == "bt1")

        {

            Response.Write("第一个button被点击了,参数是"+e.CommandArgument);

        }

        else if (e.CommandArgument == "bt12")

        {

            Response.Write("第二个button被点击了,参数是" + e.CommandArgument);

        }

}

Demo的结果很简单,就不演示了:-)

原文地址:https://www.cnblogs.com/taoqianbao/p/3486267.html