WPF Modal Window using ShowDialog() Blocks All Other Windows

前端 未结 2 622
梦如初夏
梦如初夏 2020-12-24 07:18

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

I am currently using ShowDialog() to make a WP

相关标签:
2条回答
  • 2020-12-24 07:57

    One option is to start the windows that you don't want affected by the dialog on a different thread. This may result in other issues for your application, but if those windows do really encapsulate different workflows, that may not be an issue. Here is some sample code I wrote to verify that this works:

    <Window x:Class="ModalSample.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="{Binding Identifier}" Height="150" Width="150">
        <StackPanel>
            <TextBox Text="{Binding Identifier}" />
            <Button Content="Open Normal Child" Click="OpenNormal_Click" />
            <Button Content="Open Independent Child" Click="OpenIndependent_Click" />
            <Button Content="Open Modal Child" Click="OpenModal_Click" />
        </StackPanel>
    </Window>
    

    using System.ComponentModel;
    using System.Threading;
    using System.Windows;
    
    namespace ModalSample
    {
        /// <summary>
        /// Interaction logic for MyWindow.xaml
        /// </summary>
        public partial class MyWindow : INotifyPropertyChanged
        {
            public MyWindow()
            {
                InitializeComponent();
                DataContext = this;
            }
    
            private int child = 1;
    
            private string mIdentifier = "Root";
            public string Identifier
            {
                get { return mIdentifier; }
                set
                {
                    if (mIdentifier == value) return;
                    mIdentifier = value;
                    if (PropertyChanged != null)
                        PropertyChanged(this, new PropertyChangedEventArgs("Identifier"));
                }
            }
    
            private void OpenNormal_Click(object sender, RoutedEventArgs e)
            {
                var window = new MyWindow {Identifier = Identifier + "-N" + child++};
                window.Show();
            }
    
            private void OpenIndependent_Click(object sender, RoutedEventArgs e)
            {
                var thread = new Thread(() =>
                    {
                        var window = new MyWindow {Identifier = Identifier + "-I" + child++};
                        window.Show();
    
                        window.Closed += (sender2, e2) => window.Dispatcher.InvokeShutdown();
    
                        System.Windows.Threading.Dispatcher.Run();
                    });
    
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
            }
    
            private void OpenModal_Click(object sender, RoutedEventArgs e)
            {
                var window = new MyWindow { Identifier = Identifier + "-M" + child++ };
                window.ShowDialog();
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    }
    

    I sourced this blog post for running a WPF window on a different thread.

    0 讨论(0)
  • 2020-12-24 07:59

    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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题