WPF - 如何使用绑定创建菜单和子菜单

28

我正在尝试使用数据绑定创建一个动态菜单。在我的视图模型中,我有一个包含标题和命令的对象列表。然而,它并没有起作用。我认为问题出在数据模板上。请看下面的代码:

<Menu Background="{x:Null}" Grid.Row="0" Grid.Column="1" Panel.ZIndex="2" Width="865" Height="85" HorizontalAlignment="Left" ItemsSource="{Binding Path=MenuItems}">

        <Menu.ItemTemplate>
            <HierarchicalDataTemplate DataType="MenuItemViewModel" ItemsSource="{Binding Path=MenuItems}">
                <MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" ItemsSource="{Binding Path=MenuItems}" Padding="10,12,10,0" Height="44.1" Margin="30,0,0,0" FontWeight="Bold">
                    <MenuItem.ItemsPanel>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel Orientation="Horizontal"/>
                        </ItemsPanelTemplate>
                    </MenuItem.ItemsPanel>
                </MenuItem>
                <HierarchicalDataTemplate.ItemTemplate>
                    <DataTemplate>
                        <MenuItem Header="{Binding Header}" Style="{DynamicResource MenuItemStyle1}" Padding="0,8,0,0" Height="38">
                        </MenuItem>
                    </DataTemplate>
                </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
        </Menu.ItemTemplate>            
    </Menu>

结果仅显示第一个菜单。子菜单未显示,但它们存在,因为具有子菜单的菜单,在菜单标题后打印箭头。

有人能找出绑定上的问题吗?或者有任何建议吗?

仅供参考,MenuItems 是 MenuItemViewModel 对象的列表,它具有一个标题和一个称为 MenuItems 的 MenuItemViewModel 对象(子菜单)列表。

3个回答

57

对我来说,使用这个简单的模板就可以了:

<Menu.ItemContainerStyle>
    <Style TargetType="{x:Type MenuItem}">
        <Setter Property="Command" Value="{Binding Command}" />
    </Style>
</Menu.ItemContainerStyle>
<Menu.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
        <TextBlock Text="{Binding Header}"/>
    </HierarchicalDataTemplate>
</Menu.ItemTemplate>

这是完整的示例:

MainWindow.xaml:

<Window x:Class="WpfApplication14.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication14"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <Menu DockPanel.Dock="Top" ItemsSource="{Binding MenuItems}">
            <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Command" Value="{Binding Command}" />
                </Style>
            </Menu.ItemContainerStyle>
            <Menu.ItemTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:MenuItemViewModel}" ItemsSource="{Binding Path=MenuItems}">
                    <TextBlock Text="{Binding Header}"/>
                </HierarchicalDataTemplate>
            </Menu.ItemTemplate>
        </Menu>
        <Grid>
        </Grid>
    </DockPanel>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication14
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

        public MainWindow()
        {
            InitializeComponent();

            MenuItems = new ObservableCollection<MenuItemViewModel>
            {
                new MenuItemViewModel { Header = "Alpha" },
                new MenuItemViewModel { Header = "Beta",
                    MenuItems = new ObservableCollection<MenuItemViewModel>
                        {
                            new MenuItemViewModel { Header = "Beta1" },
                            new MenuItemViewModel { Header = "Beta2",
                                MenuItems = new ObservableCollection<MenuItemViewModel>
                                {
                                    new MenuItemViewModel { Header = "Beta1a" },
                                    new MenuItemViewModel { Header = "Beta1b" },
                                    new MenuItemViewModel { Header = "Beta1c" }
                                }
                            },
                            new MenuItemViewModel { Header = "Beta3" }
                        }
                },
                new MenuItemViewModel { Header = "Gamma" }
            };

            DataContext = this;
        }
    }

    public class MenuItemViewModel
    {
        private readonly ICommand _command;

        public MenuItemViewModel()
        {
            _command = new CommandViewModel(Execute);
        }

        public string Header { get; set; }

        public ObservableCollection<MenuItemViewModel> MenuItems { get; set; }

        public ICommand Command
        {
            get
            {
                return _command;
            }
        }

        private void Execute()
        {
            // (NOTE: In a view model, you normally should not use MessageBox.Show()).
            MessageBox.Show("Clicked at " + Header);
        }
    }

    public class CommandViewModel : ICommand
    {
        private readonly Action _action;

        public CommandViewModel(Action action)
        {
            _action = action;
        }

        public void Execute(object o)
        {
            _action();
        }

        public bool CanExecute(object o)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { }
            remove { }
        }
    }
}

生成的窗口如下所示:

screen shot


谢谢!菜单已经成功构建。但是,我该如何在MenuItemViewModel中绑定命令? - Jannibelli
是的,这是可能的。请参见我上面更新的示例。当单击菜单项时,将显示一个带有菜单项标题文本的消息框。 - user128300
我无法添加“Separator”(( - Andrei Krasutski
<MenuItem.ItemContainerStyle> <Style TargetType="{x:Type MenuItem}"> <Setter Property="Command" Value="{Binding DataContext.ImportRecentItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type MenuItem}, AncestorLevel=1}}" /> <Setter Property="CommandParameter" Value="{Binding}" /> </Style> </MenuItem.ItemContainerStyle> - AJAY KUMAR

3
这很容易,您可以使用以下代码来创建嵌套菜单:
ViewModel:TopMenuViewModel.cs
public partial class TopMenuViewModel 
{
    public TopMenuViewModel()
    {
        TopMenuItems = new ObservableCollection<MenuItem>
        {
            new MenuItem
            {
                Title = "File",
                PageName =typeof(OfficeListView).FullName,
                ChildMenuItems= {
                    new MenuItem
                    {
                        Title = "New"
                    },
                     new MenuItem
                    {
                        Title = "Open"
                    },
                     new MenuItem
                    {
                        Title = "Save"
                    }
                }
            },
            new MenuItem
            {
                Title = "Edit"
            },
            new MenuItem
            {
                Title = "Search"
            }
        };
    }

视图:TopMenuView.xaml

<Menu IsMainMenu="True" ItemsSource="{Binding TopMenuItems}">
            <Menu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">
                    <Setter Property="Header" Value="{Binding Title}"/>
                    <Setter Property="ItemsSource" Value="{Binding Path=ChildMenuItems}"/>
                </Style>
            </Menu.ItemContainerStyle>
</Menu>   

你的视图模型对特定UI元素的了解真的忽略了MVVM的重点。 - Ready Cent

2
如果您和我一样希望在XAML代码中保留UI构造,则经过一些困难后,我已经找到了一种不错的方法来绑定自定义类型的集合以创建菜单项。
在XAML中:
<Menu DockPanel.Dock="Top">
    <MenuItem Header="SomeHeaderName" ItemsSource="{Binding Path=MyCollection}">
        <MenuItem.ItemsContainerStyle>
            <Setter Property="Header" Value="{Binding Path=SomeRelevantTextProperty}"/>
            <EventSetter Event="Click" Handler="SomeMenuItemClickEventHandler"/>
        </MenuItem.ItemsContainerStyle>
    </MenuItem>
</Menu>

在代码后台:
ObservableCollection<MyClass> MyCollection;

private void SomeMenuItemClickEventHandler(object sender, RoutedEventArgs e)
{
    MenuItem menuItem = sender as MenuItem;
    MyClass myClass = menuItem.DataContext as MyClass;
    // do something useful!
}

public class MyClass
{
    public string SomeRelevantTextProperty { get; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接