WPF中如何在打开新窗口前关闭上下文菜单?
解决WPF上下文菜单在重型窗口实例化时保持打开的问题
问题场景
我有一个依赖重型库的WPF窗口,完全渲染耗时较长,该库封装在UserControl中。窗口通过父窗口的上下文菜单命令打开,采用MVVM模式,需要在新窗口关闭时通过DialogResult访问其ViewModel。但点击上下文菜单项后,菜单会保持打开状态直到新窗口实例化完成,影响交互体验。
解决方案
核心思路是先强制关闭上下文菜单,再异步初始化并显示重型窗口,避免阻塞UI线程导致菜单无法及时关闭:
- 在命令执行的第一步,从命令参数中获取当前激活的ContextMenu并关闭它
- 使用
Dispatcher.BeginInvoke异步执行窗口创建逻辑,让UI线程先处理菜单关闭操作,再处理重型窗口的初始化
修改后的关键代码
主窗口XAML(传递ContextMenu作为命令参数)
<Image Source="{Binding ImagePath}" Height="100" Width="100"> <Image.ContextMenu> <ContextMenu> <MenuItem Header="Open Window" Command="{x:Static local:MainWindow.ShowMyDialogCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}}" /> </ContextMenu> </Image.ContextMenu> </Image>
主窗口代码(调整命令执行逻辑)
public partial class MainWindow : Window { public static RoutedCommand ShowMyDialogCommand { get; } = new RoutedCommand("ShowMyDialogCommand", typeof(MainWindow)); private readonly MainVM myMainVM; public MainWindow() { InitializeComponent(); myMainVM = new MainVM(); DataContext = myMainVM; var showMyDialogCommandBinding = new CommandBinding(ShowMyDialogCommand, ExecuteShowMyDialogCommand, CanExecuteShowMyDialogCommand); this.CommandBindings.Add(showMyDialogCommandBinding); } private void CanExecuteShowMyDialogCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true; private void ExecuteShowMyDialogCommand(object sender, ExecutedRoutedEventArgs e) { // 第一步:立即关闭上下文菜单 if (e.Parameter is ContextMenu contextMenu) { contextMenu.IsOpen = false; } // 第二步:异步初始化窗口,避免阻塞UI线程 Dispatcher.BeginInvoke(new Action(() => { ViewerVM vm = new ViewerVM(); var okDialog = new OkDialog() { Title = "Viewer Dialog", DataContext = vm }; bool? dialogResult = okDialog.ShowDialog(); if (dialogResult == true) { this.myMainVM.HandleData(vm); } }), DispatcherPriority.Background); } }
原代码其他部分(无需修改)
MainVM
public class MainVM : ObservableObject { private string myImagePath; public MainVM() { myImagePath = "flower.jpg"; } public string ImagePath { get { return myImagePath; } set { if (myImagePath == value) return; myImagePath = value; OnPropertyChanged(nameof(ImagePath)); } } public void HandleData(ViewerVM viewModel) { // 自定义处理逻辑 } }
新窗口XAML
<Window.Template> <ControlTemplate TargetType="Window"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <!-- 动态内容行 --> <RowDefinition Height="Auto" /> <!-- 静态内容行(确认/取消按钮等) --> </Grid.RowDefinitions> <!-- 动态内容 --> <ContentPresenter Grid.Row="0" /> <!-- 静态内容 --> <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right"> <Button Content="Ok" IsDefault="True" Command="{x:Static local:OkDialog.OkCommand}" /> <Button Content="Cancel" IsCancel="True" /> </StackPanel> </Grid> </ControlTemplate> </Window.Template>
新窗口代码
public partial class OkDialog : Window { public static RoutedCommand OkCommand { get; } = new RoutedCommand("OkCommand", typeof(MainWindow)); public OkDialog() { InitializeComponent(); var okCommandBinding = new CommandBinding(OkDialog.OkCommand, ExecuteOkCommand, CanExecuteOkCommand); this.CommandBindings.Add(okCommandBinding); this.DataContextChanged += OnDataContextChanged; } // 未显式设置Content时,自动使用DataContext private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) => this.Content = e.NewValue; private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = (this.DataContext as IOkDialogVM)?.CanExecuteOkCommand() ?? false; private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e) => this.DialogResult = true; }
IOkDialogVM接口
interface IOkDialogVM { bool CanExecuteOkCommand(); }
UserControl XAML
<UserControl x:Class="ContextMenuTest.Viewer" 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:ddes="http://schemas.devdept.com/winfx/2008/xaml/control" xmlns:ddgr="http://schemas.devdept.com/winfx/2008/xaml/graphics" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <ddes:Design x:Name="myDesigner" Height="300" Width="300"> <ddes:Design.Viewports> <ddes:Viewport> <ddes:Viewport.Background> <ddgr:BackgroundSettings StyleMode="Solid" TopColor="White"/> </ddes:Viewport.Background> </ddes:Viewport> </ddes:Design.Viewports> </ddes:Design> </Grid> </UserControl>
UserControl代码
public partial class Viewer : UserControl { public Viewer() { InitializeComponent(); } }
ViewerVM
public class ViewerVM : ObservableObject, IOkDialogVM { public bool CanExecuteOkCommand() => true; }
App.xaml
<Application.Resources> <DataTemplate DataType="{x:Type local:ViewerVM}"> <local:Viewer/> </DataTemplate> </Application.Resources>
内容的提问来源于stack exchange,提问作者Yannick
相关产品推荐
相关产品推荐

