web-dev-qa-db-fra.com

WPF: impossible de réutiliser la fenêtre après sa fermeture

J'essaie de garder une instance d'un Window autour et en cas de besoin appeler ShowDialog. Cela a fonctionné dans WinForms, mais dans WPF, je reçois cette exception:

System.InvalidOperationException: impossible de définir la visibilité ou d'appeler Show, ShowDialog ou WindowInteropHelper.EnsureHandle après la fermeture d'une fenêtre.

Existe-t-il un moyen de faire quelque chose comme ça dans WPF?

MyWindow.Instance.ShowDialog();

public class MyWindow : Window
{
    private static MyWindow _instance;

    public static MyWindow Instance
    {
        if( _instance == null )
        {
            _instance = new Window();
        }
        return _instance();
    }
}
43
Jerod Houghtelling

Je suppose que vous pourriez le faire si vous modifiez la visibilité de la fenêtre plutôt que de la fermer. Vous devez le faire dans l'événement Closing (), puis annuler la clôture. Si vous autorisez la fermeture, vous ne pouvez certainement pas rouvrir une fenêtre fermée - de ici :

Si l'événement de clôture n'est pas annulé, les événements suivants se produisent:

...

Les ressources non gérées créées par la fenêtre sont supprimées.

Après cela, la fenêtre ne sera plus jamais valide.

Cependant, je ne pense pas que cela en vaille la peine - ce n'est vraiment pas un gros problème de performances pour créer une nouvelle fenêtre à chaque fois et vous êtes beaucoup moins susceptible d'introduire des bugs/fuites de mémoire difficiles à déboguer. (De plus, vous devez vous assurer qu'il a bien fermé et libéré ses ressources lorsque l'application est fermée)


Il suffit de lire que vous utilisez ShowDialog (), cela rendra la fenêtre modale et le cacher ne reviendra pas à la fenêtre parent. Je doute qu'il soit possible de le faire avec des fenêtres modales.

45
Martin Harris

Si je ne me trompe pas, vous pouvez annuler l'événement de fermeture de cette fenêtre et définir la visibilité sur caché

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
        this.Visibility = Visibility.Hidden;
    } 
38
Rain
public class MyWindow : Window

public MyWindow ()
    {
        InitializeComponent();            
        Closed += new System.EventHandler(MyWindow_Closed);
    }

private static MyWindow _instance;

public static MyWindow Instance
{
    if( _instance == null )
    {
        _instance = new Window();
    }
    return _instance();
}
void MyWindow_Closed(object sender, System.EventArgs e)
    {
         _instance = null;
    }
2
Winson Yang

Lorsque nous essayons d'afficher la fenêtre qui est fermée, nous obtiendrons l'exception suivante.

"Impossible de définir la visibilité ou d'appeler Show, ShowDialog ou WindowInteropHelper.EnsureHandle après la fermeture d'une fenêtre."

Donc, pour gérer ce cas, il serait préférable d'utiliser l'option Visibility de la fenêtre. Nous devons définir la visibilité de la fenêtre sur Hidden ou Collapsed au lieu de la fermer directement.

this.Visibility = System.Windows.Visibility.Collapsed or Hidden;

Si nous voulons l'afficher à nouveau, il suffit de définir la visibilité sur Visible

this.Visibility = System.Windows.Visibility.Visible;

2
Srikanth Dornala

si vous annulez l'événement de fermeture et définissez visibilité = caché, vous pouvez ignorer ce problème

Private Sub ChildWindow_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles Me.Closing
        e.Cancel = True
        Me.Visibility = Windows.Visibility.Hidden
End Sub
1
Shiyas

J'ai eu un problème similaire. Donc boîte de dialogue modale, mais dans cette boîte de dialogue, vous avez le bouton "Sélectionner" qui doit passer au formulaire principal (de préférence sans fermer la boîte de dialogue modale), sélectionnez une zone à partir de là, puis revenez à la boîte de dialogue modale avec les informations de sélection. J'ai essayé de jouer un peu avec les boîtes de dialogue/afficher/masquer non modales et après, je n'ai pas trouvé de bonne solution (facile à coder), une approche codée en quelque sorte hacky utilisant des appels de fonction native win32. Ce que j'ai testé - cela fonctionne bien avec winforms et aussi avec xaml.

Le problème lui-même n'est pas nécessairement facile - l'utilisateur appuie donc sur "Sélectionner", puis il peut oublier qu'il sélectionnait quelque chose et revenir à la même boîte de dialogue de sélection, ce qui peut entraîner deux ou plusieurs instances de la même boîte de dialogue. .

J'essaie de résoudre ce problème en utilisant des variables statiques (instance/parent) - si vous avez des winforms purs ou la technologie wpf pure, vous pouvez obtenir le parent de instance.Parent ou instance.Owner.

public partial class MeasureModalDialog : Window
{
    //  Dialog has "Select area" button, need special launch mechanism. (showDialog / SwitchParentChildWindows)
    public static MeasureModalDialog instance = null;
    public static object parent = null;

    static public void showDialog(object _parent)
    {
        parent = _parent;
        if (instance == null)
        {
            instance = new MeasureModalDialog();

            // Parent is winforms, child is xaml, this is just glue to get correct window owner to child dialog.
            if (parent != null && parent is System.Windows.Forms.IWin32Window)
                new System.Windows.Interop.WindowInteropHelper(instance).Owner = (parent as System.Windows.Forms.IWin32Window).Handle;

            // Enable parent window if it was disabled.
            instance.Closed += (_sender, _e) => { instance.SwitchParentChildWindows(true); };
            instance.ShowDialog();

            instance = null;
            parent = null;
        }
        else
        {
            // Try to switch to child dialog.
            instance.SwitchParentChildWindows(false);
        }
    } //showDialog

    public void SwitchParentChildWindows( bool bParentActive )
    {
        View3d.SwitchParentChildWindows(bParentActive, parent, this);
    }


    public void AreaSelected( String selectedAreaInfo )
    {
        if( selectedAreaInfo != null )     // Not cancelled
            textAreaInfo.Text = selectedAreaInfo;

        SwitchParentChildWindows(false);
    }

    private void buttonAreaSelect_Click(object sender, RoutedEventArgs e)
    {
        SwitchParentChildWindows(true);
        View3d.SelectArea(AreaSelected);
    }

    ...

public static class View3d
{

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool EnableWindow(IntPtr hWnd, bool bEnable);

    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool BringWindowToTop(IntPtr hWnd);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowEnabled(IntPtr hWnd);

    /// <summary>
    /// Extracts window handle in technology independent wise.
    /// </summary>
    /// <param name="formOrWindow">form or window</param>
    /// <returns>window handle</returns>
    static public IntPtr getHandle( object formOrWindow )
    {
        System.Windows.Window window = formOrWindow as System.Windows.Window;
        if( window != null )
            return new System.Windows.Interop.WindowInteropHelper(window).Handle;

        System.Windows.Forms.IWin32Window form = formOrWindow as System.Windows.Forms.IWin32Window;
        if (form != null)
            return form.Handle;

        return IntPtr.Zero;
    }

    /// <summary>
    /// Switches between modal sub dialog and parent form, when sub dialog does not needs to be destroyed (e.g. selecting 
    /// something from parent form)
    /// </summary>
    /// <param name="bParentActive">true to set parent form active, false - child dialog.</param>
    /// <param name="parent">parent form or window</param>
    /// <param name="dlg">sub dialog form or window</param>
    static public void SwitchParentChildWindows(bool bParentActive, object parent, object dlg)
    {
        if( parent == null || dlg == null )
            return;

        IntPtr hParent = getHandle(parent);
        IntPtr hDlg = getHandle(dlg);

        if( !bParentActive )
        {
            //
            // Prevent recursive loops which can be triggered from UI. (Main form => sub dialog => select (sub dialog hidden) => sub dialog in again.
            // We try to end measuring here - if parent window becomes inactive - 
            // means that we returned to dialog from where we launched measuring. Meaning nothing special needs to be done.
            //
            bool bEnabled = IsWindowEnabled(hParent);
            View3d.EndMeasuring(true);   // Potentially can trigger SwitchParentChildWindows(false,...) call.
            bool bEnabled2 = IsWindowEnabled(hParent);

            if( bEnabled != bEnabled2 )
                return;
        }

        if( bParentActive )
        {
            EnableWindow(hDlg, false);      // Disable so won't eat parent keyboard presses.
            ShowWindow(hDlg, 0);  //SW_HIDE
        }

        EnableWindow(hParent, bParentActive);

        if( bParentActive )
        {
            SetForegroundWindow(hParent);
            BringWindowToTop(hParent);
        } else {
            ShowWindow(hDlg, 5 );  //SW_SHOW
            EnableWindow(hDlg, true);
            SetForegroundWindow(hDlg);
        }
    } //SwitchParentChildWindows

    ...

Le même paradigme peut avoir des problèmes de boîte de dialogue non modale, car chaque chaîne d'appel de fonction de sélection mange la pile et, éventuellement, vous pouvez obtenir un débordement de pile, ou vous pouvez également rencontrer des problèmes avec la gestion de l'état de la fenêtre parent (l'activer/le désactiver).

Je pense donc que c'est une solution assez légère à un problème, même si cela semble plutôt complexe.

0
TarmoPikaro

Voici comment je gère:

public partial class MainWindow 
{
    bool IsAboutWindowOpen = false;

    private void Help_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (!IsAboutWindowOpen)
        {
            var aboutWindow = new About();
            aboutWindow.Closed += new EventHandler(aboutWindow_Closed);
            aboutWindow.Show();
            IsAboutWindowOpen = true;
        }
    }

    void aboutWindow_Closed(object sender, EventArgs e)
    {
        IsAboutWindowOpen = false;
    }
}
0
Rumplin