Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / XAML

Reusable Silverlight 2 Popup Menu

Rate me:
Please Sign up or sign in to vote.
4.53/5 (7 votes)
15 Jan 2009CPOL1 min read 47.8K   503   29   5
A popup menu implementation with submenus.

Menu in action

Introduction

While Silverlight has a lot of useful controls, it seems to be lacking support for some of the things we've all become accustomed to. Recently, I had the "pleasure" of putting together a dropdown menu with submenus built from our database. This was tricky for multiple reasons. The code presented here is a reusable menu driven by a list of items with sub menu support.

Using the Code

To embed the menu in a page, simply add the PopupMenuExample namespace to the XAML as usual.

XML
xmlns:pop="clr-namespace:PopupMenuExample"

And then, add it in to the XAML layout (preferably in a vertical StackPanel with the item that will trigger it).

XML
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top">
    <Border x:Name="PopupBorder" HorizontalAlignment="Left" 
            Height="30" Width="100" BorderBrush="Beige" 
            BorderThickness="1" Background="LightGray">
        <TextBlock HorizontalAlignment="Center" 
             VerticalAlignment="Center">Menu Root</TextBlock>
    </Border>
    <pop:PopupMenu x:Name="PopupMenu1" />
    <TextBlock HorizontalAlignment="Left" x:Name="txtClicked" 
               Margin="0 100 0 0">You last clicked on: Nothing</TextBlock>
</StackPanel>

Some code has to be added to add the menu items and link the menu to the popup trigger. This could possibly be done in the XAML at some point, though I don't have the skills to do that just yet. Here's the code from the sample project that initializes the menu:

C#
PopupMenu1.SetMenuItems(new List&;lt;popupmenuitem>()
{
    new PopupMenuItem(){ Heading = "Item with no submenu. (Tag: Bacon)", 
                         Tag="Bacon", Id=0, ParentId=null},
    new PopupMenuItem(){ Heading = "Item with submenu. (Tag: Eggs)", 
                         Tag="Eggs", Id=1, ParentId=null},
    new PopupMenuItem(){ Heading = "Submenu Item. {Tag: Easter}", 
                         Tag="Easter", Id=2, ParentId=1},
    new PopupMenuItem(){ Heading = "Submenu Item with submenu. (Tag: Foo)", 
                         Tag="Foo", Id=3, ParentId=1},
    new PopupMenuItem(){ Heading = "Sub-Submenu Item. (Tag: Bar}", 
                         Tag="Bar", Id=4, ParentId=3}
});
PopupMenu1.PopupFrom = PopupBorder;

Now, that isn't terribly useful, as most times, it will be data driven (or at least, it will be for me). Here's an example of setting the menu items using a LINQ query:

C#
PopupMenu1.SetMenuItems(
from item in db.MainMenu 
select new PopupMenuItem()
{ 
    Heading = item.Heading, 
    Tag=item.Uri, 
    Id=item.Id, 
    ParentId=item.ParentId
});

To capture the item clicks, you simply handle the ItemClick event.

C#
PopupMenu1.ItemClick += new PopupMenuItemClickHandler(PopupMenu1_ItemClick);

    ...

void PopupMenu1_ItemClick(object sender, PopupMenuItem item)
{
    txtClicked.Text = "You last clicked on: " + (string)item.Tag;
}

Points of Interest

In order to catch when the mouse comes out of the menu (and all child menus or the trigger object), a timer is used to give the mouse some time to enter the new object (or the UI to catch up and send the MouseEnter event).

C#
/// <summary>
/// Catches the mouse leaving this menu (or a child menu).
/// </summary>
void Menu_MouseLeave(object sender, MouseEventArgs e)
{
    //set the timer to see if the user is out of the menu.
    _popTimer.Change(100, System.Threading.Timeout.Infinite);

    //Bubble up an event for parents to see.
    if (_childMouseLeave != null)
    {
        _childMouseLeave(sender, e);
    }
}

/// <summary>
/// Catches the mouse entring this menu (or a child menu).
/// </summary>
void Menu_MouseEnter(object sender, MouseEventArgs e)
{
    //make sure we are still open.
    popMenu.IsOpen = true;

    //stop the timer if its running.
    _popTimer.Change(System.Threading.Timeout.Infinite, 
                     System.Threading.Timeout.Infinite);

    //Bubble up an event for parents to see.
    if (_childMouseEnter != null)
    {
        _childMouseEnter(sender, e);
    }
}

/// <summary>
/// When the timer elapses, the menu is closed.
/// </summary>
void PopupTimer_Elapsed(object state)
{
    popMenu.Dispatcher.BeginInvoke(() => popMenu.IsOpen = false);
}

/// <summary>
/// Catches the mouse leaving this menu's popup trigger.
/// </summary>
void _popupFrom_MouseLeave(object sender, MouseEventArgs e)
{
    _popTimer.Change(100, System.Threading.Timeout.Infinite);
}

/// <summary>
/// Catches the mouse entering this menu's popup trigger.
/// </summary>
void _popupFrom_MouseEnter(object sender, MouseEventArgs e)
{
    popMenu.IsOpen = true;
    _popTimer.Change(System.Threading.Timeout.Infinite, 
                     System.Threading.Timeout.Infinite);
}

History

  • 01/16/2009: Fixed source download link.

License

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


Written By
Software Developer (Senior) Lifeguard America, Inc.
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralKeyboard navigaton and hotkey support Pin
AndrusM19-Jan-09 9:15
AndrusM19-Jan-09 9:15 
GeneralRe: Keyboard navigaton and hotkey support Pin
Philip Kin20-Jan-09 8:41
Philip Kin20-Jan-09 8:41 
GeneralRe: Keyboard navigaton and hotkey support Pin
AndrusM20-Jan-09 9:09
AndrusM20-Jan-09 9:09 
GeneralThe source file can't download !!!! Pin
firehang_16615-Jan-09 18:48
firehang_16615-Jan-09 18:48 
GeneralFixed Pin
Philip Kin16-Jan-09 4:06
Philip Kin16-Jan-09 4:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.