Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Event Bubbling: Sending a Message from a User Control to the Page Container

4.36/5 (8 votes)
15 Nov 2009CPOL8 min read 53.7K   574  
This article describes how to send a message from a user control back to the page that contains the control.

Business Problem

There is a listing of clubs, and the user base wants to submit requests for a new club to be added to the list. In the first view, clubs are listed in a grid view control. The user clicks an add club link to submit a club using the form view control. The view changes to allow a user submission, and the list of clubs is hidden from view. The form view control is contained in a generic web user control. When the user selects Submit or Cancel, a message must be sent to the page containing the control. The information contained in the message is used to display a message indicating a successful or cancelled submission. When the user has completed or cancelled the submission, the view changes back to the listing of the clubs. The appropriate message is displayed, and the submission form is hidden from view.

Illustrating the Use Case

This is how the application starts with the grid view populated. The user clicks the Add New Club link to submit a new club.

Initial view

The user enters the club information and chooses to submit or cancel the entry. The page, which is the container of the web user control, receives a message indicating the user's choice.

Submit club

This demonstrates that the user chose to submit the club and the form view user control sent a message that bubbled up the the containing page. The page determines the submission was a success and displays an acknowledgement.

Submission successful.

When the user chooses to cancel the submission, the web user control that contains the form view sends a message to the page containing the control. Here, the user chose to cancel the club submission.

Image 4

Background

The challenge and solution: To implement this business process, the developer must leverage event bubbling. Using event bubbling, a message is sent from the web user control back to the club listing page. The information in the message is used to change the state of the page and list an acknowledgement of the submission. Unfortunately, the documentation on the web describing how to implement this process is scarce and sketchy at best. This article illustrates to the developer the details of how to implement this process.

Event bubbling in a nutshell: An event bubble simply contains a message that is sent from one object to another. The message is contained in a class that inherits from EventArgs. EventArgs is a system base class that contains event data. This, of course, is a simplification of the concept.

To illustrate, the page class contains both the grid view and the custom web user control. The web user control is a container for the form view control. The form view control contains the Submit and Cancel links.

What we want is to send a message from the Submit or Cancel links up through the control hierarchy to the Club List page container. The message is said to bubble up to the page container. There, an event handler interprets the message, and displays the success or cancellation message to the user.

Image 5

That is about as deep as this article is going to get into the event bubbling theory. On to composing the code.

Key Elements

The UserControlEventArgs class has two parts. The class declaration and the declaration of the event delegate:

UserControlEventArgs Class
Instance Variable(s)
InsertCompleteThis flag indicates a successful insertion of a new record or a cancellation of the submission.

The event delegate:

Event Delegate
ClubInsertedEventHandlerAccepts two arguments. The first is the sender of the type object. The second is the UserControlEventArgs used to pass the message in the bubble.

The WebUserControlAddUser class requires an instance variable for the event handler.

WebUserControlAddUser
Instance Variable
public event ClubInsertedEventHandler ClubInsertComplete;

Declare this variable to be an event of type ClubInserted EventHandler. This is used to raise the event that will bubble up to the ClubList page.

Firing the Event
UserControlEventArgs args = new UserControlEventArgs(true);Declare the args variable that will be passed into the event bubble.
ClubInsertComplete(this, args);Raise the event.

Finally, the ClubList page consumes the event.

Club List Page
Click Event (ASPX)
OnClubInsertComplete="ClubInsertComplete"

The web user control WebUserControlAddUser invokes the method ClubInsertComplete.

Handling the Web User Control Event
ClubInsertComplete(object sender, UserControlEventArgs e)"e" exposes the InsertComplete variable that is passed as an argument into the event handler.

Composing the Code

Key Concepts

The UserControlEventArgs class is used to pass the message from the web user control to the default web page that contains the user control. A delegate called the ClubInsertedEventHandler is used to declare the “args” delegate type for the event that is raised in the user control. The event is called “ClubInsertComplete”.

The delegate event declaration resides in the UserControlEventArgs class. Note that the declaration is external to the class. It is a mistake to include the delegate within the body of the UserControlEventArgs class, and will cause the event to be hidden from the default page which is the container for the web user control.

When a record has been inserted into the data table, the args value is instantiated and passed into the “ClubInsertComplete” event as an argument. Note that the delegate for the event follows the Microsoft pattern for declaring events.

When the “ClubInsertComplete” event is raised, the event bubbles up to the default page that is a container for the “WebUserControlAddUser”, and there it is handled. VS2008 intellisense does not show the event in the customary manner. However, the user can go to the GUI portion of the default page where the user control has been added. There, VS2008 intellisense will list the “OnClubInsertComplete” event that is set to the “ClubInsertComplete” method in the default code-behind.

Steps to follow:

Step 1. The ClubList class is a helper that simply creates a datatable and populates it with a list of clubs. They will be displayed in the grid view control on the default page.

Static class ClubList:

MethodsDescription
BuildClubList()A data table is populated with a default club list. The club list is later bound to the GridView control.
AddClub()Add a club to the data table.
ClearTable()Specific to the VS2008 debugger. It is necessary to clear out the table when using a static class because the object has not been disposed by the debugger when it is closed between debugging runs.
C#
public static class ClubList
{
    private static DataTable _dt = new DataTable("ClubList");

    static public DataTable dt
    {
        get { return _dt; }
        set { _dt = value; }
    }

    //CLEAR THE TABLE BECAUSE OF A 
    //PECULARITY WITH VS2008 WHEN 
    //RUNNING IN DEBUG MODE.
    static public void ClearTable()
    {
        _dt.Columns.Clear();
        _dt.Rows.Clear();
    }

    static public DataTable BuildClubList()
    {
        DataColumn col;
        DataColumn col2;
        DataColumn col3;
        DataRow row;
        // Create new Datacol, set DataType, 
        // colName and add to DataTable. 
        col = new DataColumn();
        col.DataType = Type.GetType("System.Int32");
        col.ColumnName = "ClubID";
        col.ReadOnly = true;
        col.Unique = false;
        // Add the col to the DatacolCollection.
        _dt.Columns.Add(col);

        col2 = new DataColumn();
        col2.DataType = Type.GetType("System.String");
        col2.ColumnName = "ClubName";
        col2.AutoIncrement = false;
        col2.Caption = "Club Name";
        col2.ReadOnly = false;
        col2.Unique = false;
        _dt.Columns.Add(col2);
        col3 = new DataColumn();
        col3.DataType = Type.GetType("System.String");
        col3.ColumnName = "URL";
        col3.AutoIncrement = false;
        col3.Caption = "URL";
        col3.ReadOnly = false;
        col3.Unique = false;
        _dt.Columns.Add(col3);
        row = _dt.NewRow();

        row["ClubID"] = 1000;
        row["ClubName"] = "Joe's Rocketry";
        row["URL"] = "www.joes.com";
        _dt.Rows.Add(row);
        row = _dt.NewRow();

        row["ClubID"] = 1001;
        row["ClubName"] = "American Rocketry";
        row["URL"] = "www.ar.com";
        _dt.Rows.Add(row);
        return _dt;
    }

    public static DataTable AddClub(int ClubID, string ClubName, string url)
    {
        DataRow row;

        row = _dt.NewRow();
        row["ClubID"] = ClubID;
        row["ClubName"] = ClubName;
        row["URL"] = url;
        _dt.Rows.Add(row);
        row = _dt.NewRow();
        return _dt;
    }
}

Step 2. Create a class that inherits from EventArgs. This class also has an event delegate declared outside of the body of the class.

The class UserControlEventArgs contains a single property InsertComplete that encapsulates the message being sent from the web user control to the club list page. The EventArgs inheritance is a base class that resides in the system.

C#
/// <summary>
/// Jeff Kent - 11/14/2009
/// EVENT CLASS THAT ENCAPSULATES THE EVENT ARGUMENTS.
/// </summary>
public class UserControlEventArgs : EventArgs
{
    //CONSTRUCTOR USED TO DETERMINE IF A
    //RECORD INSERTION WAS SUCCESSFUL.
    public UserControlEventArgs(bool InsertComplete)
    {
        if (InsertComplete)
        {
            this.InsertComplete = true;
        }
        else
        {
            this.InsertComplete = false;
        }
    }
    private bool _InsertComplete;
    public bool InsertComplete{ 
    get { return _InsertComplete ; }
    set { _InsertComplete = value; } 
}

//DECLARE THE EVENT DELEGATE FOR THE CONTROL.
//NOTE HOW THE DELEGATE RESIDES OUTSIDE THE SCOPE OF THE CLASS.
public delegate void ClubInsertedEventHandler(object sender, UserControlEventArgs e);

Step 3. Add a web user control to your project. I use the form view control with the insert item template to gather data from the user. Observe how the ItemCommand event is used to manage the state of the form view control. I do that because of peculiarities present in the control.

The key here is the ClubInsertedEventHandler, which is an event delegate that declares the variable ClubInsertComplete. When the form view ItemCommand event fires, the switch structure fall through to CommitInsert or CancelInsert. There, the args variable is instantiated using the constructor. The value of true or false flags whether the user chose to submit a club or cancel the submission. ClubInsertComplete initiates the event bubbling process. A message is sent back to the Club List page where it is handled.

C#
/// <summary>
/// Jeff Kent - 11/14/2009
/// SAMPLE WEB USER CONTROL WITH EVENT BUBBLING SUPPORT.
/// THE FORM VIEW CONTROL IS USED TO COLLECT AND PROCESS CLUB INFORMATION.
/// ONLY THE INSERT ITEM TEMPLATE IS USED IN THIS EXAMPLE.
/// </summary>
public partial class WebUserControlAddUser : System.Web.UI.UserControl
{
    //DECLARE THE DELEGATE VARIABLE.
    public event ClubInsertedEventHandler ClubInsertComplete;
    //PUT THE FORM VIEW INTO THE INSERT MODE 
    //EVERY TIME THE PAGE LOADS THE CONTROL.

    protected void FormView1_Load(object sender, EventArgs e)
    {
        FormView1.ChangeMode(FormViewMode.Insert);
    }

    // THE FORM VIEW CONTROL CAN BE TRICKY. 
    // THE WORK AROUND THE QUIRKS IS TO INTERCEPT THE ITEMCOMMAND EVENT
    // THEN USE THE SWITCH STRUCTURE TO CONTROL THE STATE OF THE CONTROL.
    protected void FormView1_ItemCommand(object sender, FormViewCommandEventArgs e)
    {
        UserControlEventArgs args;

        switch (e.CommandName)
        {
            case "StartInsert":
                //NOT USED IN THIS IMPLEMENTATION.
                break;
            case "CommitInsert":
                InsertNewClub();
                FormView1.ChangeMode(FormViewMode.ReadOnly);
                //FIRE THE EVENT TO SEND NOTICE TO
                //THAT THE CLUB SUBBMISSION WAS SUCCESSFUL.
                args = new UserControlEventArgs(true);
                ClubInsertComplete(this, args);
                break;
            case "CancelInsert":
                FormView1.ChangeMode(FormViewMode.ReadOnly);
                FormView1.DataBind();
                //FIRE THE EVENT TO SEND NOTICE TO
                //THAT THE CLUB SUBBMISSION WAS CANCELLED.
                args = new UserControlEventArgs(false);
                ClubInsertComplete(this, args);
                break;
            default:
                break;
        }
    }

    //OBTAIN THE DATA FROM THE FORM 
    //AND INSERT IT INTO THE DATA TABLE.
    protected void InsertNewClub()
    {
        TextBox tb;
        tb = (TextBox)FormView1.FindControl("ClubID");
        int ClubID = Convert.ToInt32(tb.Text);
        tb = (TextBox)FormView1.FindControl("ClubName");
        string ClubName = tb.Text;
        tb = (TextBox)FormView1.FindControl("URL");
        string url = tb.Text;
        ClubList.AddClub(ClubID, ClubName, url);
    }
}

Step 4. Create the user control and use the form view control to collect the data. Note that only the insert item template is used in this example.

ASP.NET
<%@ Control Language="C#" AutoEventWireup="true" 
    CodeFile="WebUserControlAddUser.ascx.cs"
    Inherits="WebUserControlAddUser" %>

<asp:FormView ID="FormView1" runat="server" Width="300px" 
  EmptyDataText="Empty rocketry data"
  OnItemCommand="FormView1_ItemCommand" OnLoad="FormView1_Load">

<InsertItemTemplate>
<table cellpadding="2" cellspacing="0" 
    style="vertical-align: top; border-color: Black;border-style: outset; 
          border-width: 3px; width: 400px">
<tr>
<td style="text-align: left; vertical-align: top; width: 33%">
Club ID (xxx - int):
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="ClubID" runat="server" Columns="30" 
    Style="text-align: left" Text='<%# Bind("ClubID") %>'
    TabIndex="10"> </asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" 
    runat="server" Text="*" ErrorMessage="Enter an integer" 
    ControlToValidate="ClubID"></asp:RequiredFieldValidator>
</td>
</tr>
<tr>
<td style="text-align: left; vertical-align: top">
Club Name:
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="ClubName" runat="server" Columns="30" 
    Style="text-align: left" Text='<%# Bind("ClubName") %>'
    TabIndex="10"> </asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
    runat="server" ControlToValidate="ClubName"
    Text="*" 
    ErrorMessage="The club name is required.">
</asp:RequiredFieldValidator>
<br />
</td>
</tr>
<tr>
<td style="text-align: left; vertical-align: top">
URL:
</td>
<td style="text-align: left; vertical-align: top">
<asp:TextBox ID="url" runat="server" 
    Style="text-align: left" Text='<%# Bind("url") %>'
    TabIndex="20"></asp:TextBox>
<br />
<br />
</td>
</tr>
<tr>
<td colspan="2">
<asp:LinkButton ID="LinkButton4" runat="server" 
    CommandName="CommitInsert" CausesValidation="true"
    TabIndex="100">Submit</asp:LinkButton>
<asp:LinkButton ID="LinkButton3" runat="server" 
    CommandName="CancelInsert" CausesValidation="false"
    TabIndex="200">Cancel</asp:LinkButton>
</td>
</tr>
<tr>
<td colspan="2">
<asp:ValidationSummary ID="ValidationSummary1" runat="server" />
</td>
</tr>
</table>
</InsertItemTemplate>
<EditRowStyle />
</asp:FormView>

Step 5. Finally, create a default page with a multiview control and drop the user control into part one of the view. Put a data grid in the second view.

ClubInsertComplete intercepts the message that bubbled up from the web user control. The message is passed to ClubInsertComplete and is exposed by the value e, which is of type UserControlEventArgs. It is the UserControlEventArgs object that encapsulates the message that was sent from the web user control back to the Club List page. It is here that the submit successful or cancellation label is set to be made visible. Also, the view is set as necessary.

C#
public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            // SET THE INITIAL VIEW TO THE GRID WITH
            // A SAMPLE LISTING OF CLUBS.
            MultiView1.SetActiveView(view2);
            ClubList.ClearTable();
            gvClubList.DataSource = ClubList.BuildClubList();
            gvClubList.DataBind();
            SubmissionSuccessful.Visible = false;
        }
    }
    //CHANGE THE VIEW SO THAT THE FORM FOR A CLUB ENTRY IS EXPOSED.
    protected void LinkButton1_Click(object sender, EventArgs e)
    {
        MultiView1.SetActiveView(view1);
    }
    //CUSTOM EVENT THAT BUBBLES FROM ROCKETRY CLUB CONTROL.
    protected void ClubInsertComplete(object sender, UserControlEventArgs e)
    {
        //NOTIFY THE USER WHEN THE SUBMISSION IS SUCCESSFUL.
        if (e.InsertComplete)
        {
            SubmissionSuccessful.Visible = true;
        }
        MultiView1.SetActiveView(view2);
        gvClubList.DataSource = ClubList.dt;
        gvClubList.DataBind();
    }
}

Of special note is that VS2008 intellisense does not expose the events for the web user control in the customary manner. However, in the ASPX page, the OnClubInsertComplete event appears in intellisense. Set that to ClubInsertComplete to handle the event that is bubbled up to the page from the web user control.

ASP.NET
<pre>
<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Src="WebUserControlAddUser.ascx" 
    TagName="WebUserControlAddUser" TagPrefix="uc1" %>
<!DOCTYPE html 
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="view1" runat="server">
<!-- THE "OnClubInsertComplete" EVENT IS EXPOSED TO 
    INTELLISENSE FOR THE USER CONTROL. -->
<uc1:WebUserControlAddUser ID="AddClubUser" runat="server" 
    OnClubInsertComplete="ClubInsertComplete" />
</asp:View>
<asp:View ID="view2" runat="server">
<asp:Label ID="SubmissionSuccessful" runat="server" 
    Text="Your Club Has Been Submitted."></asp:Label><br />
<asp:LinkButton ID="LinkButton1" runat="server" 
    Text="Add New Club" OnClick="LinkButton1_Click">
    Add New Club</asp:LinkButton><br />
<asp:GridView ID="gvClubList" runat="server">
</asp:GridView>
</asp:View>
</asp:MultiView>
</div>
</form>
</body>
</html>

Conclusion

It was challenging to find all of the materials necessary for developing the actual solution that has been implemented on a live production site. The development process involved putting together diverse concepts, and there was a clear lack of references on the web. At best, the materials that I found were dated. It is my hope that developers will find the example useful in understanding how event bubbling, as it relates to web user controls, can be utilized on production web sites.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)