启动外部进程后,WPF ContextMenu仍然可见

我正在从ContextMenu启动外部应用程序,我必须在目标应用程序运行时阻止源应用程序。 为了实现这一点,我使用Process.WaitForExit()来避免源应用程序响应事件。

问题是上下文菜单仍然领先于目标应用程序。 让我们看一个简单的例子:

在此处输入图像描述

这是我用于示例的代码。

  public MainWindow() { InitializeComponent(); this.ContextMenu = new ContextMenu(); MenuItem menuItem1 = new MenuItem(); menuItem1.Header = "Launch notepad"; menuItem1.Click += MyMenuItem_Click; this.ContextMenu.Items.Add(menuItem1); } void MyMenuItem_Click(object sender, RoutedEventArgs e) { Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.StartInfo.CreateNoWindow = false; p.Start(); p.WaitForExit(); p.Close(); } 

如何在显示目标应用程序之前使ContextMenu消失?

一种可能的解决方案是在菜单关闭时启动进程:

 bool _start; public MainWindow() { InitializeComponent(); ContextMenu = new ContextMenu(); var menuItem = new MenuItem() { Header = "Launch notepad" }; menuItem.Click += (s, e) => _start = true; ContextMenu.Items.Add(menuItem); ContextMenu.Closed += (s, e) => { if (_start) { _start = false; using (var process = new Process() { StartInfo = new ProcessStartInfo("notepad.exe") { CreateNoWindow = false } }) { process.Start(); process.WaitForExit(); } } }; } 

经过测试似乎可行,但我怀疑是否有长时间阻止UI线程的想法是个好主意。

经过一些测试,这似乎工作正常:

 void LaunchAndWaitForProcess(object sender, RoutedEventArgs routedEventArgs) { this.ContextMenu.Closed -= LaunchAndWaitForProcess; Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.StartInfo.CreateNoWindow = false; p.Start(); p.WaitForExit(); p.Close(); } void MyMenuItem_Click(object sender, RoutedEventArgs e) { this.ContextMenu.Closed += LaunchAndWaitForProcess; } 

因为(在评论中)你说你正在使用ICommand ,我想你是在XAML中绑定它们并且不想失去它。

一个通用的方法(丑陋,并且不允许你将CanExecute绑定到menuitem的Enabled状态,但是我能在如此短的时间内弄清楚最不丑陋)可以将它们绑定到另一个属性(例如: Tag ),同时还绑定到单个Click处理程序。 就像是:

在XAML中:

  

在代码隐藏中:

 private ICommand _launchCommand; private object _launchCommandParameter; void ExecuteContextMenuCommand(object sender, RoutedEventArgs routedEventArgs) { this.ContextMenu.Closed -= ExecuteContextMenuCommand; if(_launchCommand != null && _launchCommand.CanExecute(_launchCommandParameter)) _launchCommand.Execute(_launchCommandParameter); } void MenuItemsClick(object sender, RoutedEventArgs e) { var mi = (sender as MenuItem); if(mi == null) return; var command = mi.Tag as ICommand; if(command == null) return; _launchCommand = command; _launchCommandParameter = mi.CommandParameter; this.ContextMenu.Closed += ExecuteContextMenuCommand; } 

阻止UI线程(绘图,窗口交互)会产生可怕的UX:它看起来像应用程序被冻结,实际上它是。 鉴于约束条件,我会这样做:

 void MyMenuItem_Click(object sender, RoutedEventArgs e) { Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.StartInfo.CreateNoWindow = false; Title = "Waiting for Notepad to be closed."; executeBlocking(p, () => { Title = "Finished work with Notepad, you may resume your work."; }); } void executeBlocking(Process p, Action onFinish) { IsEnabled = false; BackgroundWorker processHandler = new BackgroundWorker(); processHandler.DoWork += (sender, e) => { p.Start(); p.WaitForExit(); // block in background p.Close(); }; processHandler.RunWorkerCompleted += (sender, e) => { IsEnabled = true; onFinish.Invoke(); }; processHandler.RunWorkerAsync(); } 

禁用Window本身( IsEnabled = false )将实现“我必须阻止源应用程序” ,不允许用户与您的应用程序交互,除了移动,resize和关闭它。 如果你需要阻止退出,你可以这样做:

 InitializeComponent(); Closing += (sender, e) => { if (!IsEnabled) { MessageBox.Show("Sorry, you must close Notepad to exit the application"); e.Cancel = true; } }; 

对用户来说也应该一般礼貌地表示你正在等待记事本,一旦完成(关闭)你的应用程序将再次可用,我在窗口Title这样做是为了简单和由于演示中没有任何控件应用程序。

基于Jcl给出的伟大构想……

我找到了一个使用自定义MenuItems进行菜单的简单解决方案。 它会延迟MenuItem.Click()事件,直到关闭父ContextMenu

 class MyMenuItem : MenuItem { protected override void OnClick() { ContextMenu parentContextMenu = Parent as ContextMenu; parentContextMenu.Closed += ContextMenu_Closed; parentContextMenu.IsOpen = false; } void ContextMenu_Closed(object sender, RoutedEventArgs e) { ContextMenu parent = Parent as ContextMenu; parent.Closed -= ContextMenu_Closed; base.OnClick(); } } 

注意:这不会阻止主应用程序,因此如果有必要,此答案将不适合您

ui线程被阻止,因此无法取消上下文菜单。 为什么你可以通过其他程序看到它可能与它在屏幕上的绘制方式有关。 如Process.WaitForExit文档中所述

WaitForExit()使当前线程等待,直到关联的进程终止。 应该在进程上调用所有其他方法之后调用它。 要避免阻止当前线程,请使用Exited事件。

所以你需要将代码更改为此

 void MyMenuItem_Click(object sender, RoutedEventArgs e) { Process p = new Process(); p.StartInfo.FileName = "notepad.exe"; p.StartInfo.CreateNoWindow = false; p.EnableRaisingEvents = true; p.Exited += new EventHandler(myProcess_Exited); p.Start(); } 

然后

 private void myProcess_Exited(object sender, System.EventArgs e) { //Do whatever logic you have for when the program exits }