Wpf 如何将ViewModels的ObservableCollection绑定到MenuItem?

Wpf 如何将ViewModels的ObservableCollection绑定到MenuItem?,wpf,xaml,mvvm,datatemplate,menuitem,Wpf,Xaml,Mvvm,Datatemplate,Menuitem,当我使用ObservableCollection绑定菜单项时,只能单击菜单项的“内部”区域: 在我的视图中我有以下菜单: <Menu> <MenuItem Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}" ItemTemplate="{StaticResource MainMenuTemplate}"/> </Menu&g

当我使用ObservableCollection绑定菜单项时,只能单击菜单项的“内部”区域:

在我的视图中我有以下菜单:

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

然后我用这个数据模板绑定它:

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

由于ObservableCollectionManageMenuPageItemViewModels中的每个ViewModel都有一个属性TitleIdCode,因此上述代码乍一看效果很好

然而,问题在于数据模板中的菜单项实际上位于另一个菜单项(好像被绑定了两次)的内部,因此在上述数据模板中,背景为Background=“Red”每个菜单项内都有一个红色框,只能单击该区域,而不能单击整个菜单项区域本身(例如,如果用户单击复选标记所在的区域或内部可单击区域的右侧或左侧,则什么也不会发生,如果没有单独的颜色,则会非常混乱。)

将菜单项绑定到ViewModels的可观察集合的正确方法是什么,这样每个菜单项内的整个区域都可以单击?

更新: 因此,我根据以下建议进行了以下更改,现在有了以下内容:

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

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

我将命令绑定放在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>

不要将
菜单项
放在
数据模板
中。数据模板定义了菜单项的内容。相反,通过
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>

...

另外,请看一下HierarchycalDataTemplates.

以下是我如何完成菜单的。这可能不是你所需要的,但我认为这很接近

  <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是将出现在菜单栏上的菜单的集合。它们各自包含将要显示的菜单名和一个名为SubMonItems的集合,我将该集合设置为ItemsSource

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

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


希望这能有所帮助。

我发现将MVVM与MenuItems结合使用非常具有挑战性。我的应用程序的其余部分使用DataTemplates将视图与ViewModel配对,但这似乎不适用于菜单,因为正是您所描述的原因。下面是我最终如何解决它的。我的观点是这样的:

<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:ICommand
{
字符串头{get;}
IEnumerable项{get;}
对象图标{get;}
布尔是可检查的{get;}
布尔已检查{get;set;}
bool可见{get;}
布尔IsSeparator{get;}
字符串工具提示{get;}
}
请注意,IMenuItem定义了IEnumerable项,这是获得子菜单的方式。此外,IsSeparator是一种在菜单中定义分隔符的方法(另一个棘手的小技巧)。您可以在xaml中看到,如果IsSeparator为true,它如何使用DataTrigger将样式更改为现有分隔符样式。以下是MainViewModel如何定义MainMenu属性(视图绑定到该属性):

public IEnumerable主菜单{get;set;}
这似乎很有效。我想你可以在主菜单中使用ObservableCollection。实际上,我使用MEF将菜单组成多个部分,但在这之后,菜单项本身是静态的(即使每个菜单项的属性不是静态的)。我还使用了一个AbstractMenuItem类来实现IMenuItem,它是一个助手类来实例化各个部分中的菜单项

更新:


关于您的颜色问题,是否有帮助?

只需将您的数据模板设置为文本块(或者是带有图标和文本块的堆栈面板)

你的意思是在Menu.ItemContainerStyle中定义标题/颜色,然后在DataTemplate中放置定义命令和CommandParameter的HierarchycalDataTemplate?谢谢,这正是我要找的。效果很好。谢谢+1-HierarchycalDataTemplates使整个问题变得几乎微不足道。好吧,这很好,它可以工作(我想我已经尝试过了),但现在我必须以某种方式将命令连接到TextBlock,它没有命令属性,我不能使用我的DelegateCommand,你使用了AttachedBehaviors还是其他什么?在上面相应地编辑了它,发布的屏幕截图,仍然不起作用:-(嗯,你可能必须将其绑定到TextBlock的标记上,然后编写一个OnApplyTemplate处理程序或其他东西,然后沿着树返回MenuItem。超级黑客和非WPF'y,但有时你必须。谢谢,编辑
public IEnumerable<IMenuItem> MainMenu { get; set; }