Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Reusable Silverlight 2 Popup Menu

0.00/5 (No votes)
15 Jan 2009 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here