Introduction
At least twice in my programming career, I've had the need to create a hierarchical menu. In this example, I will show how to do a hierarchical context sensitive menu with very simple code, almost ridiculously simple. I searched some solutions on the net, and they involved quite complex code for finding the children in real time when the item was opened, converters, etc., and there was a huge amount of code to accomplish such a simple task.
Using the Code
Well, it boils down to having a hierarchical data structure that is to be put in the menu. If the data is fixed and known, it's just a matter of designing the menu in the designer, but if it is dynamic data, we have to implement some functionality to accomplish this.
A simple representation is a list with entries that has an ID, a parent ID, and a header name. Then I also add an item for the icon (if any), ImageFile
, and if the entry should respond on mouse click ("HasEvent
").
public class MenuObj
{
public int Id;
public string Name;
public int ParentId;
public bool HasEvent;
public string ImageFile;
}
The list in this example looks like this:
this.ContextMenu = menu.GetMenu(new List<MenuObj>
{
new MenuObj() { Id = 1, Name = "Presence", ImageFile = "icon1.png"},
new MenuObj() { Id = 2, Name = "Absence", ImageFile = "icon2.png"},
new MenuObj() { Id = 3, Name = "Other", ImageFile = "icon1.png"},
new MenuObj() { Id = 11, ParentId = 1, Name = "Work time" },
new MenuObj() { Id = 12, ParentId = 1, Name = "Overtime" },
new MenuObj() { Id = 13, ParentId = 1, Name = "Additional time" },
new MenuObj() { Id = 21, ParentId = 2, Name = "Sickleave" },
new MenuObj() { Id = 22, ParentId = 2, Name = "Parentleave" },
new MenuObj() { Id = 23, ParentId = 2, Name = "Flex" },
new MenuObj() { Id = 24, ParentId = 2, Name = "Duty off" },
new MenuObj() { Id = 31, ParentId = 3, Name = "Miles" },
new MenuObj() { Id = 32, ParentId = 3, Name = "Bonus" },
new MenuObj() { Id = 111, ParentId = 11, Name = "Project 1", HasEvent = true },
new MenuObj() { Id = 112, ParentId = 11, Name = "Project 2", HasEvent = true },
new MenuObj() { Id = 113, ParentId = 11, Name = "Project 3", HasEvent = true }
});
Let's now create a class that creates a context menu with these items, where ParentId = 0
means that items belong to the root, and other parentId
s reference the items above.
public class HCMenu
{
RoutedEventHandler MouseDownEventHandler;
public HCMenu(RoutedEventHandler MouseDownEventHandler)
{
this.MouseDownEventHandler = MouseDownEventHandler;
}
public ContextMenu GetMenu(List<MenuObj> MenuObjects)
{
ContextMenu cm = new ContextMenu();
Hashtable MenuObjs = new Hashtable();
MenuObjs[0] = cm;
foreach (MenuObj mo in MenuObjects)
{
MenuItem mi = new MenuItem();
mi.Header = mo.Name;
mi.Tag = mo.Id;
if (mo.ImageFile != null)
{
mi.Icon = new Image() {Source = new BitmapImage(
new Uri(mo.ImageFile, UriKind.Relative)), Width=22};
}
if (mo.HasEvent) mi.Click += MouseDownEventHandler;
MenuObjs[mo.Id] = mi;
if (mo.ParentId == 0)
{
(MenuObjs[mo.ParentId] as ContextMenu).Items.Add(mi);
}
else
{
(MenuObjs[mo.ParentId] as MenuItem).Items.Add(mi);
}
}
return cm;
}
}
In the code above, we start iterating trough the menu items (supposed to be sorted from lower to higher IDs). We save the items we create in a Hashtable
for convenience, and add a certain item to its parent item (that we access through the hashtable). Study the code a little, and you will understand how simple it works.
In the constructor of the menu, we also send a RoutedEventHandler
to the mouse click event in the menu. These events should be in the main class. Note the e.Handled = true;
in the end! Very important. I discovered that if you don't stop the routing, the parental items will also raise the mouse click event until it reaches the root.. We don't want that, we want the one we clicked at to be the only one raising the event.
void MenuClicked(object sender, RoutedEventArgs e)
{
int Id = (int)((sender as MenuItem).Tag);
switch (Id)
{
case 111: MessageBox.Show("Do something with item 111"); break;
case 112: MessageBox.Show("Do something with item 112"); break;
default: MessageBox.Show("Do something with item "+Id); break;
}
e.Handled = true;
}
I hope this will save you some trouble in making doing a task quickly and efficiently.