You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF如何在不打开ContextMenu的情况下加载其中的Items?

问题描述

尝试从ContextMenu中获取MenuItem时,只有至少打开过一次上下文菜单才能正常获取;未打开时会抛出异常:
System.InvalidOperationException: 'Operation is not valid while ItemCollection has no inner collection. ItemCollection is uninitialized or binding on ItemsControl.ItemSource supplied null for collection.'

经分析该问题由ItemSource绑定机制导致。实际需求是编写助手类,在子上下文菜单打开时合并父菜单的MenuItem(复制父MenuItem属性并添加到子菜单),但当前方案仅在父菜单打开过一次时有效。请问如何在不手动打开ContextMenu的情况下加载其中的Items?


演示代码

XAML

<StackPanel>
    <Rectangle Fill="Red" Height="100" Width="100">
        <Rectangle.ContextMenu>
            <ContextMenu x:Name="MainContextMenu" ItemsSource="{Binding Items}">
                <ContextMenu.ItemContainerStyle>
                    <Style TargetType="MenuItem">
                        <Setter Property="Header" Value="{Binding}"/>
                    </Style>
                </ContextMenu.ItemContainerStyle>
            </ContextMenu>
        </Rectangle.ContextMenu>
    </Rectangle>
    <Button Content="Click" Command="{Binding GetMenuItemCommand}"
            CommandParameter="{Binding ElementName=MainContextMenu}"
            Height="30" Width="100"/>
</StackPanel>

ViewModel

public class ViewModel
{
    public List<string> Items { get; }
    public MyCommand GetMenuItemCommand { get; }

    public ViewModel()
    {
        Items = new List<string>()
        {
            "a0", "a1", "a2", "a3", "a4", "a5"
        };

        GetMenuItemCommand = new MyCommand(GetMenuItem);
    }

    public void GetMenuItem(object obj)
    {
        var contextMenu = (ContextMenu)obj;

        // 此处抛出异常
        var item = contextMenu.Items[0] as MenuItem;
        var header = item.Header;
    }
}

MyCommand

public class MyCommand : ICommand
{
    private Action<object> execute;

    public MyCommand(Action<object> execute)
    {
        this.execute = execute;
    }

    public event EventHandler? CanExecuteChanged;


    public bool CanExecute(object? parameter) => true;

    public void Execute(object? parameter)
    {
        execute?.Invoke(parameter);
    }
}

说明

  • 以上为演示代码,未严格遵循MVVM规范

解决方案

方法1:直接操作绑定数据源(推荐)

既然ContextMenu的Items绑定到ViewModel的Items集合,直接基于数据源生成MenuItem更可靠,完全不依赖UI容器的初始化状态,适合菜单合并需求:

public void GetMenuItem(object obj)
{
    var contextMenu = (ContextMenu)obj;
    var viewModel = (ViewModel)contextMenu.DataContext;
    
    // 复用ContextMenu的ItemContainerStyle创建MenuItem
    var menuItem = new MenuItem();
    menuItem.Style = contextMenu.ItemContainerStyle;
    menuItem.DataContext = viewModel.Items[0];
    menuItem.ApplyTemplate(); // 确保样式生效
    
    var header = menuItem.Header;
    // 复制属性并添加到子菜单的逻辑写在这里
}

方法2:强制触发ContextMenu初始化

通过调用内部方法强制完成ItemCollection和容器的初始化,无需实际显示菜单:

public void GetMenuItem(object obj)
{
    var contextMenu = (ContextMenu)obj;
    
    // 确保ContextMenu已初始化
    if (!contextMenu.IsInitialized)
    {
        contextMenu.InitializeComponent();
    }
    
    // 监听容器生成状态,完成后获取MenuItem
    contextMenu.ItemContainerGenerator.StatusChanged += (s, e) =>
    {
        if (contextMenu.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            var item = contextMenu.Items[0] as MenuItem;
            var header = item.Header;
            // 后续处理逻辑
        }
    };
    
    // 强制刷新Items绑定
    contextMenu.Items.Refresh();
}

方法3:手动生成容器

使用ItemContainerGenerator强制生成指定位置的MenuItem容器:

public void GetMenuItem(object obj)
{
    var contextMenu = (ContextMenu)obj;
    
    // 确保数据源绑定完成
    var bindingExpr = BindingOperations.GetBindingExpression(contextMenu, ContextMenu.ItemsSourceProperty);
    bindingExpr?.UpdateTarget();
    
    // 强制生成容器
    contextMenu.ItemContainerGenerator.StartAt(0, GeneratorDirection.Forward);
    var item = contextMenu.ItemContainerGenerator.ContainerFromIndex(0) as MenuItem;
    
    if (item != null)
    {
        var header = item.Header;
        // 后续处理逻辑
    }
}

内容的提问来源于stack exchange,提问作者GeorgeKarlinzer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:20:29