MVVM Dynamic Menu UI from binding with ViewModel

45,853

Solution 1

Try something like this:

public class MenuItemViewModel
{
    public MenuItemViewModel()
    {
        this.MenuItems = new List<MenuItemViewModel>();
    }

    public string Text { get; set; }

    public IList<MenuItemViewModel> MenuItems { get; private set; }
}

Assume that your DataContext has a property called MenuItems which is a list of MenuItemViewModel. Something like this should work, then:

<Window x:Class="WpfApplication1.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:self="clr-namespace:WpfApplication1"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <HierarchicalDataTemplate DataType="{x:Type self:MenuItemViewModel}"
                                  ItemsSource="{Binding Path=MenuItems}">
            <ContentPresenter Content="{Binding Path=Text}" />
        </HierarchicalDataTemplate>
    </Window.Resources>
    <DockPanel>
        <Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
        <Grid />
    </DockPanel>
</Window>

Solution 2

This should get you where you are going

<UserControl x:Class="WindowsUI.Views.Default.MenuView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:ViewModels="clr-namespace:WindowsUI.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
    <Style TargetType="{x:Type MenuItem}">
        <Setter Property="Header" Value="{Binding Path=DisplayName}"/>
        <Setter Property="Command" Value="{Binding Path=Command}"/>
    </Style>
    <HierarchicalDataTemplate 
        DataType="{x:Type ViewModels:MenuItemViewModel}"
        ItemsSource="{Binding Path=Items}">
    </HierarchicalDataTemplate>
</UserControl.Resources>
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Items}"/>

Note that in my example, my menu Item has a property of type ICommand called Command.

Solution 3

This solution doesn't need any code in code behind and that makes it simpler solution.

        <Menu>
            <MenuItem ItemsSource="{Binding Path=ChildMenuItems}" Header="{Binding Path=Header}">
                <MenuItem.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type vm:MenuItemViewModel}" ItemsSource="{Binding ChildMenuItems}">
                        <MenuItem Header="{Binding Path=Header}" Command="{Binding Path=Command}"/>
                    </HierarchicalDataTemplate>
                    <DataTemplate DataType="{x:Type vm:SeparatorViewModel}">
                        <Separator>
                            <Separator.Template>
                                <ControlTemplate>
                                    <Line X1="0" X2="1" Stroke="Black" StrokeThickness="1" Stretch="Fill"/>
                                </ControlTemplate>
                            </Separator.Template>
                        </Separator>
                    </DataTemplate>
                </MenuItem.Resources>
            </MenuItem>
        </Menu>

And MenuItem is represented as:

public class MenuItemViewModel : BaseViewModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MenuItemViewModel"/> class.
        /// </summary>
        /// <param name="parentViewModel">The parent view model.</param>
        public MenuItemViewModel(MenuItemViewModel parentViewModel)
        {
            ParentViewModel = parentViewModel;
            _childMenuItems = new ObservableCollection<MenuItemViewModel>();
        }

        private ObservableCollection<MenuItemViewModel> _childMenuItems;
        /// <summary>
        /// Gets the child menu items.
        /// </summary>
        /// <value>The child menu items.</value>
        public ObservableCollection<MenuItemViewModel> ChildMenuItems
        {
            get
            {
                return _childMenuItems;
            }
        }

        private string _header;
        /// <summary>
        /// Gets or sets the header.
        /// </summary>
        /// <value>The header.</value>
        public string Header
        {
            get
            {
                return _header;
            }
            set
            {
                _header = value; NotifyOnPropertyChanged("Header");
            }
        }

        /// <summary>
        /// Gets or sets the parent view model.
        /// </summary>
        /// <value>The parent view model.</value>
        public MenuItemViewModel ParentViewModel { get; set; }

        public virtual void LoadChildMenuItems()
        {

        }
    }

The concrete MenuItems can be either instantiated directly or you could make your own SubTypes through inheritance.

Solution 4

I know this is an old post but I need this plus how to bind Commands.

As to Guge's question on how to bind Commands: VMMenuItems is a property in my view model class of type

ObservableCollection<Menu>

and Menu is the class defined above. The MenuItem's Command Property is being bound to the Command Property of the Menu class. In my view model class

Menu.Command = _fou

where

private ICommand _fou;

The xaml

<ListView.ContextMenu>
    <ContextMenu ItemsSource="{Binding Path=VMMenuItems}">
           <ContextMenu.ItemContainerStyle>
                <Style TargetType="{x:Type MenuItem}">                                    
                        <Setter Property="Command" Value="{Binding Command}"/>
                  </Style>
            </ContextMenu.ItemContainerStyle>
      </ContextMenu>                    
</ListView.ContextMenu>

Solution 5

If you're wondering how to do separators it's really quite easy.

The code below is part of my ViewModel. Since XAML uses reflection all I need to do is to return 'object' which can be a MenuItemViewModel, Separator, or (if for some wierd reason I needed to) an actual MenuItem.

I'm using yield to dynamically generate the items because it just seems to read better for me. Even though I'm using yield - if the items change I still need to raise a PropertyChanged event for "ContextMenu" as usual but I don't unnecessarily generate the list until it's needed.

    public IEnumerable<object> ContextMenu
    {
        get
        {
            // ToArray() needed or else they get garbage collected
            return GetContextMenu().ToArray();
        }
    }

    public IEnumerable<object> GetContextMenu()
    {
        yield return new MenuItemViewModel()
        {
            Text = "Clear all flags",
        };

        // adds a normal 'Separator' menuitem
        yield return new Separator();

        yield return new MenuItemViewModel()
        {
            Text = "High Priority"
        };

        yield return new MenuItemViewModel()
        {
            Text = "Medium Priority"
        };

        yield return new MenuItemViewModel()
        {
            Text = "Low Priority"
        };

        yield break;
    }
Share:
45,853
Raj
Author by

Raj

yet another software junkie

Updated on July 09, 2022

Comments

  • Raj
    Raj almost 2 years

    I am working with a team on LoB application. We would like to have a dynamic Menu control, which creates the menu based on the logged in user profile. In previous development scenarios (namely ASP.NET) we use to iterate through data which describes collection and generate MenuItem dynamically. In MVVM how would I do this? Can I separate XAML view from ViewModel which describes menu elements?

    Solution:

    With inputs from commentators I were able to bind Menu dynamically with the data from ViewModel. This article was of great help too.

    XAML:

    <HierarchicalDataTemplate DataType="{x:Type self:Menu}" ItemsSource="{Binding Path=Children, UpdateSourceTrigger=PropertyChanged}">
        <ContentPresenter Content="{Binding Path=MenuText}" RecognizesAccessKey="True"/>
    </HierarchicalDataTemplate>
    
    [...]
    
    <Menu Height="21" Margin="0" Name="mainMenu" VerticalAlignment="Top" HorizontalAlignment="Stretch" 
          ItemsSource="{Binding Path=MenuItems, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource TopMenuItems}">
        <Menu.Background>
            <ImageBrush ImageSource="/Wpf.Modules;component/Images/MenuBg.jpg" />
        </Menu.Background>
    </Menu>
    

    Menu data class:

    public class Menu : ViewModelBase
    {
        public Menu()
        {
            IsEnabled = true;
            Children = new List<Menu>();
        }
    
        #region [ Menu Properties ]
    
        private bool _isEnabled;
        private string _menuText;
        private ICommand _command;
        private IList<Menu> _children;
    
        public string MenuText
        {
            get { return _menuText; }
            set
            {
                _menuText = value;
                base.OnPropertyChanged("MenuText");
            }
        }
    
        public bool IsEnabled
        {
            get { return _isEnabled; }
            set
            {
                _isEnabled = value;
                base.OnPropertyChanged("IsEnabled");
            }
        }
    
        public ICommand Command
        {
            get { return _command; }
            set
            {
                _command = value;
                base.OnPropertyChanged("Command");
            }
        }
    
        public IList<Menu> Children
        {
            get { return _children; }
            set
            {
                _children = value;
            }
        }
    
        #endregion
    }