WPF: How to dynamically generate ContextMenu WPF: How to dynamically generate ContextMenu wpf wpf

WPF: How to dynamically generate ContextMenu


I am aware that this is an old question. It seems that there is a very simple answer which solves the OP's original problem in a MVVM scenario, because the ContextMenu class supports binding via the ItemsSource property.

Hope it helps someone encountering this.

XAML

      <ContextMenu ItemsSource="{Binding Path=ItemList, UpdateSourceTrigger=PropertyChanged}">      </ContextMenu>

In the ViewModel, you can modify the "ItemList" property dynamically according to the current application state.


I've found the answer to my question and it's ContextMenuOpening Event.Basically I need to handle this event and do menu adjustments according to the current application state. More details here: https://msdn.microsoft.com/en-us/library/Bb613568(v=vs.100).aspx


If you have a set of predefined context menus that you want to use based on specific scenarios you can always create your context menus as resources.

<Window.Resources>    <ContextMenu x:Key="Menu1">        <MenuItem>Item1</MenuItem>    </ContextMenu>    <ContextMenu x:Key="Menu2">        <MenuItem>Item1</MenuItem>        <MenuItem>Item2</MenuItem>    </ContextMenu></Window.Resources>

And then create data triggers on your ListBox to set the ContextMenu to use, rather than what I have done below I would suggest binding to properties on your view model or code behind for this as it might get very messy in xaml.The implementation here checks to see if only one item is selected and in that case switches to Menu1

<ListBox x:Name="mylist" SelectionMode="Multiple" ContextMenu="{StaticResource Menu2}" >    <ListBox.Style>        <Style TargetType="{x:Type ListBox}">            <Style.Triggers>                <DataTrigger Binding="{Binding Path=SelectedItems.Count, RelativeSource={RelativeSource Self}}" Value="1" >                    <Setter Property="ContextMenu" Value="{StaticResource ResourceKey=Menu1}" />                </DataTrigger>            </Style.Triggers>        </Style>    </ListBox.Style>    <ListBox.ItemTemplate>        <DataTemplate>            <TextBlock Text="{Binding Path=DisplayName}" />        </DataTemplate>    </ListBox.ItemTemplate></ListBox>

If the selection for which context menu to show is only of concern to the view I would suggest handling it in code behind.

public partial class MainWindow : Window{    public MainWindow()    {        // Hook up any events that might influence which menu to show        mylist.SelectionChanged += listSelectionChanged;        InitializeComponent();    }    private void listSelectionChanged(object sender, SelectionChangedEventArgs e)    {        var listBox = sender as ListBox;        if (listBox == null)            return; // Or throw something, hard        ContextMenu menuToUse;        // Logic for selecting which menu to use goes here        listBox.ContextMenu = menuToUse;    }}

While if the ViewModel does have some interest in which menu to show (doesn't sound like it but it's hard to tell without knowing the full context) you could expose some properties that let you decide in the ViewModel which ContextMenu to show. Although rather than individual Boolean properties you'd most likely want to create a class that makes sure that only one of the Booleans is true at any given time.

public class MyViewModel : INotifyPropertyChanged{    public MyViewModel()    {        SelectedItems = new ObservableCollection<string>();        SelectedItems.CollectionChanged += SelectedItemsChanged;    }    private void SelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)    {        // Logic to see which ShowMenuX property to set to true goes here    }    public ObservableCollection<string> SelectedItems { get; set; }    private bool _showMenu1 = false;    public bool ShowMenu1    {        get { return _showMenu1; }        set        {            _showMenu1 = value;            RaisePropertyChanged("ShowMenu1");        }    }    // INotifyPropertyChanged implementation goes here}