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);
        }
    }
}
rmariottiAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

rmariottiAuthor Commented:
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
0
raterusCommented:
I'm looking at your Child control, and I don't think it's created properly.  You're using the Render method, which isn't for use in composite controls.  You should be using CreateChildControls in the child control if you are going to add a LinkButton like that.  Ultimately, I'd say your event isn't firing because the Render event comes way after where it should be created if you are going to catch an event on it.  I'd change it to something like this,

/* ********************************
     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 CreateChildControls() {

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

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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
rmariottiAuthor Commented:
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);    
        }
    }
}
0
rmariottiAuthor Commented:
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
0
rmariottiAuthor Commented:
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;
            }
        }
    }
}
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
ASP.NET

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.