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
相关产品推荐
相关产品推荐

