【问题标题】:How can I bind an ObservableCollection of ViewModels to a MenuItem?如何将 ViewModel 的 ObservableCollection 绑定到 MenuItem?
【发布时间】:2009-07-01 08:14:36
【问题描述】:

当我将菜单项与 ObservableCollection 绑定时,只有 MenuItem 的“内部”区域是可点击的:

alt text http://tanguay.info/web/external/mvvmMenuItems.png

在我的视图中,我有这个菜单:

<Menu>
    <MenuItem 
        Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
              ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>

然后我将它与这个 DataTemplate 绑定:

<DataTemplate x:Key="MainMenuTemplate">
    <MenuItem
        Header="{Binding Title}" 
        Command="{Binding DataContext.SwitchPageCommand,
        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}" 
        Background="Red"
        CommandParameter="{Binding IdCode}"/>
</DataTemplate>

由于 ObservableCollection ManageMenuPageItemViewModels 中的每个 ViewModel 都有一个属性 Title 和 IdCode,所以上面的代码乍一看可以正常工作。

然而,问题是DataTemplate中的MenuItem实际上是inside另一个MenuItem(好像它被绑定了两次),因此在上面带有 Background="Red" 的 DataTemplate 中,每个菜单项内都有一个 红色框 并且只有这个区域可以被点击,而不是整个菜单项区域本身(例如,如果用户点击复选标记所在的区域或内部可点击区域的右侧或左侧,则没有任何反应,这,如果没有单独的颜色是很混乱的。)

将 MenuItems 绑定到 ViewModel 的 ObservableCollection 以使每个 MenuItem 内的整个区域都可点击的正确方法是什么?

更新:

所以我根据以下建议进行了以下更改,现在有了这个:

alt text http://tanguay.info/web/external/mvvmMenuItemsYellow.png

我的 DataTemplate 中只有一个 TextBlock,但我仍然不能“为整个 MenuItem 着色”,只能为 TextBlock:

<DataTemplate x:Key="MainMenuTemplate">
    <TextBlock Text="{Binding Title}"/>
</DataTemplate>

我将 Command 绑定放入 Menu.ItemContainerStyle 但它们现在不触发:

<Menu DockPanel.Dock="Top">
    <Menu.ItemContainerStyle>
        <Style TargetType="MenuItem">
            <Setter Property="Background" Value="Yellow"/>
            <Setter Property="Command" Value="{Binding DataContext.SwitchPageCommand,
        RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
            <Setter Property="CommandParameter" Value="{Binding IdCode}"/>
        </Style>
    </Menu.ItemContainerStyle>
    <MenuItem 
        Header="MVVM" ItemsSource="{Binding MvvmMenuPageItemViewModels}"
              ItemTemplate="{StaticResource MainMenuTemplate}"/>
    <MenuItem 
        Header="Application" ItemsSource="{Binding ApplicationMenuPageItemViewModels}"
              ItemTemplate="{StaticResource MainMenuTemplate}"/>
    <MenuItem 
        Header="Manage" ItemsSource="{Binding ManageMenuPageItemViewModels}"
              ItemTemplate="{StaticResource MainMenuTemplate}"/>
</Menu>

【问题讨论】:

    标签: wpf xaml mvvm datatemplate menuitem


    【解决方案1】:

    我发现将 MVVM 与 MenuItems 一起使用非常具有挑战性。我的应用程序的其余部分使用 DataTemplates 将 View 与 ViewModel 配对,但由于您所描述的原因,这似乎不适用于 Menus。这就是我最终解决它的方法。我的视图如下所示:

    <DockPanel>
    <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:MainViewModel.MainMenu)}">
        <Menu.ItemContainerStyle>
            <Style>
                <Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/>
                <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/>
                <Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/>
                <Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/>
                <Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/>
                <Setter Property="MenuItem.Command" Value="{Binding}"/>
                <Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IMenuItem.Visible), 
                    Converter={StaticResource BooleanToVisibilityConverter}}"/>
                <Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IMenuItem.ToolTip)}"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true">
                        <Setter Property="MenuItem.Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type MenuItem}">
                                    <Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Menu.ItemContainerStyle>
    </Menu>
    </DockPanel>
    

    如果您注意到,我定义了一个名为 IMenuItem 的接口,它是 MenuItem 的 ViewModel。这是代码:

    public interface IMenuItem : ICommand
    {
        string Header { get; }
        IEnumerable<IMenuItem> Items { get; }
        object Icon { get; }
        bool IsCheckable { get; }
        bool IsChecked { get; set; }
        bool Visible { get; }
        bool IsSeparator { get; }
        string ToolTip { get; }
    }
    

    请注意,IMenuItem 定义了 IEnumerable Items,这是您获得子菜单的方式。此外,IsSeparator 是一种在菜单中定义分隔符的方法(另一个棘手的小技巧)。如果 IsSeparator 为真,您可以在 xaml 中看到它如何使用 DataTrigger 将样式更改为现有分隔符样式。下面是 MainViewModel 如何定义 MainMenu 属性(视图绑定到的):

    public IEnumerable<IMenuItem> MainMenu { get; set; }
    

    这似乎运作良好。我假设您可以为 MainMenu 使用 ObservableCollection。我实际上是使用 MEF 将菜单组成部分,但之后项目本身是静态的(即使每个菜单项的属性不是)。我还使用了一个实现 IMenuItem 的 AbstractMenuItem 类,它是一个帮助类来实例化各个部分中的菜单项。

    更新:

    关于你的颜色问题,this thread 有帮助吗?

    【讨论】:

    • +1 - 非常适合使用分隔符和所有内容的完整示例。
    • 我有一个非常相似的设计,除了显示分隔符外一切正常。如果我将模板更改为 ,那么我会在分隔符所在的位置看到“分隔符”。但是,当我尝试使用您的答案中的模板时,没有显示任何内容。我尝试了 然后分隔符可见,但我想要默认样式,其宽度由菜单宽度动态设置。我应该在某处定义 SeparatorStyleKey 吗?我在网上搜索,但找不到任何有帮助的...谢谢!
    【解决方案2】:

    不要将MenuItem 放在DataTemplate 中。 DataTemplate 定义了MenuItem 的内容。相反,通过ItemContainerStyle 为MenuItem 指定无关属性:

    <Menu>
        <Menu.ItemContainerStyle>
            <Style TargetType="MenuItem">
                <Setter Property="Header" Value="{Binding Title}"/>
                ...
            </Style>
        </Menu.ItemContainerStyle>
        <MenuItem 
            Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}"
                  ItemTemplate="{StaticResource MainMenuTemplate}"/>
    </Menu>
    

    另外,看看HierarchicalDataTemplates。

    【讨论】:

    • 您的意思是在 Menu.ItemContainerStyle 中定义标题/颜色,然后在 DataTemplate 中放置一个定义 Command 和 CommandParameter 的 HierarchicalDataTemplate?
    • 谢谢,这正是我一直在寻找的。效果很好。谢谢!
    • +1 - HierarchicalDataTemplates 使整个问题变得微不足道。
    【解决方案3】:

    这是我制作菜单的方法。它可能不是您所需要的,但我认为它非常接近。

      <Style x:Key="SubmenuItemStyle" TargetType="MenuItem">
        <Setter Property="Header" Value="{Binding MenuName}"></Setter>
        <Setter Property="Command" Value="{Binding Path=MenuCommand}"/>
        <Setter Property="ItemsSource" Value="{Binding SubmenuItems}"></Setter>
      </Style>
    
      <DataTemplate DataType="{x:Type systemVM:TopMenuViewModel}" >
        <Menu>
          <MenuItem Header="{Binding MenuName}"         
                        ItemsSource="{Binding SubmenuItems}" 
                        ItemContainerStyle="{DynamicResource SubmenuItemStyle}" />
        </Menu>
      </DataTemplate>
    
        <Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" />
    

    TopMenuViewModel 是将出现在菜单栏上的菜单的集合。它们每个都包含将显示的 MenuName 和一个名为 SubMenuItems 的集合,我将其设置为 ItemsSource。

    我通过样式 SumMenuItemStyle 控制子菜单项的显示方式。每个 SubMenuItem 都有自己的 MenuName 属性、ICommand 类型的 Command 属性以及可能的另一个 SubMenuItem 集合。

    结果是我能够将所有菜单信息存储在数据库中,并在运行时动态切换显示的菜单。整个 menuitem 区域均可点击并正确显示。

    希望这会有所帮助。

    【讨论】:

      【解决方案4】:

      只需将您的 DataTemplate 设置为 TextBlock(或者可能是带有图标和 TextBlock 的堆栈面板)。

      【讨论】:

      • 好的,很好,它可以工作(我以为我已经尝试过了),但现在我必须以某种方式将命令连接到 TextBlock,它没有命令属性,我不能使用我的 DelegateCommand,你使用了 AttachedBehaviors 还是其他什么?
      • 在上面进行了相应的编辑,发布了屏幕截图,仍然无法正常工作:-(
      • 嗯,您可能必须将其绑定到 TextBlock 的标签,然后编写一个 OnApplyTemplate 处理程序或其他东西,然后将树返回到 MenuItem。超级 hacky 和非 WPF'y,但有时你必须这样做。
      • 谢谢,编辑 ItemContainerStyle 对我有用。 (使用 Kent Boogaart 的例子。)
      猜你喜欢
      • 2012-12-16
      • 2017-12-28
      • 2018-05-10
      • 2012-11-15
      • 1970-01-01
      • 1970-01-01
      • 2020-07-12
      • 2021-12-10
      • 2013-08-07
      相关资源
      最近更新 更多