Click here to Skip to main content
Click here to Skip to main content

Reusable Silverlight 2 Popup Menu

, 15 Jan 2009
Rate this:
Please Sign up or sign in to vote.
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.

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).

<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:

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:

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.

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).

/// <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)

About the Author

Philip Kin
Software Developer (Senior) Lifeguard America, Inc.
United States United States
No Biography provided

Comments and Discussions

 
GeneralKeyboard navigaton and hotkey support PinmemberAndrusM19-Jan-09 9:15 
GeneralRe: Keyboard navigaton and hotkey support PinmemberPhilip Kin20-Jan-09 8:41 
GeneralRe: Keyboard navigaton and hotkey support PinmemberAndrusM20-Jan-09 9:09 
You can look into DevExpress Open-source AgDataMenu.
It has keyboard navigation but unfortunately this works only if focuc/( is set from code.
When menu is activated using mouse, keyboard navigation does not work.
 
andrus.
 
Andrus

GeneralThe source file can't download !!!! Pinmemberfirehang_16615-Jan-09 18:48 
GeneralFixed PinmemberPhilip Kin16-Jan-09 4:06 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 15 Jan 2009
Article Copyright 2009 by Philip Kin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid