Search code examples
c#wpfxamlmvvmhierarchicaldatatemplate

How to get the Content of selected Menu Item?


Summary of the problem

Goal

My goal is to create a Navigation menu in WPF.

The navigation items are built in the View Model using Nodes.

Selected SubItem of the menu will display the content of the selected Node.

Expected and actual result

This is what I have built so far:

enter image description here

Error

Now I need to present the selected MenuItem to ContentPresenter - and this is where I have the problem with.

What I have tried

This is my current code

XAML

<!--Menu-->
<Menu x:Name="NavigationTreeView" IsMainMenu="True" ItemsSource="{Binding Navigation}" Grid.Row="0">
    <Menu.Resources>
        <Style TargetType="{x:Type MenuItem}">
            <Setter Property="Header" Value="{Binding Path=Title}" />
            <Setter Property="Template" Value="{StaticResource VsMenuTop}" />
        </Style>
        <HierarchicalDataTemplate DataType="{x:Type nav:PageViewModel}" ItemsSource="{Binding Children}" />
    </Menu.Resources>
</Menu>

<!--Content-->
<ContentPresenter Grid.Row="1" Content="{Binding ElementName=NavigationTreeView, Path=SelectedItem.Content}" />

This will not work, because I am not able to bind it to Menu using this line here: SelectedItem.Content - and the Menu does not have a property SelectedItem.

Alternative

My alternative option would be to use ListView, because it contains a property SelectedItem. But preferably I'd like to use Menu.

Question

How can I get the selected item from the Hierarchical Data Template?


Solution

  • The Menu control does not have a notion of a selected item, as its MenuItems are essentially buttons that should execute an action when clicked. You can set the IsCheckable property to true to be a able to check menu items like a CheckBox, but I guess that does not fit your use-case here.

    Since you probably want to display different content depending on which menu item was clicked, you could use a command, that is executed when you click a MenuItem and gets the corresponding view model as parameter and sets a property on a view model that can be bound by the ContentPresenter.

    Introduce a Selected property of type PageViewModel and a Select command in your main view model that contains the Navigation porperty. The command sets the Selected property.

    public class MainViewModel : INotifyPropertyChanged
    {
       public MainViewModel()
       {
          Selected = new RelayCommand<PageViewModel>(pageViewModel => Selected = pageViewModel);
          // ...other code.
       }
    
       public DelegateCommand<PageViewModel> Select { get; }
       
       private PageViewModel _selected;
       public PageViewModel Selected
       {
          get => _selected;
          private set
          {
             if (_selected == value)
                return;
       
             _selected = value;
             OnPropertyChanged();
          }
       }
       
       // ...other code.
    }
    

    You can replace the RelayCommand by the command implementation that you have at your disposal.

    Then, you can adapt your style to bind the command on the main view model (either using ElementName or RelativeSource to the data context) and to bind the view model of the clicked MenuItem as command parameter. This way it is passed to the command, which sets it as Selected.

    <Style TargetType="{x:Type MenuItem}">
       <Setter Property="Header" Value="{Binding Path=Title}" />
       <Setter Property="Template" Value="{StaticResource VsMenuTop}" />
       <Setter Property="Command" Value="{Binding DataContext.Select, ElementName=NavigationTreeView}"/>
       <Setter Property="CommandParameter" Value="{Binding}"/>
    </Style>
    

    Bind the Content of your ContentPresenter to the Selected property on the main view model.

    <ContentPresenter Grid.Row="1" Content="{Binding ElementName=NavigationTreeView, Path=Selected}" />