Модальное окно WPF с использованием ShowDialog () блокирует все другие окна - PullRequest
18 голосов
/ 07 апреля 2010

Мое приложение имеет несколько независимых окон «верхнего уровня», которые имеют совершенно разные функции / рабочие процессы.

В настоящее время я использую ShowDialog (), чтобы сделать окно WPF модальным. Модальное окно является дочерним по отношению к одному из главных окон. Тем не менее, он блокирует все окна верхнего уровня, как только он открыт. Я хотел бы, чтобы диалог блокировал ТОЛЬКО родительское окно, из которого оно было запущено. Это возможно?

Я не уверен, имеет ли это значение, но окно, открывающее диалоговое окно, является начальным окном приложения, поэтому все остальные окна верхнего уровня открываются из него.

Ответы [ 2 ]

11 голосов
/ 07 апреля 2010

Один из вариантов - запускать окна, на которые вы не хотите воздействовать диалогом в другом потоке. Это может привести к другим проблемам для вашего приложения, но если эти окна действительно инкапсулируют различные рабочие процессы, это может не быть проблемой. Вот пример кода, который я написал, чтобы убедиться, что это работает:

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

Я получил это сообщение в блоге для запуска окна WPF в другой теме.

7 голосов
/ 09 апреля 2014

У меня была та же проблема и я реализовал модальное поведение диалога, как описано в этом посте: http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf

Я также попробовал подход с несколькими потоками пользовательского интерфейса, но это вызвало проблемы со сторонними библиотеками (caliburn micro & telerik wpf control), так как они не созданы для использования в нескольких потоках пользовательского интерфейса. Можно заставить их работать с несколькими потоками пользовательского интерфейса, но я предпочитаю более простое решение ...

Если вы реализуете диалог, как описано, вы больше не сможете использовать свойство DialogResult, поскольку это приведет к тому, что «DialogResult может быть установлен только после того, как Window будет создан и показан как диалог». Просто реализуйте свое собственное свойство и используйте его вместо этого.

Вам нужна следующая ссылка на API Windows:

/// <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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...