Event Bubbling

Expose Internal Events in Custom Control Development

ASP.NET controls are often built as a hierarchy of constituent controls. And just as often, the overall behavior of the resulting control results from some processing made on the events fired by internal controls. The key technique that allows child controls to propagate events up their containment hierarchy is event bubbling. With event bubbling, events physically fired by internal controls can be made visible at a more convenient level of the control hierarchy. All in all, event bubbling is not a technique for everyday use. More precisely, most of the time its use passes unnoticed, but event bubbling is still there to guarantee that internal events can be exposed to the page code.

 

When the Page Posts Back

Any control events originate from a postback. The click event of a button, for example, is triggered by a client-side submit event. The TextChanged event of a textbox comes when the text of the control changes after a postback. And the list could go on. When a postback occurs, the HTTP payload contains the name of the client-side control that caused the post. The names of client-side controls match the names of their server-side counterpart. If the postback is originated by a submit button, the ID of the button is part of the transmitted data. A sample page with two textboxes and a button may transmit the following markup during a postback:

__VIEWSTATE=%D ... %2D

&TextBox1=Foo

&TextBox2=Bar

&btnClick=Click here

The content of the request body is mapped to a name/value collection. On the server, ASP.NET examines the names of the collection and matches them against the ID of all controls in the page hierarchy. If a match is found for a control that implements the IPostBackEventHandler interface, that control will be given a chance to raise a postback event. Let s see what happens when a button is clicked.

Once ASP.NET has identified the server control, it calls the RaisePostBackEvent method the only method on the IPostBackEventHandler interface. For a button, the implementation of the method, among other things, raises the Click event to the host page.

Imagine now that you have a custom control that incorporates a button. The button shows through the control s interface so that users can see it and interact. When the user clicks on the button, the ID of the button is packed into the body of the postback request. In this case, though, there s no certainty that the ASP.NET runtime will be able to locate a server control with a matching ID. Why is it so?

ASP.NET looks for matching controls only in the Controls collection of the form control. The form control maintains a link to first-level children, but it doesn t know anything about lower-level, contained controls. Naming containers help to fix this issue.

 

Control Containment

A naming container is the parent control where the name of a child control is rooted. A naming container stands to controls in much the same way a directory stands to a file name. You can have two files with the same name in different directories. Likewise, you can have two controls with the same ID under different naming containers. It is not by mere chance that naming containers are extensively used in data-bound iterative controls such as DataGrid and Repeater controls.

Again, think for a moment of a custom control that contains a button. Without taking any countermeasures, there will be no way for an outermost control to intercept any user clicking on the contained button. The transmitted button ID is just the ID of the button, and can likely be something as generic as ctl03. As mentioned, the button is not a direct child of the form, so its name won t be found in the checked Controls collection.

By implementing the INamingContainer interface in the custom control, you establish a clear link between the outermost control and the contained button. In this way, the ID of the button is prefixed with the name of the outermost control, and this is simply the string that gets transmitted with the postback event. The difference is that now ASP.NET recognizes the prefix and looks for the button ID in the proper Controls collection the collection of the custom control. As a result, the right button control is found and the Click event is correctly fired and handled.

Naming containers make it possible for contained controls to fire their own events. This means, though, that while building the parent control, you specify an event handler for the child button. The code listed below demonstrates the building of a child control hierarchy:

Button btn = new Button();

btn.CommandName = "GoGetData";

btn.Text = "Go get data";

this.Controls.Add(btn);

 

Dim btn As New Button

btn.CommandName = "GoGetData"

btn.Text = "Go get data"

Me.Controls.Add(btn)

 

You create a new instance of the Button control, configure it to the extent that it is possible, and then add it to the Controls collection of the parent. As you can see, the Button control has no explicitly defined Click event handler. In this case, naming containment is not sufficient to make the effects of the user action visible to the outside world. The internal event the button click can still be propagated to the upper level of the hierarchy. To make it happen, though, you need to implement event bubbling.

 

Raising Events

Event bubbling refers to the control s ability to propagate an internal event to its parent. A control inherits two methods from the base Control class that relate to event bubbling. The methods are OnBubbleEvent and RaiseBubbleEvent. The following code shows the signatures of both:

protected virtual bool OnBubbleEvent(

  object source,

  EventArgs args

);

protected void RaiseBubbleEvent(

  object source,

  EventArgs args

);

 

 

Protected Overridable Function OnBubbleEvent( _

  ByVal source As Object, _

  ByVal args As EventArgs _

) As Boolean

Protected Sub RaiseBubbleEvent( _

  ByVal source As Object, _

  ByVal args As EventArgs _

)

 

You override OnBubbleEvent in a control to catch an event that another control, lower in the hierarchy, has raised. To bubble an event, a control uses the RaiseBubbleEvent method. OnBubbleEvent is expected to return a boolean value, which denotes whether the event has been fully handled. If false, bubbling can continue and any other controls, higher in the hierarchy, are allowed access. The base Control class implements OnBubbleEvent, as follows:

protected virtual bool OnBubbleEvent(object source,

                                    EventArgs args)

{

   return false;

}

 

 

Protected Overridable Function OnBubbleEvent(_

  ByVal source As Object, ByVal e As EventArgs) As Boolean

  Return False

End Function

 

The net effect is that, by default, bubbling is enabled at all levels and for all controls. RaiseBubbleEvent is a closed method implemented by the Control class. It is marked as protected, but is not overridable. The method loops through all upper controls in the hierarchy and gives each a chance to handle the bubbled event. The pseudo-code below helps you form a precise idea of the internal implementation of RaiseBubbleEvent:

protected void RaiseBubbleEvent(object source,

                               EventArgs args)

{

     Control ctl = Parent;

     while(ctl != null)

     {

       if (ctl.OnBubbleEvent(source, args))

       {

          return;

       }

       ctl = ctl.Parent;

     }

}

 

 

Protected Sub RaiseBubbleEvent(ByVal source As Object,

                              ByVal e As EventArgs)

   Dim ctl As Control = Parent

   While Not ctl Is Nothing

     If ctl.OnBubbleEvent(source, args) Then

          Return

     End If

     ctl = ctl.Parent

   End While

}

 

The core code of the control is a for statement where the index is a Control instance and the step is set by setting the index to the Parent property of the current control. As a control developer, you can call RaiseBubbleEvent for any event that you catch inside a control and want to be visible and handled at a higher level. RaiseBubbleEvent is not intended for use at the page level. It s not coincidental, in fact, that the method is marked as protected. Most built-in controls use it to bubble their events up. You should use RaiseBubbleEvent with all custom controls that have chances to be embedded in composite controls and, of course, key events to expose.

OnBubbleEvent should only be overridden from within a custom composite control whose interface is made of other child controls. To make sense of event bubbling, let s consider what happens with a Button control.

Button is a self-contained control; it has no children that can bubble events up. For this reason, the Button control has no need to override OnBubbleEvent. The situation is fairly different for RaiseBubbleEvent.

The Button control is one of the pillars of ASP.NET. Much of the Web programming passes through buttons and clicks. Being one of the most used constituent controls, for a button to expose its own events is a must.

A Button control has two key events: Click and Command. The following pseudo-code illustrates what happens on a postback caused by a button:

protected virtual void RaisePostBackEvent(

 string eventArgument)

{

 if (CausesValidation)

 {

     Page.Validate(ValidationGroup);

 }

 OnClick(new EventArgs());

 OnCommand(new CommandEventArgs(

  CommandName, CommandArgument));

}
 

Protected Overridable Sub RaisePostBackEvent(

 ByVal eventArgument As String)

  If CausesValidation Then

      Page.Validate(ValidationGroup)

  End If

  OnClick(New EventArgs)

  OnCommand(New CommandEventArgs(

   CommandName, CommandArgument))

End Sub

After the validation stage, the control fires the Click and Command events in sequence. The Click event is a mere notification that the button was pressed. The Command event carries more information, such as the command name associated with the button and an optional argument. The Click event is not bubbled; the Command event is. Here s the pseudo-code of both OnClick and OnCommand:

protected virtual void OnClick(EventArgs e)

{

  if (Click != null)

      Click(this, e);

}

protected virtual void OnCommand(CommandEventArgs e)

{

  if (Command != null)

      Command(this, e);

  base.RaiseBubbleEvent(this, e);

}

 

 

Protected Overridable Sub OnClick(ByVal e As EventArgs)

  RaiseEvent Click, e

End Sub

Protected Overridable Sub OnCommand(ByVal e As CommandEventArgs)

  RaiseEvent Command, e

  MyBase.RaiseBubbleEvent(this, e);

End Sub

 

What does all this mean to you? If you re going to have a button embedded in a composite control, be aware that any Click event triggered by the button will never reach any outermost level. As a result, you can t handle the Click event raised by a child button from the parent, let alone from the host page. If the parent control needs to react to the Click event, then you need to add an explicit handler for the Click event:

Button btn = new Button();

btn.CommandName = "GoGetData";

btn.Text = "Go get data";

btn.Click += new EventHandler(HandleClick);

this.Controls.Add(btn);

 

 

Dim btn As New Button

btn.CommandName = "GoGetData"

btn.Text = "Go get data"

AddHandler btn.Click, AddressOf HandleClick

Me.Controls.Add(btn)

 

For a button embedded in a parent control, there s not much difference between Click and Command. This means that you could avoid adding an explicit Click handler and rely on the Command event bubbling to detect user clickings. The control that embeds a button and wants to be notified of a command event can override the OnBubbleEvent method, as follows:

protected override bool OnBubbleEvent(

 object source, EventArgs e)

{

  bool handled = false;

  if (e is CommandEventArgs)

  {

     CommandEventArgs ce = (CommandEventArgs) e;

     if (ce.CommandName == "Click")

     {

        OnClick(ce); // handle the event at this level

        handled = true;

     }

  }

  return handled;

}

 

 

Protected Overrides Function OnBubbleEvent(_

 ByVal source As Object, ByVal e As EventArgs) As Boolean

  Dim handled As Boolean = false

  If TypeOf(e) Is CommandEventArgs Then

     Dim ce As CommandEventArgs = CType(_

      e, CommandEventArgs)

     if ce.CommandName = "Click" Then

        OnClick(ce)  ' handle the event at this level

        handled = true

     End If

  End If

  Return handled

End Function

 

The RaiseBubbleEvent call in the RaisePostBackEvent method of the Button class guarantees that the above code is ever called. When writing a custom control with events, you might want to place yourself a call to RaiseBubbleEvent after firing important events that parent controls might want to handle or rethrow with a different name.

Let s briefly consider a toolbar-like custom control. The control is implemented as a list of buttons. How can you handle the click event on each button? A possible solution entails that you bind the Click event of each button to the same piece of code, which raises the same event or another one. Although functional, this code is inefficient because it duplicates a code fragment that s already in place. You have no need to reimplement bubbling with buttons because bubbling is already natively available through the Command event. So, in the toolbar-like button, you add a distinct command name to each button and override OnBubbleEvent. In the override, you can either throw a custom event say, ToolbarClick or bubble the original Command event through a call to RaiseBubbleEvent.

 

Rendering vs. Control Hierarchy

As a final note, consider that when you create a custom composite control you typically have two ways of building its output: via rendering or through a control hierarchy. If you opt for rendering, you output the markup by accumulating text in an HTML text writer. This approach is clearly faster because no child control is created. For this reason, rendering is mostly used by low-level controls, the building blocks of ASP.NET (button, dropdown, textbox, and a few others). But is rendering convenient to use with more complex and sophisticated controls?

Rendering is not the recommended approach for composite controls (a composite control is a control made of other controls). The reason is that a control built through rendering has no live children, even when the final markup contains child elements. To illustrate this point, consider the following code output by a custom control that contains a label and a button:

output.Write("<span id=label1>...");

output.Write("<input type=submit id=button1>...</input>");

 

The button1 control doesn t exist for ASP.NET, and any postback event caused by button1 will be ignored. The button1 control is never declared and initialized. If you build the same output as follows, things are radically different:

 

Label label1 = new Label();

:

Controls.Add(label1);

Button button1 = new Button();

:

Controls.Add(button1);

 

 

Dim label1 As New Label

:

Controls.Add(label1)

Dim button1 As New Button

:

Controls.Add(button1)

 

In this case, the button1 control is up and running and all of its events can be bubbled and detected.

Speed and functionality are rarely on the same side of the road. Make sure you always stay on the right side, or at least on the side that is right for your current project.

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish