Steve Holstad's "the bright lights"

"Just because your voice reaches halfway around the world doesn't mean you are wiser than when it reached only to the end of the bar." - Edward R. Murrow
in

Capture Master Page Events in Content Pages - ASP.NET 2.0

Using Delegates with Master Page Events:

For newer developers, using events and delegates can sometime seem pretty intimidating.  This article is aimed to give a very simple example of using events, delegates, public properties and subscribing forms to capture events from one form to the next.

The problem:  A challenge developers may have using ASP.NET 2.0's master pages and content pages is the ability to notify content pages that something has happened in the master page that may be of interest inside the content page.  For this example, let's assume our master page holds a list of buttons, and your content page needs to be notified that a new item is selected.  If you tried to assign the menu value to a public property and then check this value in the Page_Load event of the content page, you'd be dissapointed by the results.  Why?  The Page Load of the content page will fire before the Master Page's Button Click event, so the public property won't be changed yet.  For new developers, this is the point you curse the new technology and start wondering if your local pub is hiring bartenders.  Don't despair, you've got this handled:

The solution:  To solve this, we'll create a delegate.  Add this outside of the code behind class to expose it to the content forms.  Think of a delegate as a notification system that is avaiable for all to use.  Let's think of this as the Ding! download from Southwest Airlines. 

public delegate void MasterPageMenuClickHandler(object sender, System.EventArgs e);
We'll also create a public event we'll use to reference this button click event (this is inside the code behind class):
public event MasterPageMenuClickHandler MenuButton;

When the Master Page's button is clicked, its regular click event fires.  Inside this event, the delegate is called to tell any subscribing entities that something great has happened, and they should react.  I like to think of this action as the Southwest Airlines adding a new flight that should be broadcast to the Ding! users.  Here's how our master page is set up: 

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %>

<!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>
    
<link href="StyleSheet.css" rel="stylesheet" type="text/css" />
</
head>
<body>
    
<form id="form1" runat="server">
    
<div id="wrapper">
        
<div id="header">
            Header Area
        
</div>
        
<div id="menuArea">
            
<asp:Button ID="btnMenu1" runat="server" Text="Menu 1" OnClick="btnMenu1_Click" />
            <
asp:Button ID="btnMenu2" runat="server" Text="Menu 2" OnClick="btnMenu2_Click" />
        </
div>
        
<div id="mainContent">
            
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
            
</asp:contentplaceholder>
        
</div>
        
<div id="footer">
            Footer Area
        
</div>
    
</div>
    
</form>
</body>
</html>

And the code behind class for the Master Page:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

#region Public Delegates

// Expose delegates for Master Page Events
public delegate void MasterPageMenuClickHandler(object sender, System.EventArgs e);

#endregion Public Delegates

public partial class MasterPage : System.Web.UI.MasterPage
{
    #region Public Properties

    private string _currentButton;

    public string CurrentButton
    {
        get { return _currentButton; }
        set { _currentButton = value; }
    }
    
    #endregion Public Properties

    #region Public Events

    public event MasterPageMenuClickHandler MenuButton;

    #endregion Public Events

    #region Page Events

    /// <summary>
    /// Handles Page Load Event
    /// 
</summary>
    /// 
<param name="sender"></param>
    /// 
<param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    /// 
<summary>
    /// Handles Button 1 Click Event
    /// 
</summary>
    /// 
<param name="sender"></param>
    /// 
<param name="e"></param>
    protected void btnMenu1_Click(object sender, EventArgs e)
    {
        // Assign value to public property
        _currentButton = "One";

        // Fire event to existing delegates
        OnMenuButton(e);
    }
    
    /// 
<summary>
    /// Handles Button 2 Click Event
    /// 
</summary>
    /// 
<param name="sender"></param>
    /// 
<param name="e"></param>
    protected void btnMenu2_Click(object sender, EventArgs e)
    {
        // Assign value to public property
        _currentButton = "Two";

        // Fire event to existing delegates
        OnMenuButton(e);
    }

    #endregion Page Events

    #region Virtual Methods

    /// 
<summary>
    /// Invokes subscribed delegates to menu click event.
    /// 
</summary>
    /// 
<param name="e">Click Event arguments</param>
    protected virtual void OnMenuButton(EventArgs e)
    {
        if (MenuButton != null)
        {
            //Invokes the delegates.
            MenuButton(this, e);
        }
    }

    #endregion Virtual Methods

}

Now, subscribing to this delegate is easy.  In the Page Load of the content page, we simply add an event handler to the public event you created in the master page:

Master.MenuButton +=new MasterPageMenuClickHandler(Master_MenuButton);
One important item to note: to reference Master in this fashion, you'll have to add a MasterType directive to the top of the content page's aspx page.
<%@ MasterType virtualPath="~/MasterPage.master"%>

To complete our airline thought, this is similar to your sister downloading the Ding! software onto your machine. 

So here's the process:

A new flight is made available (Master Page button click)
The Ding! service is told about the new item (Our delegate is called by the button click event)
The service tells your computer's Ding! client to show the flight (The subscriber is notified)
You book a flight to Norway (Your form handles the event and gets new value from master page's public property.

Delegates are so useful because they allow a class to notify others of an event, without being tied in any way to the subscribing forms.  Here's the default.aspx content page code behind:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page
{
    /// <summary>
    /// Handles Page Load Event
    /// 
</summary>
    /// 
<param name="sender"></param>
    /// 
<param name="e"></param>
    protected void Page_Load(object sender, EventArgs e)
    {
        Master.MenuButton +=new MasterPageMenuClickHandler(Master_MenuButton);
    }

    /// 
<summary>
    /// Reacts to Master Page menu click event
    /// 
</summary>
    /// 
<param name="sender"></param>
    /// 
<param name="e"></param>
    void Master_MenuButton(object sender, EventArgs e)
    {
        lblOutput.Text = string.Format("Button {0} was pressed", Master.CurrentButton.ToString());
    }
}


Pretty simple explanation, I hope. Sometimes it just takes a basic example to get you thinking about a process correctly...hopefully this will push you towards answering the issue that brought you here. You can download my simple example here. If you work for Southwest, I do accept PayPal <g>

-steve

Here are some other articles that may help you out:
MSDN events
MSDN 2 events
newsgroup discussion

Comments

ze.rodrigues@netcabo.pt said:

Hi steve !

Great peace of code. I'm trying to do the same in vb, and i can´t do it, because in vb i can´t do this :

Master.MenuButton +=new MasterPageMenuClickHandler(Master_MenuButton);

do you have any ideia ?
Thanks
Zé Miguel
# March 9, 2006 4:30 AM

sholstad said:

Ze` Miguel,

You'll have to switch into vb.net syntax, which will change this call to something in the form of:

AddHandler myEvent, AddressOf myEventHandlingMethod

Here's a solid article to help you out if you're running into any issues using events in vb.net:

http://www.samspublishing.com/articles/article.asp?p=23020&rl=1



# March 12, 2006 2:28 AM

ramazan said:

Vb.net code here, my comment turkish:)

'------master page page
Public Delegate Sub DMenu1_MenuItemClick(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.MenuEventArgs)

Partial Class MasterPage
Inherits System.Web.UI.MasterPage

'bir delege değişkeni oluştur, R:referans
'Public RMenu1_MenuItemClick As DMenu1_MenuItemClick
Public Event EMenu1_MenuItemClick As DMenu1_MenuItemClick
Protected Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.MenuEventArgs) Handles Menu1.MenuItemClick
'Invokes the delegates.
RaiseEvent EMenu1_MenuItemClick(Me, e)
End Sub
...
Public Property Master_Menu1() As Menu
Get
Return Me.Menu1
End Get
Set(ByVal value As Menu)
Me.Menu1 = value
End Set
End Property
...

'------content page
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
'master page de tanımlanan olayı event hadler a dinamik olarak bağlanır
AddHandler Master.EMenu1_MenuItemClick, AddressOf Me.Menu1_MenuItemClick
End Sub
...
Protected Sub Menu1_MenuItemClick(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.MenuEventArgs)
...

'------.aspx file, put it header
<%@ MasterType virtualpath="~/Top.master" %>

# March 25, 2006 7:48 PM

Subrahmanyam said:

Excellent Article with which I got clear idea in using Delegates and Events along with relation between Master and Content pages.

Thanks.
Subrahmanyam.
# April 29, 2006 10:34 PM