WPF 中的分层菜单






4.86/5 (7投票s)
一种创建分层上下文菜单的简单方法。
引言
在我的编程生涯中,至少两次我都需要创建分层菜单。在这个例子中,我将展示如何使用非常简单的代码来创建一个分层上下文敏感菜单,几乎可以说是简单到可笑的程度。我搜索了一些网络上的解决方案,它们涉及在打开项目时实时查找子项、转换器等,并且需要大量的代码才能完成这项简单的任务。
Using the Code
实际上,关键在于拥有一个要放入菜单中的分层数据结构。如果数据是固定的并且已知的,那么只需在设计器中设计菜单即可,但如果是动态数据,则必须实现一些功能来完成此操作。
一个简单的表示方法是包含 ID、父 ID 和标题名称的列表。然后我还会添加一个用于图标(如果有)的项,ImageFile
,以及该条目是否应响应鼠标单击(“HasEvent
”)。
public class MenuObj
{
public int Id;
public string Name;
public int ParentId;
public bool HasEvent;
public string ImageFile;
}
此示例中的列表如下所示
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 }
});
现在让我们创建一个类,该类使用这些项目创建上下文菜单,其中 ParentId = 0
表示项目属于根目录,而其他 parentId
引用上面的项目。
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;
}
}
在上面的代码中,我们开始遍历菜单项(假设从较低到较高的 ID 排序)。为了方便起见,我们将创建的项目保存在 Hashtable
中,并将某个项目添加到其父项目(我们通过哈希表访问)。仔细研究一下代码,你就会明白它的工作原理是多么简单。
在菜单的构造函数中,我们还将一个 RoutedEventHandler
发送到菜单中的鼠标单击事件。这些事件应该在主类中。注意 e.Handled = true;
在最后!非常重要。我发现如果你不停止路由,父项目也会引发鼠标单击事件,直到到达根目录。我们不希望这样,我们希望只引发我们单击的项目。
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;
}
我希望这能帮助你快速有效地完成任务。