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