What is the best way in MVVM to build a menu that displays various pages?

Edward Tanguay picture Edward Tanguay · Jun 19, 2009 · Viewed 8.1k times · Source

I want to build a simple application with the MVVM pattern.

This application will have two main parts:

  • menu on top
  • content below

The navigation will be simple:

  • each menu item (e.g. "Manage Customers" or "View Reports") will fill the content area with a new page that has some particular functionality

I have done this before with code behind where the code-behind event-handler for menu items had all pages loaded and the one that should be displayed was loaded in as a child of a StackPanel. This, however, will not work in MVVM since you don't want to be manually filling a StackPanel but displaying e.g. a "PageItem" object with a DataTemplate, etc.

So those of you who have made a simple click-menu application like this with MVVM, what was your basic application structure? I'm thinking along these lines:

MainView.xaml:

<DockPanel LastChildFill="False">

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

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

</DockPanel>

where the Menu is filled with a collection of "PageItems" and the DataTemplate displays the Title of each "PageItem object" as the Header of each MenuItem.

And the ContentControl will be filled with a View/ViewModel pair which has full functionality, but am not sure on this.

Answer

Nir picture Nir · Jun 20, 2009

First, I think you should keep the code-behind event handler, there's no point in changing a simple 2 line event handler to a complex command driven monster for no practical reason (and don't say testebility, this is the main menu, it will be tested every time you run the app).

Now, if you do want to go the pure MVVM route, all you have to do it to make your menu fire a command, first, in some resource section add this style:

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

This style will make the menu item fire a the SwitchViewCommand on the attached view model with the MenuItem's DataContext as the command parameter.

The actual view is the same as your code with an additional reference to that style as the ItemContainerStyle (so it applies to the menu item and not the content of the DataTemplate):

<DockPanel LastChildFill="False">

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

Now in the view model you need (I used strings because I don't have your PageItem code):

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

And use whatever command class you use to make the command call this code:

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

Now, when the user clicks a menu item the menu item will call the SwitchViewCommand with the page item as the parameter.

The command will call the DoSwitchViewCommand that will set the SelectedPageItem property

The property will raise the NotifyPropertyChanged that will make the UI update via data binding.

Or, you can write a 2 line event handler, your choice