【转】Asp.net控件开发学习笔记整理篇 - 服务器控件事件
2013-12-22 19:00 朱峰(Peter.zhu) 阅读(326) 评论(0) 编辑 收藏 举报最近一直在做MVC项目,对于WEBFORM 好像快忘记了。周末无聊,顺带看看他人的笔记。再次温习下。
复习大纲:
事件和委托之间的暧昧关系往往是大多Web Developer在学习.net中的一个里程碑,当明白事件和委托的关系后,.net水平往往就上了一个新的台阶。
下面说到服务器控件的事件模型.
在任何一个服务器编程开发框架中,事件都是解耦功能和具体实现的一剂良方,Asp.net当然也不例外。比如说吧,页面上的button的click事件表示它的功能,而具体的实现将会被分离交给Developer来进行具体实现。
传统的编程模型和基于事件的编程模型可以用下图进行简约概括:
我们可以看出事件极大的简化了编程工作,客户端程序只需要注册到事件并且和事件的签名保持一致(即参数个数和类型相同)即可。在事件发生后,客户端程序会被通知并执行相应实现(.net framework的事件正是观察者模式的最好例子:-))
Asp.net通过ViewState和Http 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的结果很简单,就不演示了:-)