I'm launching an external application from a ContextMenu, and I must block the the source application while the target application is running. To achieve this I'm using Process.WaitForExit()
to avoid the source application responding to events.
The problem is the context menu is still ahead the target application. Let's see it with a simple example:
This is the code I'm using for the example.
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();
}
How could I make the ContextMenu to disappear before the target application is displayed?
One possible solution is to start process when menu is closed:
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();
}
}
};
}
Tested and seems to work, but I doubt if idea with blocking UI thread for a long time is a good idea.
After some tests, this seems to work ok:
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;
}
Since (in the comments) you say you are using ICommand
s, I guess you are binding them in XAML and don't want to lose that.
A general-purpose way (ugly, and won't allow you to have CanExecute
bound to the Enabled
state of the menuitem, but the least ugly I could figure out in so little time) could be binding them to another property (for example: Tag
), while also binding to a single Click
handler. Something like:
In the XAML:
<MenuItem Header="Whatever" Tag="{Binding MyCommand}" Click="MenuItemsClick"
<MenuItem Header="Other Item" Tag="{Binding OtherCommand}" Click="MenuItemsClick" />
In the code-behind:
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;
}
Blocking the UI thread (drawing, Window interaction) makes horrible UX: it looks like the application is frozen, which it actually is. I would go like this given the constraints:
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();
}
Disabling the Window itself (IsEnabled = false
) will achieve "I must block the source application" by not letting the user interact with your app, other than Moving, Resizing and Closing it. If you need to block exiting as well you can do so like this:
InitializeComponent();
Closing += (sender, e) => {
if (!IsEnabled) {
MessageBox.Show("Sorry, you must close Notepad to exit the application");
e.Cancel = true;
}
};
It should be also general courtesy to the user to indicate you're waiting for notepad and once that's finished (closed) your app will be usable again, I did this in the window Title
for simplicity and due to lack of any controls in the demo app.
Based on the great ideas given by Jcl...
I found a simple solution using custom MenuItems for the menu. It delays the MenuItem.Click()
event until the parent ContextMenu
is closed.
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With