在MVVM中,构建显示各种页面的菜单的最佳方法是什么?

8

我希望使用MVVM模式构建一个简单的应用程序。

该应用程序将有两个主要部分:

  • 顶部的菜单
  • 下面的内容

导航将很简单:

  • 每个菜单项(例如“管理客户”或“查看报告”)都将填充内容区域,并显示具有特定功能的新页面

我以前使用了代码后台实现过这个功能,其中菜单项的代码后台事件处理程序加载了所有页面,并将应该显示的页面作为StackPanel的子级加载。然而,在MVVM中,这并不起作用,因为您不想手动填充StackPanel,而是要显示例如具有DataTemplate的“PageItem”对象等。

所以,您们当中那些使用MVVM制作过这样的简单点击菜单应用程序的人,您的基本应用程序结构是什么? 我考虑以下几个方面:

MainView.xaml:

<DockPanel LastChildFill="False">

    <Menu 
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"/>

    <ContentControl 
        Content="{Binding SelectedPageItem}"/>        

</DockPanel>

菜单中填充了“PageItems”集合,DataTemplate将每个“PageItem对象”的标题显示为每个菜单项的标题。

而ContentControl将填充具有完整功能的View/ViewModel对,但对此并不确定。

3个回答

9
首先,我认为你应该保留代码后端事件处理程序,没有必要为了没有实际意义而将一个简单的两行事件处理程序变成复杂的命令驱动怪物(不要说可测试性,这是主菜单,在每次运行应用程序时都会进行测试)。
现在,如果你确实想走纯 MVVM 路线,你只需让菜单触发一个命令。首先,在某个资源部分添加此样式:
<Style x:Key="MenuItemStyle" TargetType="MenuItem">
    <Setter Property="Command" 
            Value="{Binding DataContext.SwitchViewCommand,
            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/>
    <Setter Property="CommandParameter" 
            Value="{Binding}"/>
</Style>

这个样式会使得菜单项在附加的视图模型上触发SwitchViewCommand命令,并将MenuItem的DataContext作为命令参数。

实际的视图与您的代码相同,只是增加了对该样式的引用作为ItemContainerStyle(因此它适用于菜单项而不是DataTemplate的内容):

<DockPanel LastChildFill="False">

    <Menu DockPanel.Dock="Top"
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"
        ItemContainerStyle="{StaticResource MenuItemStyle}"/>
    <ContentControl 
    Content="{Binding SelectedPageItem}"/>
</DockPanel>

现在在视图模型中,你需要这样做(我使用字符串,因为没有你的PageItem代码):

private string _selectedViewItem;
public List<string> PageItemsMainMenu { get; set; }
public string SelectedPageItem
{
    get { return _selectedViewItem; }
    set { _selectedViewItem = value; OnNotifyPropertyChanged("SelectedPageItem"); }
}
public ICommand SwitchViewCommand { get; set; }

使用您所用的任何命令类来调用此代码:

private void DoSwitchViewCommand(object parameter)
{
    SelectedPageItem = (string)parameter;
}

现在,当用户点击菜单项时,菜单项将使用页面项作为参数调用SwitchViewCommand。
该命令将调用DoSwitchViewCommand,设置SelectedPageItem属性。
该属性将引发NotifyPropertyChanged事件,通过数据绑定使UI更新。
或者,您可以编写一个两行的事件处理程序,由您选择。

非常好的代码,FindAncestor语法帮助我解决了这个问题:https://dev59.com/OnNA5IYBdhLWcg3wSrqa - Edward Tanguay

0
另一个选项是使用 ListBox 而不是菜单,将 ListBox 样式设置为看起来像菜单,然后您可以绑定到所选值,就像这样:
<DockPanel LastChildFill="False">

    <ListBox 
        ItemsSource="{Binding PageItemsMainMenu}" 
        ItemTemplate="{StaticResource MainMenuStyle}"
        IsSynchronizedWithCurrentItem="True"/>

    <ContentControl 
        Content="{Binding PageItemsMainMenu/}"/>        

</DockPanel>

请注意IsSynchronizedWithCurrentItem="True"以设置所选项目,并使用带有尾随斜杠的{Binding PageItemsMainMenu/}。

0
我可以想象在VM中有一个ObservableCollection,它包含了所有可以从菜单中调用的页面。 然后将ItemsControl和ContentControl绑定到它上面,以使ContentControl始终显示该列表中的CurrentItem。 当然,菜单只会绑定到某些Title属性,而ContentControl将采用整个项目,并根据类型插入适当的视图。

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