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

nbubbleevent和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。
小结
本文重点介绍了复合控件的事件实现方法,并通过典型示例说明了这些实现方法的具体应用。总体而言,为复合控件实现事件并不是特别困难的事情。关键是开发人员必须在领会为普通控件实现事件的基础之上,掌握包含法和冒泡法的实现要点。