控件生命周期是一个合格控件开发者必须掌握的概念。生命周期是按时间,即控件生成过程的先后阶段定义的。在每个阶段要完成控件生成所必须的特定功能。
一般控件生命周期分为11个阶段,但也不是绝对的。例如,在ASP.NET中比较特殊的一个控件-----页面控件System.Web.UI.Page的生命周期中有些阶段分的更细。举个例子来说,比如在一般控件中只有一个Init事件,而在Page控件中同一个事件分成了PreInit,Init,InitComplete三个阶段,当然这三个阶段完成的功能和一般控件的Init事件完成的功能是相似的。
把一个阶段分为三个阶段,与Page控件的页面生命周期的复杂性有关。Page 是所有控件的容器(载体),Page控件生成完成,就意味着它里面的所有子控件也已经生成完成,Page生成的过程也就是它的子控件生成的过程。除了这些,它还负责管理主题、母版等好多方方面面。事实上,一般说来在Page的PreInit事件要完成设置母版页和主题的属性,只有这次机会,一旦到了Init阶段,就不能再修改这些属性:Page的Init事件要依次触发各个子控件的Init事件方法,Init事件再仿效调用相应的OnInit方法,初始化各个子控件,并为各个子控件设置命名容器等。Page的InitComplete事件要使控件具有视图跟踪能力。
11个阶段:
1 初始化(对应OnInit方法)
2
在这个阶段主要完成控件的初始化,页面通过ProcessRequest方法来递归遍历它的子控件,使子控件依次调用它们的OnInit方法。我们可以重写控件的Oninit方法,来扩展控件功能或增加初始化内容。另外,在本阶段控件还要完成一个很重要的工作,即打开控件的视图状态跟踪功能,具体步骤是调用TrackViewState方法,这样存储在ViewState对象里面的值在页面回发时才能够恢复到控件属性中。
2. 加载视图状态(对应 LoadViewState方法)
本阶段仅在页面回发时才执行,实时上,在第一次访问页面时我们还没有或得存储到视图的状态数据。本阶段主要完成加载视图状态到控件的任务,前提是该控件启用了视图状态功能。
3. 加载回传数据(对应LoadPostData方法)
本阶段仅在页面回发时执行。LoadPostData是实现接口IPostBackDataHandler的一个方法,要想实现控件数据回传功能,必须要继承此接口。
其中本方法的参数有一个NameValueCollection类型的对象,装载了客户端提交的数据。另外,在本方法中隔绝控件旧值和新值的比较返回一个bool类型的值,还可以决定是否执行厦门的RaisePostDataChangedEvent方法。
IPostDataHandler
通过实现IPostDataHandler接口,服务器可以在不使用Page和Request对象的情况下来读取客户端回传数据。IPostDataHandler还提供了在用户状态改变的情况下来引发相应事件的框架。IPostDataHandler的定义如下:
public interface IPostBackDataHandler
{
public bool LoadPostData(string postDataKey,
NameValueCollection postCollection);
public void RaisePostDataChangedEvent();
}
对于LoadPostData函数,如果返回值为true,则会引发下面的RaisePostDataChangeEvent方法。这个方法中我们可以加入需要引发的事件,比如:
public virtual void RaisePostDataChangeEvent()
{
OnTextChanged(EventArgs.Empty);
}
所以可以这样来获得本控件这一次的回传数据:
postCollection[postDataKey]
当然也可以获得其他控件这一次的回传数据以达到和其它控件的交互,比如:
postCollection[3]
4. 装载 (对应 OnLoad方法)
程序员一般对本方法比较熟悉,因为对Page控件的Page_Load方法用得最频繁。这里要说的页面装载时先执行页面的Page_Load事件,再一次递归执行各个子控件的OnLoad方法。
5. 数据回传事件通知(对应 RaisePostDataChangedEnent 方法)
本阶段仅在页面回发时执行。RaisePostDataChangedEvent是实现接口IPostBackDataHandler的一个方法,要想实现控件数据回传事件功能,必须要继承这个接口。当LoadPostData方法返回值为True时,RaisePostDataChangedEvent方法才会被调用,也就是说此方法是可以用程序控制的。
第一、必须在控件呈现中将控件的name的属性值设置为UniqueID。这是由于发生回传后,页框架将在发送的内容中搜索与实现IPostBackDataHandler的服务器控件的UniqueID匹配的值,然后才能调用LoadPostData方法。 第二、控件类必须实现IPostBackDataHandler接口,并实现LoadPostData和RaisePostDataChangedEvent方法。LoadPostData方法用来检查提交给服务器的数据。该方法包含两个参数:postDataKey表示用于识别控件内数据的关键值,postData是提交数据的集合,其采用Key/Value结构便于使用索引名称访问。要访问集合中的控件数据,只要采用如下代码即可:"string nData = postData[postDataKey]; "。在LoadPostData方法中,通过新数据(客户端发送的数据值)与旧数据(先前提交给客户端的数据值)进行比较的结果来确定方法返回值。如果新旧数据相同,则说明数据没有被修改,方法返回值为false;如果新旧数据不同,则表明旧数据已经被客户端修改,方法返回值true。下面是LoadPostData方法的一个简单应用。
public virtual bool LoadPostData(string postDataKey,NameValueCollection postData){ string presentValue = Text; //旧数据 string postedValue = postData[postDataKey];//新数据 //检查新旧数据 if(presentValue.Equals(postedValue) || presentValue == null) { Text = postedValue; return true; } return false;} |
如果LoadPostData方法返回true,.NET框架将自动调用RaisePostDataChangedEvent方法。该方法用信号要求服务器控件对象通知ASP.NET应用程序该控件的状态已更改,控件开发者可以在该方法中定义根据数据变化引发的事件。下面是简单的调用OnTextChanged方法:
public virtual void RaisePostDataChangedEvent(){ OnTextChanged(EventArgs.Empty);} |
以上是处理回传数据的实现要点,掌握这些要点对于事件处理具有至关重要的意义。同时,其内容也说明了以下.NET框架处理回传数据的过程: (1)首先在发送的内容中搜索与实现IPostBackDataHandler的服务器控件的UniqueID匹配的值。 (2)调用LoadPostData方法,并返回bool值。 (3)如果LoadPostData方法返回true,那么调用RaisePostDataChangedEvent方法。 (4)执行RaisePostDataChangedEvent方法中定义的OnEvent方法。
6. 触发回发事件 (对应 RaisePostBackEvent方法)
本阶段仅在页面回发时执行,主要处理引起回发的客户端事件,成功捕获回发的客户端事件进行服务器端的相应处理。前提是要实现IPostBackEventHandler接口。还可以通过本方法的参数来判断是哪个控件触发的回发事件,进而执行不同的事件处理逻辑。
1. 实现捕获回传事件 如果服务器控件需要捕获来自客户端的回传事件,并想为该回传事件自定义服务器端事件处理逻辑,那么控件必须实现System.Web.UI.IPostBackEventHandler接口。下面列举了该接口定义。
public interface IPostBackEventHandler{ void RaisePostBackEvent(string eventArgument);} |
如上代码所示,IPostBackEventHandler接口仅包括一个成员方法RaisePostBackEvent。该方法使服务器控件能够处理将窗体发送到服务器时引发的事件,其参数eventArgument表示要传递到事件处理的可选事件参数。开发人员可以在RaisePostBackEvent方法中实现服务器控件回传过程中执行的逻辑。一般情况下,RaisePostBackEvent方法将引发一个或者多个服务器端事件。以下代码片段显示了在服务器上引发Click事件的RaisePostBackEvent实现。
public void RaisePostBackEvent(String eventArgument){ OnClick(EventArgs.Empty);} |
实现捕获回传事件并不是仅仅使服务器控件类实现IPostBackEventHandler接口,并实现该接口成员方法就可以的。开发人员还需要注意实现其他内容。下面列举了实现捕获回传事件过程中的三个要点。 第一,也是最重要的,即自定义服务器控件类必须实现IPostBackEventHandler接口,并实现该接口成员RaisePostBackEvent方法。这一过程在上文中已经进行了介绍。 第二,为控件分配UniqueID。 定义引起回传事件的控件的name属性值为UniqueID,是正确实现RaisePostBackEvent方法的关键之一。当引发回传后,页框架就会搜索发送的内容,并确定发送对象的名称是否与实现IPostBackEventHandler的服务器控件的UniqueID对应。如果对应,页框架就会在该控件上调用RaisePostBackEvent方法。这里的重点是需要开发人员在呈现逻辑中,为控件的name属性分配UniqueID。下面列举了一个简单的代码示例。
protected override void Render(HtmlTextWriter output){ output.Write("<INPUT TYPE=submit name="+this.UniqueID+"Value='Click Me' />");} |
如上代码所示,在控件呈现方法Render中,呈现了一个按钮,其name属性值为UniqueID。只有为引起回传的控件的name属性分配了UniqueID,才能够正确实现捕获回传事件。 第三,实现事件属性结构。 事件属性结构是一种优化的事件实现方式。在介绍之前,我们首先看看常见的控件事件实现方式。具体代码如下所示。
......public class WebCustomControl:WebControl,IPostBackEventHandler{ //声明Click事件委托 public event EventHandler Click; //实现RaisePostBackEvent方法 void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { OnClick(EventArgs.Empty); } //定义OnClick事件处理程序 protected virtual void OnClick(EventArgs e) { if(Click != null) { Click(this,e); } } ......} |
在以上代码中,包括了与事件定义相关的三个关键内容:一、定义Click事件委托;二、控件类实现了IPostBackEventHandler接口,其中当实现接口成员方法RaisePostBackEvent过程中,定义了事件处理程序OnClick;三、实现OnClick事件处理程序。以上实现方法简单易用,然而却存在一个缺点,即执行效率低。尤其是在一个类中引发多个事件的情况下,将会增加开销,浪费大量服务器资源,最终导致运行效率降低。 为了解决以上问题,下面介绍一种优化的事件实现方式--事件属性结构。该结构使用System.ComponentModel.EventHandlerList类,这个类提供一个简单的委托列表。通过使用该类所提供的相关方法,开发人员能够灵活的操作控件的事件处理程序委托列表。例如,控件中的Click事件,使用事件属性结构如下:
protected static readonly object EventClick = new object();public event EventHandler Click{ add { Events.AddHandler(EventClick,value); } remove { Events.RemoveHandler(EventClick,value); }} |
在事件属性结构定义之前,首先需要定义Click事件委托对象。由于每个事件仅创建一次,因此,需要声明为静态和只读的。然后,在属性结构中通过AddHandler、RemoveHandler方法操作事件处理程序委托列表。当页面调用Click事件时,它向控件的EventHandlerList集合中添加或者删除处理程序。由于这种实现方法,在多个事件的声明过程中比普通的实现方法效率高,因此是非常值得推荐的方法。 另外,在OnClick方法的实现过程中,当用一个事件属性时,必须从EventHandlerList中取回委托,并将其转换成EventHandler的类型。
protected virtual void OnClick(EventArgs e){ EventHandler clickHandler = (EventHandler)Events[EventClick]; if(clickHandler != null) { clickHandler(this,e); }} |
7. 预呈现 (对应 OnPreRender方法)
在此阶段 主要完成控件呈现之前的所需要的一些工作,一般在控件开发中都会重写此方法,对控件进行资源注册,如注册Javascript脚本和隐藏域控件等。
8.保存视图状态
与上面讲的LoadViewState过程相反,SaveViewState是把页面控件视图信心进行存储。还有一点不同,SaveViewState在第一次页面请求就会执行这个操作(把页面控件的视图状态保存起来),而LoadViewState仅在页面回发时才执行(从视图状态中获取数据,初始化各个启用视图状态功能的控件)。
9. (对应 Render方法)
在这个阶段,主要将控件标记和字符文本输出到服务器控件输出流中。可以直接写HTML标记,也可以调用每个控件都有的RentControl方法到输出流。
10 卸载 (OnUnload方法)
11. 释放 (Dispose方法)