【问题标题】:WPF Modal Window using ShowDialog() Blocks All Other Windows使用 ShowDialog() 的 WPF 模态窗口阻止所有其他窗口
【发布时间】:2011-02-05 07:34:08
【问题描述】:

我的应用程序有几个独立的“顶级”窗口,它们都有完全不同的功能/工作流程。

我目前正在使用 ShowDialog() 来制作 WPF 窗口模式。模态窗口是其中一个主窗口的子窗口。但是,一旦打开,它就会阻止所有顶级窗口。我希望对话框仅阻止它启动的父窗口。这可能吗?

我不确定这是否重要,但打开对话框的窗口是应用程序的初始窗口 - 所以所有其他顶级窗口都是从它打开的。

【问题讨论】:

    标签: wpf


    【解决方案1】:

    我遇到了同样的问题,并按照本文所述实现了模态对话框行为: http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf

    我也尝试了多 UI 线程方法,但这会导致第三方库(caliburn micro 和 telerik wpf 控件)出现问题,因为它们不是为在多个 UI 线程中使用而构建的。可以让它们与多个 UI 线程一起工作,但我更喜欢更简单的解决方案...

    如果按照描述实现对话框,则不能再使用 DialogResult 属性,因为它会导致“只有在创建 Window 并显示为对话框后才能设置 DialogResult”异常。只需实现您自己的属性并使用它。

    您需要以下 Windows API 参考:

    /// <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);
    

    然后使用这个:

    // 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();
    

    这是在对话框关闭时重新启用父窗口的事件处理程序:

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

    这就是实现模态对话框行为所需的 ShowAndWaitHelper(这会阻塞线程的执行,但仍会执行消息循环。

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

    【讨论】:

    • 您的回答非常有帮助。我为此做了一个async Task&lt;DialogResult&gt; ShowDialogAsync 扩展方法版本。
    【解决方案2】:

    一个选项是在不同线程上启动您不希望受对话框影响的窗口。这可能会导致您的应用程序出现其他问题,但如果这些窗口确实封装了不同的工作流程,那可能不是问题。这是我编写的一些示例代码,用于验证它是否有效:

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

    我使用 this blog post 在不同的线程上运行 WPF 窗口。

    【讨论】:

      猜你喜欢
      • 2015-05-29
      • 2023-03-09
      • 1970-01-01
      • 2015-06-15
      • 1970-01-01
      • 1970-01-01
      • 2019-06-15
      • 2012-10-23
      • 1970-01-01
      相关资源
      最近更新 更多