WPF:如何动态生成ContextMenu

时间:2022-11-23 08:40:23

I have ListBox (with SelectionMode=Extended) that has multiple items and I want to to add context menu feature. The problem is how to dynamically create context menu based on some conditions. Eg. I'd like to show regular context menu if only one item is selected but to show other context menu (eg. with some new items added) when more than one item is selected. In addition, I'd like to create third kind of context menu if among the selected item is at least one that has some property set. Etc... there can be multiple conditions like these.

我有ListBox(带SelectionMode = Extended)有多个项目,我想添加上下文菜单功能。问题是如何根据某些条件动态创建上下文菜单。例如。如果只选择了一个项目,我想显示常规上下文菜单,但是当选择多个项目时,显示其他上下文菜单(例如,添加了一些新项目)。另外,如果所选项目中至少有一个具有一些属性集,我想创建第三种上下文菜单。等等......可能有多种这样的条件。

Basically I need to dynamically generate context menu right after user has right-click the mouse button but just before the menu is actually shown. Is this possible?

基本上我需要在用户右键单击鼠标按钮之后立即动态生成上下文菜单,但在菜单实际显示之前。这可能吗?

3 个解决方案

#1


10  

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.

我知道这是一个老问题。似乎有一个非常简单的答案解决了MV在MVVM场景中的原始问题,因为ContextMenu类支持通过ItemsSource属性进行绑定。

Hope it helps someone encountering this.

希望它可以帮助有人遇到这个。

XAML

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.

在ViewModel中,您可以根据当前应用程序状态动态修改“ItemList”属性。

#2


6  

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

我找到了问题的答案,它是ContextMenuOpening事件。基本上我需要处理这个事件,并根据当前的应用程序状态进行菜单调整。更多细节:https://msdn.microsoft.com/en-us/library/Bb613568(v = vs.100).aspx

#3


3  

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上创建数据触发器以设置要使用的ContextMenu,而不是我在下面所做的,我建议绑定到视图模型上的属性或后面的代码,因为它可能在xaml中变得非常混乱。此处的实现检查是否只选择了一个项目,在这种情况下切换到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.

如果ViewModel确实对哪个菜单有兴趣(听起来不像它但很难在不知道完整上下文的情况下判断),那么您可以公开一些属性,让您在ViewModel中决定要显示哪个ContextMenu。虽然您不是单独的布尔属性,但您最有可能想要创建一个类,以确保在任何给定时间只有一个布尔值为真。

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
}

#1


10  

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.

我知道这是一个老问题。似乎有一个非常简单的答案解决了MV在MVVM场景中的原始问题,因为ContextMenu类支持通过ItemsSource属性进行绑定。

Hope it helps someone encountering this.

希望它可以帮助有人遇到这个。

XAML

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.

在ViewModel中,您可以根据当前应用程序状态动态修改“ItemList”属性。

#2


6  

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

我找到了问题的答案,它是ContextMenuOpening事件。基本上我需要处理这个事件,并根据当前的应用程序状态进行菜单调整。更多细节:https://msdn.microsoft.com/en-us/library/Bb613568(v = vs.100).aspx

#3


3  

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上创建数据触发器以设置要使用的ContextMenu,而不是我在下面所做的,我建议绑定到视图模型上的属性或后面的代码,因为它可能在xaml中变得非常混乱。此处的实现检查是否只选择了一个项目,在这种情况下切换到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.

如果ViewModel确实对哪个菜单有兴趣(听起来不像它但很难在不知道完整上下文的情况下判断),那么您可以公开一些属性,让您在ViewModel中决定要显示哪个ContextMenu。虽然您不是单独的布尔属性,但您最有可能想要创建一个类,以确保在任何给定时间只有一个布尔值为真。

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
}