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

3

8

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.

Omnipotence answered 19/6, 2009 at 15:7 Comment(0)
C
9

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

Coshow answered 20/6, 2009 at 15:52 Comment(1)
Very nice code, the FindAncestor syntax helped me to solve this: #1026842Omnipotence
S
0

i could imagine an ObservableCollection in the VM, that holds all the pages to be callable from the menu. Then bind an ItemsControl And the ContentControl to it to make the ContentControl always show the CurrentItem from that List. Of course, the menu will only bind to some Title property whereas the ContentControl will adopt the whole item and plug in some appropriate view according to the type.

Sauers answered 20/6, 2009 at 13:47 Comment(0)
C
0

Another option is to use a ListBox instead of a menu, style the ListBox to look like a menu and then you can bind to the selected value, like this:

<DockPanel LastChildFill="False">

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

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

</DockPanel>

Note the IsSynchronizedWithCurrentItem="True" to set the selected item and the {Binding PageItemsMainMenu/} with the trailing slash to use it.

Coshow answered 20/6, 2009 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.