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).
void Menu_MouseLeave(object sender, MouseEventArgs e)
{
_popTimer.Change(100, System.Threading.Timeout.Infinite);
if (_childMouseLeave != null)
{
_childMouseLeave(sender, e);
}
}
void Menu_MouseEnter(object sender, MouseEventArgs e)
{
popMenu.IsOpen = true;
_popTimer.Change(System.Threading.Timeout.Infinite,
System.Threading.Timeout.Infinite);
if (_childMouseEnter != null)
{
_childMouseEnter(sender, e);
}
}
void PopupTimer_Elapsed(object state)
{
popMenu.Dispatcher.BeginInvoke(() => popMenu.IsOpen = false);
}
void _popupFrom_MouseLeave(object sender, MouseEventArgs e)
{
_popTimer.Change(100, System.Threading.Timeout.Infinite);
}
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.