如何将ViewModels的ObservableCollection绑定到MenuItem? [英] How can I bind an ObservableCollection of ViewModels to a MenuItem?

查看:67
本文介绍了如何将ViewModels的ObservableCollection绑定到MenuItem?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我将菜单项与ObservableCollection绑定时,只能单击MenuItem的内部"区域:

替代文本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 ,因此上述代码乍看之下效果很好.

但是,问题在于数据模板中的 MenuItem 实际上位于另一个菜单项( 内部 >好像它被绑定了两次),因此在上面带有 Background ="Red" 的DataTemplate中,每个菜单项内都有一个 红色框 ,则只能单击此区域,而不能单击整个菜单项区域本身(例如,如果用户单击选中标记所在区域或内部可单击区域的右侧或左侧,则什么也没有发生, ,如果您没有单独的颜色,则会非常混乱.)

将MenuItem绑定到ViewModel的ObservableCollection以便可以单击每个MenuItem内部的整个区域的正确方法是什么?

更新:

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

替代文本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>

解决方案

我发现将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为true,则可以在xaml中看到它如何使用DataTrigger将样式更改为现有的分隔符样式.这是MainViewModel定义MainMenu属性(视图绑定到该属性)的方式:

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

这似乎运作良好.我假设您可以对MainMenu使用ObservableCollection.我实际上是使用MEF从菜单中组成菜单,但此后菜单项本身就是静态的(即使每个菜单项的属性都不是静态的).我还使用了实现IMenuItem的AbstractMenuItem类,该类是用于实例化各个部分中的菜单项的帮助器类.

更新:

关于您的颜色问题,此线程有帮助吗?

When I bind Menu Items with an ObservableCollection, only the "inner" area of the MenuItem is clickable:

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

In my View I have this menu:

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

Then I bind it with this 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>

Since each ViewModel in the ObservableCollection ManageMenuPageItemViewModels has a property Title and IdCode, the above code works fine at first sight.

HOWEVER, the problem is that the MenuItem in the DataTemplate is actually inside another MenuItem (as if it is being bound twice) so that in the above DataTemplate with Background="Red" there is a Red box inside each menu item and only this area can be clicked, not the whole menu item area itself (e.g. if the user clicks on the area where the checkmark is or to the right or left of the inner clickable area, then nothing happens, which, if you don't have a separate color is very confusing.)

What is the correct way to bind MenuItems to an ObservableCollection of ViewModels so that the whole area inside each MenuItem is clickable?

UPDATE:

So I made the following changes based on advice below and now have this:

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

I have only a TextBlock inside my DataTemplate, but I still can't "color the whole MenuItem" but just the TextBlock:

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

And I put the Command binding into Menu.ItemContainerStyle but they don't fire now:

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

解决方案

I found using MVVM with MenuItems to be very challenging. The rest of my application uses DataTemplates to pair the View with the ViewModel, but that just doesn't seem to work with Menus because of exactly the reasons you've described. Here's how I eventually solved it. My View looks like this:

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

If you notice, I defined an interface called IMenuItem, which is the ViewModel for a MenuItem. Here's the code for that:

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

Notice that the IMenuItem defines IEnumerable Items, which is how you get sub-menus. Also, the IsSeparator is a way to define separators in the menu (another tough little trick). You can see in the xaml how it uses a DataTrigger to change the style to the existing separator style if IsSeparator is true. Here's how MainViewModel defines the MainMenu property (that the view binds to):

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

This seems to work well. I assume you could use an ObservableCollection for the MainMenu. I'm actually using MEF to compose the menu out of parts, but after that the items themselves are static (even though the properties of each menu item are not). I also use an AbstractMenuItem class that implements IMenuItem and is a helper class to instantiate menu items in the various parts.

UPDATE:

Regarding your color problem, does this thread help?

这篇关于如何将ViewModels的ObservableCollection绑定到MenuItem?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆