主题
复合控件的事件机制
开篇语
实在抱歉,一开始的时候,我就应该想到,我把标题做得太大了,不是还有做WinForm
的朋友吗!其实,这里只讲Web
的事件,如果您的时间宝贵,您可以点击back
.
那会刚弄asp.net
的时候,就一直很纳闷,当点击一个按钮的时候,就会触发服务器事件,我就想当然的,在服务器事件的处理方法中,做我自己喜欢的事情。当时就想,TMD
,这个按钮的Click
事件到底定义在哪里了呢?一查看Button
类型的实现,发现原来是在服务器里定义的,不是明明有个event delegate Click
字眼吗?(其实不是,莫被我误导了:),当时我认为,如果要给按钮再加个“双击事件”,只需要在Button
类型的实现里,再加个event delegate DoubleClick
就可以了.
其实上面这些,都是一些错误的想法,误导大家很久了:)本来这里打算直接写复合控件的事件,但是感觉,突然一把,就直接写复合控的事件,感觉不知从何说起。还是从最简单控件讲起吧。如果您还不了解简单控件,请您回过头去看看系列的一二章.
因为最近,一直在看“道不远人”,所以有些例子,我干脆直接“拿来主义”,还记得下面这个漂亮的控件吗?在上一章节是通过复合控件做的,在这里,我们改用简单控件来做.这里,我们抛开一切,只讲实现简单控件事件的几种方法,后面完整的Demo会发给大家的.
第一种
图形一(咱们用Control来实现)

先看控件的事件定义

Code
static object _click = new object();

[Category("Action")]
public event EventHandler<NumericArgs> Click

{
add

{
Events.AddHandler(_click, value);
}
remove

{
Events.RemoveHandler(_click, value);
}
}
控件的事件实现(注意看蓝色的行)

Code

Render#region Render
protected override void Render(HtmlTextWriter writer)

{
//div
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative");
writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "0px");
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "1.2em");
writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "100px");
writer.AddStyleAttribute("border", "solid 1px blue");
writer.AddAttribute(HtmlTextWriterAttribute.Name,this.UniqueID);
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.Indent++;
//input
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "1em");
writer.AddStyleAttribute(HtmlTextWriterStyle.Width, "80px");
writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "0 2px");
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
writer.AddStyleAttribute("border", "0px");
writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Value.ToString());
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
//up
string upRef = Page.ClientScript.GetPostBackClientHyperlink(this, Step.ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Href, upRef);
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
writer.AddStyleAttribute("right", "0px");
writer.AddStyleAttribute("border", "solid 1px blue");
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "0.5em");
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.Indent++;
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "0px");
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative");
writer.AddStyleAttribute("top", "-3px");
writer.AddAttribute(HtmlTextWriterAttribute.Src, DesignMode?"up.gif":ResolveUrl("~/up.gif"));
writer.RenderBeginTag(HtmlTextWriterTag.Img);
writer.RenderEndTag();
writer.Indent--;
writer.RenderEndTag();
//down
string downRef = Page.ClientScript.GetPostBackClientHyperlink(this, (Step*-1).ToString());
writer.AddAttribute(HtmlTextWriterAttribute.Href, downRef);
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "absolute");
writer.AddStyleAttribute("right", "0px");
writer.AddStyleAttribute("border", "solid 1px blue");
writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "0.5em");
writer.AddStyleAttribute("bottom", "0px");
writer.RenderBeginTag(HtmlTextWriterTag.A);
writer.Indent++;
writer.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "0px");
writer.AddStyleAttribute(HtmlTextWriterStyle.Position, "relative");
writer.AddStyleAttribute("top", "-3px");
writer.AddAttribute(HtmlTextWriterAttribute.Src, DesignMode?"down.gif":ResolveUrl("~/down.gif"));
writer.RenderBeginTag(HtmlTextWriterTag.Img);
writer.RenderEndTag();
writer.Indent--;
writer.RenderEndTag();
writer.Indent--;
writer.RenderEndTag();
}

#endregion
其实挺简单的,向writer流,扔一行代码<a href="javascript:__doPostBack('NumericUpDown1','1')>
再看看控件是怎样来触发事件处理方法的

Code

IPostBackEventHandler 成员#region IPostBackEventHandler 成员

public void RaisePostBackEvent(string eventArgument)

{
NumericArgs args = new NumericArgs(eventArgument);
Value += args.AddValue;
if (Events[_click] != null)

{
(Events[_click] as EventHandler<NumericArgs>)(null, args);
}
}

#endregion
这里需要注意几个地方
1.NumericArgs类型,不过就是一事件数据封转类型,做一些数据类型或者格式的验证,没什么高深的东东.
2.记得我以前说过,处理回传数据事件的作用,就是用来监听数据是否更改的.
所以,本人觉得,不应该直接在RaisePostBackEvent事件中,加入Value += args.AddValue这样的代码,最好放到处理回传数据事件中去,现在将就点,暂时放这里:)
3.可以根据eventArgument参数的区别,来调用不同的事件处理方法,这里因为是传递的数据值,不太好说明这个现象,抱歉...
小结
没什么好说的,向writer流里扔行调用javascript:__doPostBack方法的代码就OK
第二种(其实最后查看源文件,效果都一样)

Code
protected override void CreateChildControls()

{
Controls.Clear();
_txt = new TextBox();
_txt.Text = "0";
Controls.Add(_txt);
_lbtnUp = new LinkButton();
_imgUp = new System.Web.UI.WebControls.Image();
_imgUp.Attributes["Style"] = "border-width:0px;position:relative;top:-3px;";
_imgUp.ImageUrl = DesignMode ? "up.gif" : ResolveUrl("~/up.gif");
_lbtnUp.Controls.Add(_imgUp);
_lbtnUp.CommandName = "Add";
_lbtnUp.CommandArgument = Step.ToString();
Controls.Add(_lbtnUp);
_lbtnDown = new LinkButton();
_lbtnDown.CommandName = "Reduce";
_lbtnDown.CommandArgument = (Step * -1).ToString();
_imgDown = new System.Web.UI.WebControls.Image();
_imgDown.Attributes["Style"] = "border-width:0px;position:relative;top:-3px;";
_imgDown.ImageUrl = DesignMode ? "down.gif" : ResolveUrl("~/down.gif");
_lbtnDown.Controls.Add(_imgDown);
Controls.Add(_lbtnDown);

}
小结
在这里就不多说了,还是直接去看复合控件的事件
复合控件的事件
记得刚接触复合控件的时候也很纳闷,怎么我点击复合控件的子按纽的时候,发现事件源的名称是复合控件本身,而不是子控件
这就怪了,到底是怎么弄的?原来复合控件对子控件的事件,进行了"冒泡处理",说白了,就是子事件调用父事件,所以我们看到的事件源的名称,当然是复合控件本身了
当然,复合控件的样式也可以做"冒泡处理",好让咱们在设置子控件的样式的时候,直接在最外层设置,不知道也没关系:)
事件冒泡的两方法(很多人都说过了,抱歉...)
一.包含法
包含法的核心是,通过在子控件的事件处理程序中调用复合控件的顶层事件处理程序,以完成子控件的事件上传.在执行过程中,当引发子控件事件后,子控件的事件处理程序将自动调用相关顶层事件处理程序,没什么不好理解的.
因为包含法不在推荐使用,所以这里不再多说.
二.冒泡法
冒泡法也称"事件冒泡",其核心是使用ASP.NET 2.0框架提供的事件上传机制。这种机制允许子控件将事件沿其包容层次结构向上传播到合适的位置引发,并且允许将事件处理程序附加到原始控件以及公开冒泡的事件的控件上.
冒泡法的实现,使用Control基类中专门用于事件上传的两个方法:OnBubbleEvent和RaiseBubbleEvent。它们的声明如下所示

Code
// 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;
}
}
下面,我修改一下"道不远人"原来的代码,来判断是哪一个子控件触发的事件(Thin,非常抱歉...)

Code
protected override bool OnBubbleEvent(object source, EventArgs args)

{
if (args is CommandEventArgs)

{
NumericArgs narg = new NumericArgs((args as CommandEventArgs).CommandArgument.ToString());

CommandEventArgs ce = (CommandEventArgs)args;
if (ce.CommandName == "Add")

{
OnClick(this, narg);
}
else if (ce.CommandName == "Reduce")

{
OnClick(this, narg);

}
return true;
}
else

{
return base.OnBubbleEvent(source, args);
}
}
注意蓝色部分
当然我们可以再定义个ReduceOnClick事件,来区别Add事件,有兴起的朋友可以试试.
总结
该说的都说,也没什么好总结的,大家多调试调试,不妥当的地方,大家多指教指教.
下一章,准备写复合控件的样式,待续...
代码下载