Link to home
Start Free TrialLog in
Avatar of rmariotti
rmariotti

asked on

Composite Control - Events in Child aren't Bubbled

Hello,

I am currently developing a tabbed UI control (with a tab for each section of the page).  My tab control consists of a main class (TabStrip) which uses composite bulding, CreateChildControls(), to add the individual Tab objects to the TabStrip.  The Tab objects contain LinkButtons which fire Command events, passing which tab was clicked.  

I have not been able to capture any fired events from the Tab objects, so I've simplified the problem down to the most basic Parent/Child composite control, shown in the code below.  What I need it to get this simplified version working and I'll then use the same principles to fix my production project.  In the below code, there is a parent control (TestParent) which uses composition to add a single child control (TestChild).  The Child has a single control, a LinkButton which fires a Command event.  This command event needs to be bubbled up to the TestParent, which is used in pages.  Currently, the LinkButton appears on the page correctly, with a link and causes a postback, but in debugging/running, I can never hit breakpoints at tabLink_Command() or OnBubbleEvent(), or in my code-behind using standard sytax (TestObj.Command += new CommandEventHandler(TestObj_Command);).

I have reviewed and similar PAQs, as well as posts on MSDN and Google for some clues, but so far I have found none.  I will award full points to anyone who can get my simplified TestParent/TestChild code working.

Thanks!
Ryan

/* ********************************
     PARENT CONTROL
*********************************/
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server
{
    public class TestParent : Control, INamingContainer {
        public event CommandEventHandler Command;
        public event EventHandler Click;

        protected override void CreateChildControls() {
            Controls.Add( new TestChild() );
        }

        protected override bool OnBubbleEvent(object sender, EventArgs e) {
            if (Command != null && e is CommandEventArgs) {
                Command(this, (CommandEventArgs)e);
            }
            else {
                Click(this, e);
            }
            return false;
        }
    }
}

/* ********************************
     CHILD CONTROL
*********************************/
using System;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server {
    public class TestChild : WebControl {

        protected override void Render(HtmlTextWriter writer) {

            LinkButton tabLink = new LinkButton();
            tabLink.Text = "Text";
            tabLink.CommandArgument = "CmdArg";
            tabLink.CommandName = "CmdName";
            tabLink.Command += new CommandEventHandler(tabLink_Command);
            Controls.Add(tabLink);
            base.Render(writer);
        }

        protected void tabLink_Command(object sender, CommandEventArgs e) {
            this.RaiseBubbleEvent(this, e);
        }
    }
}
Avatar of rmariotti
rmariotti

ASKER

Just for some additional details that might get things moving in the right direction:

Look to the bottom, "Detecting and Bubbling Up Events".  Looks like this is identical to what I'm trying to accomplish.
* http://msdn.microsoft.com/asp.net/community/authors/scottmitchell/default.aspx?pull=/library/en-us/dnaspp/html/databoundtemplatedcontrols.asp

Another similar case, but in VB
* http://aspnet.4guysfromrolla.com/articles/051105-1.aspx

Reference, the RaiseBubbleEvent()
http://msdn2.microsoft.com/en-US/library/system.web.ui.control.raisebubbleevent(VS.80).aspx

Cheers,
Ryan
ASKER CERTIFIED SOLUTION
Avatar of raterus
raterus
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Solved...sort of.  

I began reviewing some documentation on this subject in my Wrox Professional ASP.NET Server Controls - Building Custom Controls with C#...and found a note reminding developers that all composite controls must implement INamingContainer to wire events back to their child controls.

I moved the building of my code from Render() to CreateChildControls() and made the parent control implement INamingContainter, and the event fired successfully.  For anyone else reading this, note that it was the missing INamingContainter that was causing my events to be lost (in addition to the fact that events can't be wired at the Render() or OnPreRender() methods...thanks to raterus on that point).

SO: my issue now is that the OnBubbleEvent() of the TestParent control fires TWICE.  The updated code is shown below.  Only OnBubbleEvent() fires twice; the OnCommand() method in the TestChild control only fires once.  Any ideas?

- Ryan


/* ********************************
     PARENT CONTROL - Updated
*********************************/
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server
{
    public class TestParent : Control, INamingContainer {
        public event CommandEventHandler TabClicked;

        protected override void CreateChildControls() {
            Controls.Add(new TestChild());
        }

        protected override bool OnBubbleEvent(object sender, EventArgs e) {
            if (TabClicked != null && e is CommandEventArgs) {
                TabClicked(this, (CommandEventArgs)e);
                return true;
            }
            else {
                return false;
            }
        }
    }
}

/* ********************************
     CHILD CONTROL - Updated
*********************************/
using System;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server {

    public class TestChild : Control, INamingContainer {

        protected override void CreateChildControls(){
            LinkButton tabLink = new LinkButton();
            tabLink.ID = "TabLink";
            tabLink.Text = "Text";
            tabLink.CommandArgument = "CmdArg";
            tabLink.CommandName = "CmdName";
            tabLink.Command += new CommandEventHandler(OnCommand);
            Controls.Add(tabLink);
            base.CreateChildControls();
        }

        protected virtual void OnCommand(object sender, CommandEventArgs e){
            RaiseBubbleEvent(this, e);    
        }
    }
}
I'm not sure if this is the appropriate way to do this, but I found a way to expose the Child control's event and have it fire only once.  But it requires defining a public event delegate in the parent as WELL AS the child.

IN TestChild control:
        protected override void CreateChildControls() {
            ...
            myLinkButton.Command += new CommandEventHandler(OnCommand);    // The TabEvent from the LinkButton is wired up
            ...
        }

        protected virtual void OnCommand(object sender, CommandEventArgs e) {  
            TabClicked(this, e);
        }


Then in the parent, as I add each instance of the TestChild control, I have to wire up the event of the TestChild object which was exposed in the above code.  It looks like this in the Parent control:

IN Parent Control:
        public event CommandEventHandler TabClicked;   // This is the delegate referenced by the page

        protected override void CreateChildControls()
        {
            ...
            TestChild child1 = new TestChild();
            child1.TabClicked += new CommandEventHandler(OnTabClicked);    // The TabEvent from the CONTROL is wired up
            ...
        }

        protected void OnTabClicked(object sender, CommandEventArgs e) {
            TabClicked(this, e);
        }


I'd still like to figure out how to get the bubbling working, since that seems to be the correct way to handle eventing from within Composite controls.
Ryan
Well, as sometimes happens here on Experts, the poster figures out the solution along the way.  I figured out how to Bubble events properly.  

The concept that I was missing was the fact that both the TestParent and TestChild controls are composite controls which BOTH bubble events.  The TestChild itself bubbles up events thrown by it's enclosing LinkButtons.  So I removed explicit event registrations/wireups on the TestChild LinkButton, and added a catch-all OnBubbleEvent which just checks for a CommandEventArg and passes the event up by calling RaiseBubbleEvent().

The parent object does the same, listening for all events from it's children (TestChild controls), then calls it's public events which pages/etc. can reference.  Since raterus was the sole responder, I'll allocate the points regardless, and hope that my post will be useful to other developers.  Again, the key was the double-nested nature of my control hierarchy...both parent and child needed to bubble events since they both contained child controls.

Ryan

<h3>F I N A L    C O D E</h3>

/* ********************************
     PARENT CONTROL - Final
*********************************/
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server
{
    public class TestParent : Control, INamingContainer
      {
        public event CommandEventHandler TabClicked;

        protected override void CreateChildControls()
        {
            Controls.Add(new TestChild());
        }

        protected override bool OnBubbleEvent(object sender, EventArgs e)
        {
            if (TabClicked != null && e is CommandEventArgs)
            {
                TabClicked(this, (CommandEventArgs)e);
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}



/* ********************************
     CHILD CONTROL - Final
*********************************/
using System;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Cfs.Web.Controls.Server
{
    public class TestChild : Control, INamingContainer
    {

        protected override void CreateChildControls()
        {
            LinkButton tabLink = new LinkButton();
            tabLink.ID = "TabLink";
            tabLink.Text = "Text";
            tabLink.CommandArgument = CommandArg;
            tabLink.CommandName = "CmdName";
            Controls.Add(tabLink);

            base.CreateChildControls();
        }

        protected override bool OnBubbleEvent(object sender, EventArgs e)
        {
            if (e is CommandEventArgs)
            {
                RaiseBubbleEvent(this, e);    
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}