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

时间:2016-03-31 14:20:07

标签: c# .net wpf contextmenu

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

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

enter image description here

这是我用于示例的代码。

    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消失?

5 个答案:

答案 0 :(得分:1)

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

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绑定到菜单项的Enabled状态,但是我能想到的最难看的东西time)可以将它们绑定到另一个属性(例如:Tag),同时还绑定到单个Click处理程序。类似的东西:

在XAML中:

<MenuItem Header="Whatever" Tag="{Binding MyCommand}" Click="MenuItemsClick"         
<MenuItem Header="Other Item" Tag="{Binding OtherCommand}" Click="MenuItemsClick" />

在代码隐藏中:

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;
}

答案 1 :(得分:1)

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

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线程的想法是个好主意。

答案 2 :(得分:1)

基于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();
    }
}

答案 3 :(得分:1)

阻止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();
}

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

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

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

答案 4 :(得分:0)

注意:这不会阻止主要应用程序,所以如果有必要,这个答案对你不起作用

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

中所述
  

WaitForExit()使当前线程等到关联   流程终止。它应该在所有其他方法之后调用   呼吁这个过程。要避免阻止当前线程,请使用   退出的活动。

因此您需要将代码更改为

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
    }