WPF Modal Window using ShowDialog() Blocks All Other Windows

wpf

My application has several independent "top-level" windows, which all have completely different functions/workflows.

I am currently using ShowDialog() to make a WPF Window modal. The modal window is a child of one of the main windows. However, it is blocking all the top-level windows once it is open. I would like the dialog to block ONLY the parent window it was launched from. Is this possible?

I'm not sure if it matters, but the window that opens the dialog is the initial window of the app–so all other top-level windows are opened from it.

Best Answer

I had the same problem and implemented the modal dialog behavior as described in this post: http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf

I also tried a multiple UI thread approach, but this caused problems with third-party libraries (caliburn micro & telerik wpf controls), since they are not built to be used in multiple UI threads. It is possible to make them work with multiple UI threads, but I prefer a simpler solution...

If you implement the dialog as described, you can not use the DialogResult property anymore, since it would cause a "DialogResult can be set only after Window is created and shown as dialog" exception. Just implement your own property and use it instead.

You need the following windows API reference:

/// <summary>
/// Enables or disables mouse and keyboard input to the specified window or control. 
/// When input is disabled, the window does not receive input such as mouse clicks and key presses. 
/// When input is enabled, the window receives all input.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="bEnable"></param>
/// <returns></returns>
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

Then use this:

// get parent window handle
IntPtr parentHandle = (new WindowInteropHelper(window.Owner)).Handle;
// disable parent window
EnableWindow(parentHandle, false);
// when the dialog is closing we want to re-enable the parent
window.Closing += SpecialDialogWindow_Closing;
// wait for the dialog window to be closed
new ShowAndWaitHelper(window).ShowAndWait();
window.Owner.Activate();

This is the event handler which re-enables the parent window, when the dialog is closed:

private void SpecialDialogWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    var win = (Window)sender;
    win.Closing -= SpecialDialogWindow_Closing;
    IntPtr winHandle = (new WindowInteropHelper(win)).Handle;
    EnableWindow(winHandle, false);

    if (win.Owner != null)
    {
        IntPtr parentHandle = (new WindowInteropHelper(win.Owner)).Handle;
        // reenable parent window
        EnableWindow(parentHandle, true);
    }
}

And this is the ShowAndWaitHelper needed to achieve the modal dialog behavior (this blocks the execution of the thread, but still executes the message loop.

private sealed class ShowAndWaitHelper
{
    private readonly Window _window;
    private DispatcherFrame _dispatcherFrame;
    internal ShowAndWaitHelper(Window window)
    {
        if (window == null)
        {
            throw new ArgumentNullException("window");
        }
        _window = window;
    }
    internal void ShowAndWait()
    {
        if (_dispatcherFrame != null)
        {
            throw new InvalidOperationException("Cannot call ShowAndWait while waiting for a previous call to ShowAndWait to return.");
        }
        _window.Closed += OnWindowClosed;
        _window.Show();
        _dispatcherFrame = new DispatcherFrame();
        Dispatcher.PushFrame(_dispatcherFrame);
    }
    private void OnWindowClosed(object source, EventArgs eventArgs)
    {
        if (_dispatcherFrame == null)
        {
            return;
        }
        _window.Closed -= OnWindowClosed;
        _dispatcherFrame.Continue = false;
        _dispatcherFrame = null;
    }
}
Related Topic