pursuedream
成功=水平+业务+沟通+判断
复合控件的事件处理简介

CompositeControl类基层自WebControl基类,并且实现INamingContainer和 ICompositeControlDesignerAccessor接口。INamingContainer是一个没有方法的标记接口。当控件在实现 INamingContainer时,页框架可在该控件下创建新的命名范围,因此,能够确保子控件在控件的分层树中具有唯一的名称。当复合控件公开模板属 性,提供数据绑定或需要传送事件到子控件时,这是非常重要的。ICompositeControlDesignerAccessor接口使复合控件设计器 可以在设计时重新创建其关联控件的子控件。该接口包含一个需要实现的方法RecreateChildControls。该方法使复合控件的设计器可以在设 计时重新创建该控件的子控件。

  另外,如果创建的是数据绑定复合控件,那么自定义控件类的基类应该是CompositeDataBoundControl。有关该类的具体内容,请读者查阅相关资料。

第二、必须重写Control基类的CreateChildControls方法,以便对子控件进行初始化、实例化,并将其添加到控件树中。 CreateChildControls用于通知使用基于合成实现的服务器控件,创建它们包含的任何子控件,以便为回发或呈现做准备。重写该方法是实现复 合控件的关键所在。这种类撰写的方法将通知.NET框架有关复合控件中包含哪些子控件,以及各个子控件在控件树中的位置和关系等内容。通过这种方法,复合 控件将复用子控件提供的实现来进行呈现、事件处理、样式及其他功能。

  在实现复合控件过程中,除了掌握CompositeControl基类和CreateChildControls方法之外,ASP.NET 2.0还提供了与复合控件相关的其他方法和属性,掌握这些成员对于开发复合控件也很重要。下面列举了这些常见方法和属性。

  · protected virtual void EnsureChildControls()

该方法用于确定服务器控件是否包含子控件。如果不包含,则创建子控件。该方法首先检查 ChildControlsCreated 属性的当前值。如果此值为假,则调用CreateChildControls方法。当需要确保已创建子控件时,将调用该方法。大多数情况下,自定义服务器 控件的开发人员无需重写此方法。如果确实重写了此方法,请按与其默认行为相似的方式来使用。

  · public virtual Control FindControl(string)

  该方法用于在当前的命名容器中搜索指定的服务器控件。

  · public virtual bool HasControls()

该方法用于确定服务器控件是否包含任何子控件。如果控件包含其他控件,则为true;否则为 false。由于该方法仅确定是否存在任何子控件,它可以通过允许您避免不必要的Controls.Count属性调用来改进性能。调用此属性要求实例化 ControlCollection对象。如果没有子级,则创建该对象会浪费服务器资源。

  · protected virtual void DataBindChildren ()

该方法是ASP.NET 2.0新增内容,其用于将数据源绑定到服务器控件的子控件。这为开发数据绑定类型的复合控件提供了便利。然而,需要注意的是,在服务器控件上调用此方法 时,此方法不会将数据绑定到控件。若要绑定服务器控件及其所有子控件,请调用DataBind方法。

  · protected bool HasEvents ()

  这也是一个ASP.NET 2.0新增的方法,其用于返回一个值,该值指示是否为控件或任何子控件注册事件。如果注册事件,则为true;否则为false。

  · Controls属性

  该属性的数据类型为ControlCollection,其用于获取ControlCollection对象,该对象表示 UI 层次结构中指定服务器控件的子控件。其属性值指定服务器控件的子控件集合。

  · NamingContainer属性

  该属性的数据类型为Control,其用于获取对服务器控件的命名容器的引用,此引用创建唯一的命名空间,以区分具有相同Control.ID属性值的服务器控件。

  · ChildControlsCreated属性

  该属性的数据类型为bool,其用于获取一个值,该值指示是否已创建服务器控件的子控件。如果已创建子控件则为true;否则为false。

  实现控件事件 的核心主要是定义事件属性结构和事件处理程序等。然而,这些内容是构建所有自定义服务器控件的基础。仅仅依靠这些方法是无法实现复合控件的事件的。因为, 复合控件中包含子控件,这就使得复合控件的事件处理变得复杂起来。显而易见,在复合控件的事件实现过程中,需要面临的最大问题是:由于不允许开发人员直接 访问子控件(虽然通过Controls集合访问的方法可以实现,但是破坏了程序的封装性,因此是不被允许的),如果子控件的事件不能作为顶级事件引发,那 么将无法实现子控件的事件处理。简单的说,即如何实现子控件的事件上传。所谓事件上传是指把子控件的事件暴露为顶级事件,这样父控件可以检查到事件,并按 照定义来执行相关事件处理程序。

  由以上内容可知,复合控件的事件处理,主要是实现子控件事件上传的过程。下面将介绍两种常用的事件上传实现方法:包含法和冒泡法。这两种方法实现机理不同,然而,完成了同样的功能。在下文中,我们将通过理论结合示例的方法展开讲解。

  包含法

  包含法的核心是,通过在子控件的事件处理程序中调用复合控件的顶层事件处理程序,以完成子控件的事件上传。在执行过程中,当引发子控件事件后,子控件的事件处理程序将自动调用相关顶层事件处理程序。

  包含法的关键步骤如下:

  · 在CreateChildControls方法中,为子控件添加事件处理程序。

  · 定义顶层事件及其事件处理程序OnEventName。

  · 在子控件的事件处理程序中调用OnEventName。

  · 定义事件属性结构。

  由以上内容可知,包含法的步骤与前面文章中介绍的实现控件的方法基本类似。关键是多出了一个在CreateChildControls方法中,为子控件添加事件处理程序的步骤。为了读者能够更加清晰的理解包含法,下文列举了一个利用包含法为复合控件实现事件的示例。

  首先,利用上一篇文章中介绍的复合控件呈现方法,创建一个由文本框和按钮组成的复合控件,然后,使用上文所述的包含法,将按钮的Click事件上传为顶层事件Submit。下面列举了该控件的源代码。
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace WebControlLibrary{
 
public class CompositeEvent : CompositeControl {
  
//声明变量
  private Button _button;
  
private TextBox _textBox;
  
private static readonly object EventSubmitKey = new object();
  
//定义属性ButtonText,用于指定按钮上的文字
  [
   Bindable(
true), Category("Appearance"), DefaultValue(""), Description("获取或设置显示显示在按钮上的文字")
  ]
  
public string ButtonText {
   
get {
    EnsureChildControls();
    
return _button.Text;
   }
   
set {
    EnsureChildControls();
    _button.Text 
= value;
   }
  }
  
//定义属性Text,表示文本框的输入

  [
   Bindable(
true), Category("Appearance"), DefaultValue(""), Description("获取或设置文本框输入文本")
  ]
  
public string Text {
   
get {
    EnsureChildControls();
    
return _textBox.Text;
   }
   
set {
    EnsureChildControls();
    _textBox.Text 
= value;
   }
  }
  
// 实现事件属性结构
  public event EventHandler Submit {
   add {
    Events.AddHandler(EventSubmitKey, value);
   }
   remove {
    Events.RemoveHandler(EventSubmitKey, value);
   }
  }
  
// 实现OnSubmit
  protected virtual void OnSubmit(EventArgs e) {
   EventHandler SubmitHandler 
= (EventHandler)Events[EventSubmitKey];
   
if (SubmitHandler != null) {
    SubmitHandler(
this, e);
   }
  }
  
// 实现Submit事件引发的事件处理程序
  private void _button_Click(Object source, EventArgs e) {
   OnSubmit(EventArgs.Empty);
  }
  
// 重写ICompositeControlDesignerAccessor接口的RecreateChildContrls方法
  protected override void RecreateChildControls() {
   EnsureChildControls();
  }

  
//重写CreateChildControls方法,将子控件添加到复合控件中

  
protected override void CreateChildControls() {
   Controls.Clear();
   _button 
= new Button();
   _textBox 
= new TextBox();
   _button.ID 
= "btn";
   _button.Click 
+= new EventHandler(_button_Click);
   
this.Controls.Add(_button);
   
this.Controls.Add(_textBox);
  }

  
//重写Render方法,呈现控件中其他的HTML代码

  
protected override void Render(HtmlTextWriter output) {
   output.AddAttribute(HtmlTextWriterAttribute.Border, 
"0px");
   output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, 
"5px");  
   output.AddAttribute(HtmlTextWriterAttribute.Cellspacing, 
"0px");  
   output.RenderBeginTag(HtmlTextWriterTag.Table);
   output.RenderBeginTag(HtmlTextWriterTag.Tr);   
   output.RenderBeginTag(HtmlTextWriterTag.Td);
   _textBox.RenderControl(output);
   output.RenderEndTag();
   output.RenderBeginTag(HtmlTextWriterTag.Td);
   _button.RenderControl(output);
   output.RenderEndTag();
   output.RenderEndTag();
   output.RenderEndTag();
  }
 }
}

 如上代码所示,复合控件CompositeEvent中包含两个属性:Text和ButtonText。前者用于获取或者设置文本框中的文本内容,后者用于获取或者设置按钮的显示文本。另外,复合控件类中还实现了一个Submit事件。相关重要逻辑包括:

  第一、在重写CreateChildControls方法中,为子控件Button添加事件处理程序_button_Click。

  第二、和普通的自定义事件一样,为复合控件定义一个顶层事件Submit。这其中包括定义事件属性结构Submit,定义事件处理程序OnSubmit。

  第三、实现_button_Click事件处理程序,调用顶层事件Submit的事件处理程序OnSubmit。
在以上应用中,当用户单击"提交"按钮之后,将引发demo1_Submit处理程序的执行,由此显示文本框输入内容。

  需要读者注意 的是其内部执行过程。控件定义的顶层事件是Submit,其对应事件处理程序是OnSubmit,而不是_button_Click。 _button_Click是复合控件的子控件的Click事件处理程序。由于在控件实现的代码中定义了子控件的事件处理程序 _button_Click,所以,当用户单击按钮后,将首先执行_button_Click,该方法要求调用顶层事件Submit的事件处理程序 OnSubmit。从外部来看,子控件的事件即暴露为顶层事件。

  从以上实现过程来看,包含法使用的是程序代码上的小技巧来实现事件上传功能。下面介绍的冒泡法则与此不同,它使用.NET框架提供的事件上传机制来完成子控件的事件上传。

冒泡法

  冒泡法也称"事件冒泡",其核心是使用ASP.NET 2.0框架提供的事件上传机制。这种机制允许子控件将事件沿其包容层次结构向上传播到合适的位置引发,并且允许将事件处理程序附加到原始控件以及公开冒泡的事件的控件上。

  冒泡法的实现,使用Control基类中专门用于事件上传的两个方法:OnBubbleEvent和RaiseBubbleEvent。它们的声明如下所示。
// OnBubbleEvent方法定义
protected virtual bool OnBubbleEvent(object source,EventArgs args){ return false;}
// RaiseBubbleEvent方法定义
protected void RaiseBubbleEvent(object source,EventArgs args){
 Control currentTarget 
= _parent;
 
while(currentTarget != null) {
  
if(currentTarget.OnBubbleEvent(source,args) { return; }
  currentTarget 
= currentTarget.Parent;
 }
}
OnBubbleEvent方法用于确定子控件的事件是否沿复合控件层次结构向上传递。在该方法中,参数source表示事件源,参数args表示包含事 件数据的EventArgs对象。如果子控件的事件向上传递,则为true;否则为false。默认值为false。RaiseBubbleEvent方 法用于将所有事件源及其信息分配给控件的父级,并且不能被重写。尽管无法重写此方法,但创作的控件可以通过重写 OnBubbleEvent 方法处理或引发冒泡事件。

  复合控件的事件冒泡主要存在以下两种情况:

  情况一:控件停止事件冒泡并引发和/或处理 该事件。引发事件需要调用将事件调度给侦听器的方法。若要引发冒泡的事件,控件必须重写OnBubbleEvent以调用引发此冒泡的事件的 OnEventName方法。引发冒泡的事件的控件通常将冒泡的事件公开为顶级事件。以下代码引发一个冒泡的事件。

protected override bool OnBubbleEvent(object sender,EventArgs e){
 
bool handled = false;
 
if(e is CommandEventArgs) {
  CommandEventArgs ce 
= (CommandEventArgs)e;
  
if(ce.CommandName == "ButtonClick") {
   OnButtonClick(EventArgs.Empty);
   handled 
=true;
  }
 }
 
return handled;
}
情况二:控件进行一些处理并继续使事件冒泡。若要实现这一点,控件必须重写OnBubbleEvent,并从OnBubbleEvent调用RaiseBubbleEvent。以下代码在检查事件参数的类型后使事件冒泡。

protected override bool OnBubbleEvent(object sender,EventArgs e){
 
if(e is CommandEventArgs) {
  CommandEventArgs ce 
= (CommandEventArgs)e;
  RaiseBubbleEvent(
this,ce);
  
return true;
 }
 
return false;
}
为了使读者能够更好的理解冒泡法,下面利用冒泡法对上一小节示例进行了重新实现。控件类的源代码如下所示,其中没有改变的部分使用省略号表示。
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
namespace WebControlLibrary{
 
public class CompositeEvent : CompositeControl {
  
//声明变量
  private Button _button;
  
private TextBox _textBox;
  
private static readonly object EventSubmitKey = new object();
  
//定义属性ButtonText,用于指定按钮上的文字
  [
   Bindable(
true), Category("Appearance"), DefaultValue(""), Description("获取或设置显示显示在按钮上的文字")
  ]
  
public string ButtonText {  }
  
//定义属性Text,表示文本框的输入

  [
   Bindable(
true), Category("Appearance"), DefaultValue(""), Description("获取或设置文本框输入文本")
  ]

  
public string Text {  } // 实现事件属性结构
  public event EventHandler Submit
  {
   add {
    Events.AddHandler(EventSubmitKey, value);
   }
   remove {
    Events.RemoveHandler(EventSubmitKey, value);
   }
  }
  
// 实现OnSubmit

  
protected virtual void OnSubmit(EventArgs e) {
   EventHandler SubmitHandler 
= (EventHandler)Events[EventSubmitKey];
   
if (SubmitHandler != null) { SubmitHandler(this, e); }
  }
  
// 删除_button_Click
  
// 重写ICompositeControlDesignerAccessor接口的RecreateChildContrls方法
  protected override void RecreateChildControls() {  }
  
//重写CreateChildControls方法,将子控件添加到复合控件中
  protected override void CreateChildControls() {
   Controls.Clear();
   _button 
= new Button();
   _textBox 
= new TextBox();
   _button.ID 
= "btn";
   _button.CommandName 
= "Submit";
   
this.Controls.Add(_button);
   
this.Controls.Add(_textBox);
  }
  
// 重写OnBubbleEvent方法,执行事件冒泡
  protected override bool OnBubbleEvent(object source, EventArgs e) {
   
bool handled = false;
   
if (e is CommandEventArgs) {
    CommandEventArgs ce 
= (CommandEventArgs)e;
    
if (ce.CommandName == "Submit") {
     OnSubmit(EventArgs.Empty);
     handled 
= true;
    }
   }
   
return handled;
  }
  
//重写Render方法,呈现控件中其他的HTML代码
  protected override void Render(HtmlTextWriter output) {  }
 }
}
本例的CompositeEvent类与上小节中的CompositeEvent类实现了同一功能。就控件呈现方面,两个类没有任何差别,差别主要表现在 对于复合控件的事件实现方面。差别一:在本例的CreateChildControl方法中,为子控件_button设置了CommandName属性, 其属性值为Submit。差别二:删除了_button_Click事件处理程序。差别三:重写了Control基类的OnBubbleEvent方法, 检查事件参数是否是CommandEventArgs类的实例。如果是,使用事件参数的CommandName成员确定是否需要引发事件处理程序 OnSubmit,并返回true。

posted on 2007-02-08 16:11  pursuedream  阅读(263)  评论(0)    收藏  举报