在ASP.NET的复合组件中实现冒泡处理机制

来源:互联网 发布:ipad如何卸载软件 编辑:程序博客网 时间:2024/06/06 13:08

 在ASP.NET的复合组件中实现冒泡处理机制

本文节选自《庖丁解牛:纵向切入ASP.NET 3.5控件和组件开发技术》一书

 

    在复合控件中,如果子控件之间结构比较复杂,并且很多情况下是多层次的结构,比如在GridView主控件中包括模板容器控件,模板容器控件中又包含命令按钮控件,且控件比较多,在这种情况下如果再使用前面讲的事件机制会比较麻烦;且代码看起来比较乱,因为要为每个按钮注册一个事件(GridView的按钮列的按钮数取决于每页的记录数,有多少行就有多少按钮)。
基于此,ASP.NET框架提供了冒泡法,即事件向上冒泡,其核心是使用.NETFramework提供的事件上传机制。这种机制允许子控件将事件沿其包容层次结构向上传播,而不是由每个命令按钮引发事件。在事件上传过程中由我们确定什么时候引发自定义的事件。通过响应这一事件,可以不必为子控件分别编写事件处理方法。
冒泡法的实现,主要是利用Control基类中专门用于事件上传的两个方法OnBubbleEvent和RaiseBubbleEvent。基类Control中这两个方法的原声明代码如下:
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
protected virtual bool OnBubbleEvent(object source, EventArgs args)
{
    return false;
}

protected void RaiseBubbleEvent(object source, EventArgs args)
{
    for (Control control = this.Parent; control != null; control = control.Parent)
    {
        if (control.OnBubbleEvent(source, args))
        {
            return;
        }
    }
}
OnBubbleEvent方法用于引发自定义事件,并通过返回布尔值指定子控件的事件是否沿复合控件层次结构继续向上传递。参数source表示事件源(如Button),参数args表示包含事件数据的EventArgs对象(当指定按钮的CommandName,系统会自动创建它的CommandEventArgs对象,该对象类型的基类为EventArgs)。如果需要处理当前容器控件冒泡事件,则要重写OnBubbleEvent事件,在OnBubbleEvent重写方法中引发自己的事件,处理完自己的事件逻辑后,最终要返回一个布尔值,来决定是否让冒泡机制继续沿着容器结构向上传递。Control基类虚方法的默认值为false。
RaiseBubbleEvent方法用于将所有事件源及其信息分配给控件的父级。有一点很重要,它不能被重写,需要调用它时直接调用即可。
下面就基于冒泡法,把SearchControl的按钮事件引发用冒泡法来实现,控件命名为SearchControlBubbleUp。实现起来非常简单,由于只是引发事件方式不同,仅需要针对变更的部分,修改一下引发事件相关的代码,即把:

btnSearch.Click += new EventHandler(btnSearch_Click);

protected virtual void OnButtonSearchClick(SearchEventArgs e)
    {
        SearchEventHandler ButtonSearchClickHandler = (SearchEventHandler) Events[ButtonSearchClickObject];
        if (ButtonSearchClickHandler != null)
        {
            ButtonSearchClickHandler(this, e);
        }
    }

修改为:

btnSearch.CommandName = "ButtonSearchClick";

protected override bool OnBubbleEvent(object sender, EventArgs e)
    {
        bool handled = false;
        if (e is CommandEventArgs)
        {
            CommandEventArgs ce = (CommandEventArgs)e;
            if (ce.CommandName == "ButtonSearchClick")
            {
                SearchEventArgs args = new SearchEventArgs();
                args.SearchValue = this.Text;
                OnButtonSearchClick(args);
                handled = true;
            }
        }
        this.RaiseBubbleEvent(sender, e);
        return handled;
    }

以上代码中,原来的SearchControl控件是通过注册Button的标准事件来完成的,而在这一节的示例控件SearchControlBubbleUp中仅做了两个步骤:
  指定Button控件的CommandName属性。一般该属性经常与CommandArgument组合使用。
  重写OnBubbleEvent方法。在该方法中,首先取得参数对象,然后根据参数对象判断当前引发事件源是不是我们指定的按钮(属性CommandName是否等于“ButtonSearch Click”)。如果是我们指定的按钮,则引发调用主控件的事件OnbuttonSearchClick(args),并设置标志返回值的变量handled为true。在最后返回值之前调用了Control基类的RaiseBulleEvent方法,把事件源sender和e参数对象发送给控件的父级。
下面看一下SearchControlBubbleUp控件完整源代码,如下所示:
/// <summary>
/// 获得本书更多内容,请看:
/// http://blog.csdn.net/ChengKing/archive/2008/08/18/2792440.aspx
/// </summary>
[DefaultProperty("Text")]
[DefaultEvent("ButtonSearchClick")]
[ToolboxData("<{0}:SearchControlBubbleUp runat=server></{0}:SearchControlBubbleUp>")]
public class SearchControlBubbleUp : CompositeControl
{
    private Button btnSearch;
    private TextBox tbSearchText;

    [Category("搜索")]
    [DefaultValue("")]
    [Description("获取文本框的值")]
    public string Text
    {
        get
        {
            this.EnsureChildControls();
            return tbSearchText.Text;
        }
    }

    private static readonly object ButtonSearchClickObject = new object();
    public event SearchEventHandler ButtonSearchClick
    {
        add
        {
            base.Events.AddHandler(ButtonSearchClickObject, value);
        }
        remove
        {
            base.Events.RemoveHandler(ButtonSearchClickObject, value);
        }
    }

    protected override void CreateChildControls()
    {
        this.Controls.Clear();
        btnSearch = new Button();
        btnSearch.ID = "btn";
        btnSearch.Text = "搜索";
        btnSearch.CommandName = "ButtonSearchClick";           

        tbSearchText = new TextBox();
        tbSearchText.ID = "tb";
        this.Controls.Add(btnSearch);
        this.Controls.Add(tbSearchText);
    }

    protected virtual void OnButtonSearchClick(SearchEventArgs e)
    {
        SearchEventHandler ButtonSearchClickHandler = (SearchEventHandler)
        Events[ButtonSearchClickObject];
        if (ButtonSearchClickHandler != null)
        {
            ButtonSearchClickHandler(this, e);
        }
    }

    protected override bool OnBubbleEvent(object sender, EventArgs e)
    {
        bool handled = false;
        if (e is CommandEventArgs)
        {
            CommandEventArgs ce = (CommandEventArgs)e;
            if (ce.CommandName == "ButtonSearchClick")
            {
                SearchEventArgs args = new SearchEventArgs();
                args.SearchValue = this.Text;
                OnButtonSearchClick(args);
                handled = true;
            }
        }
        this.RaiseBubbleEvent(sender, e);
        return handled;
    }

    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);
        tbSearchText.RenderControl(output);
        output.RenderEndTag();
        output.RenderBeginTag(HtmlTextWriterTag.Td);
        btnSearch.RenderControl(output);
        output.RenderEndTag();
        output.RenderEndTag();
        output.RenderEndTag();
    }
}
SearchControlBubbleup控件在页面中的使用方法与SearchControl完全一样。
使用冒泡机制时要注意分清楚控件的层次结构,一般开发列表控件等比较复杂的控件时,比如所有的编辑、删除等命令列的处理经常会采用此机制。
该控件现在已经能够完成基本的搜索功能了,但还缺少一个比较智能的功能。下一节我们将完善该控件,完成它的最终版本。

原创粉丝点击