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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 05:30:46