65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中的分层菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (7投票s)

2010年7月26日

CPOL

2分钟阅读

viewsIcon

41471

downloadIcon

978

一种创建分层上下文菜单的简单方法。

引言

在我的编程生涯中,至少两次我都需要创建分层菜单。在这个例子中,我将展示如何使用非常简单的代码来创建一个分层上下文敏感菜单,几乎可以说是简单到可笑的程度。我搜索了一些网络上的解决方案,它们涉及在打开项目时实时查找子项、转换器等,并且需要大量的代码才能完成这项简单的任务。

menu1.jpg

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;
}

我希望这能帮助你快速有效地完成任务。

© . All rights reserved.