ASP.NET提供了User Control技术来简化一些应用. 总的来说, User Control与ASPX即(Web Form)没有什么不同. 但ASCX可以做为一个模块嵌入到ASPX页中. 所以我们可以将应用中的业务操作尽量封装到ASCX中.从而为某一项具体的功能设计一个密封的应用程序“块“. 这样再次设计相同操作的应用是可以直接在页面上引入该“块“.
什么是自定义事件?
事件是消息驱动的编程模型的“消息“. ASP.NET框架实现了类似WINDOWS平台的消息机制. 即将用户对浏览器中页面的操作简化为服务器端的消息. (这一过程是通过Post Back Data实现的), 对象通过向容器报告消息来维持程序运行. 消息中一般可包含数据. 即Event Arguments. 比如页面上有一个服务器端按钮(asp:Button), 它可以向其容器报告Click消息. 而该消息是通过一定的数据回发机制实现的.
事件与类设计
为 控件开发事件目的是降低类型之间的依赖性. 明白地讲, 如果为对象实现了事件, 我们只要在一定时刻将事件引发就可以了. 而不用了解容器的逻辑. (一般而言, 容器内的对象无法了解容器的接口, 这个时候如果想完成某操作, 就得调用容器的方法). 容器捕获该事件时, 同时取得了事件的数据.
事件可以使程序结构更灵活
这 个时候有的朋友可能会问. 为什么要给ASCX实现事件? 这就要从ASCX的设计谈起. 给ASCX设计事件的目的是提高程序的模块化. 前面我提到ASCX将作为某具体操作的模型, 例如“显示人员的名单(数据集列表)“就是很具体的操作. 但“显示人员名单“时一般我们会想看看某个具体人员的详细资料. 这个过程必然是鼠标点击人员的姓名, 或者每行都有的一个按钮, 此时看看我们的程序如何处理. 在ASP的时代. 我们自然会想到在每一行取得改行的主键标识, 然后在构造一个URL. 让URL带上参数, 让URL指向的页面处理这个过程. 这样的过程我们在ASP.NET框架下依旧可以实现. 但是我们现在是在ASP.NET时代. 上面这一个过程框架已经为我们隐藏地很好. 这个时候我们应该将这个过程归纳为事件. 而不用管容器如何处理.
任何类都可以实现事件
在 谈如何实现之前. 先简单描述一下ASP.NET的消息机制. 下面这段话见于MSDN中文版: [在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机 制)。.NET Framework 定义了一个特殊的类型(Delegate),该类型提供函数指针的功能。] 任何事件都是一个Delegetelel类型. 需要如下声明: (C#)
public event PageChangeEventHandler PageChange;
其中PageChangeEventHandler是一个Delegate. 需要在类中如下声明:
public delegate void PageChangeEventHandler(object sender, PageChangeEventArgs e);
其中PageChangeEventArgs是一个继承自System.EventArgs的类. 其结构是该事件应该包含的数据.
public class PageChangeEventArgs: System.EventArgs
{
private int pageIndex;
public int PageIndex
{
get
{
return this.pageIndex;
}
set
{
this.pageIndex = value;
}
}
}
以上定义之后, 我们就可以在类中引发事件.
ClassName.PageChange(this, e)
难点在于
对于一般的应用. 我们都可以设计类来引发不同类型的事件. 但是现在问题是引发ASCX中的事件. 现在问题是:
1. 事件是用户在前端的操作引起的. 我们如何了解用户何时做了何种操作?
2. 如何封装事件的数据?
后端必须了解前端的操作. 也就是说, 用户在前端单击了某个链接或者按钮, 后端必须马上得到该消息. 这就需要及时向后端发送数据. 而发送数据必须通过post方式发送到后端. 这一过程框架本来是对程序员隐藏的. 而我们必须揭开其中的机制.
我 们知道ASP.NET页中仅有一个ACTION为自身的FORM. 所有的数据都是通过该FORM发送到后端的. 页面的每次发回都会向后端发送数据, 而不论引起页面回发的对象是什么. 那么就要想办法让后端了解到应该处理发回的数据. 也就是说应该让服务器端控件具有这样一种能力, 可以处理自己在前台发送的数据. 在ASP.NET框架中, POST数据的处理是通过这样一种方式实现的: 每次页面发回, 框架都会检查前台发送到后端的数据(POST数据), 与当前页面上的服务器控件检查, 如果发现与某控件的UniqueID相同, 而且该控件实现了IPostBackDataHandler接口, 那么就调用由该控件实现的IPostBackDataHandler定义的方法LoadPostData. 如果设计控件可以处理发送到后端的事件, 就必须实现IPostBackHandler接口. 关于IPostBackDataHandler接口和回发数据处理, 见MSDN文档:
ms-help://MS.MSDNQTR.2003FEB.2052/cpref/html/frlrfsystemwebuiipostbackdatahandlerclasstopic.htm
实现技巧
现 在我们明白了我们必须是控件的前端HTML可以发送特定的数据到后端, 而后端必须知道如何处理这一事件而且在处理过程中将数据封装到EventArgs中. 但是问题是, 我们要处理的是一个ASCX, 其前端构图是通过编写HTML实现的, 向后端发送数据应该不难办到. 通过一个隐藏的文本框input type=text就可以办到. 但是如何使数据的发送方的Name是当前ASCX的UniqueID呢. 每一个控件都有一个UniqueID的服务器端属性, 而且该UniqueID的命名方式决定了该ID在当前的页面是唯一的. 并且发送到客户端之前框架才可以确定该值. 所以ASCX的设计时期开发人员是无法得到该值的. 怎么办?
在设计时无法创建满足要求的控件, 就只有在运行时期动态生成. 查找System.Web.UI.UserControl的API文档, 我们发现该类提供了Controls集合. 而该集合有Add方法可以在运行时期向其内部增加WebControl类型的对象. 办法就有了. 在运行时期创建一个Input对象, 并且设置各个Attribute. 就圆满完成了需求. 需要注意的是, 控件构建时会自动增加name特性, 我们需要显式地清除该特性并且新增加多个特性, 具体代码可以如下:
System.Web.UI.WebControls.WebControl box = new WebControl(System.Web.UI.HtmlTextWriterTag.Input);
box.Attributes.Remove("name");
box.Attributes.Add("type", "text");
box.Attributes.Add("name", this.UniqueID);
box.Attributes.Add("id", "icq116987221_hidden");
box.Attributes.Add("style", "display:none");
this.Controls.Add(box);
在前端生成的目标HTML代码如下:
<input type="text" name="ResultList" id="icq116987221_hidden" style="display:none" />
(其中id特性应设置便于识别和记忆的名称以便前台脚本调用, 另外应注意重复)
这 样在该控件内就存在一个name特性为自己的UniqueID的控件, 可以向后端发送POST数据了. 只要这个不可见的element有值, 前端需要编写浏览器脚本处理由用户操作传送数据到后端的过程. 后端就可以捕获到, 并且调用该类实现的LoadPostBackData方法. 我们只要在这个方法中引发自定义的事件就可以了.
以下是类代码中有关事件和POST数据处理部分(非完整代码):
处理POST数据:
public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
{
string source = postCollection[postDataKey].Trim();
......
XxxCommandEventArgs e = new XxxCommandEventArgs();
e.CommandName = ...;
e.Id = ...;
this.RaiseCommandEvent(e);
}
return false;
}
事件定义:
/// <summary>
/// 自定义事件委托
/// </summary>
public delegate void ExpertListCommandEventHandler(object sender, Professor.UserControls.ExpertListCommandEventArgs e);
/// <summary>
/// 定义事件
/// </summary>
public event ExpertListCommandEventHandler Command;
/// <summary>
/// 引发事件的方法
/// </summary>
public void RaiseCommandEvent(Professor.UserControls.ExpertListCommandEventArgs e)
{
this.Command(this, e);
}
事件特性(参数)类:必须继承自EventArgs. 可包含多种类型数据.
public class CommandEventArgs: System.EventArgs
{
private string commandName;
/// <summary>
/// 命令名称.
/// </summary>
public string CommandName
{
get
{
return this.commandName;
}
set
{
this.commandName = value;
}
}
private int id;
/// <summary>
/// 操作对象: 被操作的专家ID数列
/// </summary>
public int Id
{
get
{
return this.id;
}
set
{
this.id = value;
}
}
}