24 votes

La fenêtre modale WPF utilisant ShowDialog() bloque toutes les autres fenêtres

Mon application comporte plusieurs fenêtres "de haut niveau" indépendantes, qui ont toutes des fonctions/flux de travail complètement différents.

J'utilise actuellement ShowDialog() pour rendre une fenêtre WPF modale. La fenêtre modale est un enfant de l'une des fenêtres principales. Cependant, elle bloque toutes les fenêtres de niveau supérieur lorsqu'elle est ouverte. Je voudrais que la boîte de dialogue ne bloque QUE la fenêtre parent à partir de laquelle elle a été lancée. Cela est-il possible ?

Je ne sais pas si cela a de l'importance, mais la fenêtre qui ouvre la boîte de dialogue est la fenêtre initiale de l'application - toutes les autres fenêtres de niveau supérieur sont donc ouvertes à partir d'elle.

12voto

DELUXEnized Points 363

J'ai eu le même problème et j'ai implémenté le comportement du dialogue modal comme décrit dans ce 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

J'ai également essayé une approche à threads multiples, mais cela a causé des problèmes avec les bibliothèques tierces (caliburn micro & telerik wpf controls), puisqu'elles ne sont pas construites pour être utilisées dans des threads multiples. Il est possible de les faire fonctionner avec plusieurs threads UI, mais je préfère une solution plus simple...

Si vous implémentez le dialogue comme décrit, vous ne pouvez plus utiliser la propriété DialogResult, car cela provoquerait une exception "DialogResult ne peut être défini qu'après la création de la fenêtre et son affichage en tant que dialogue". Implémentez simplement votre propre propriété et utilisez-la à la place.

Vous avez besoin de la référence suivante de l'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);

Alors utilisez ceci :

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

Il s'agit du gestionnaire d'événement qui réactive la fenêtre parent, lorsque le dialogue est fermé :

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

Et voici le ShowAndWaitHelper nécessaire pour obtenir le comportement de dialogue modal (il bloque l'exécution du thread, mais exécute toujours la boucle de messages.

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

10voto

Joseph Sturtevant Points 6597

Une option consiste à démarrer les fenêtres qui ne doivent pas être affectées par la boîte de dialogue sur un autre fil de discussion. Cela peut entraîner d'autres problèmes pour votre application, mais si ces fenêtres encapsulent réellement des flux de travail différents, cela peut ne pas être un problème. Voici un exemple de code que j'ai écrit pour vérifier que cela fonctionne :

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

Je me suis approvisionné cet article de blog pour exécuter une fenêtre WPF sur un autre thread.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X