Подтвердить что ты не робот

WPF: нельзя повторно использовать окно после его закрытия

Я пытаюсь сохранить один экземпляр Window вокруг и при необходимости вызвать ShowDialog. Это сработало нахождение в winforms, но в WPF я получаю это исключение:

System.InvalidOperationException: не удается установить видимость или вызвать Show, ShowDialog или WindowInteropHelper.EnsureHandle после закрытия окна.

Есть ли способ сделать что-то подобное в WPF?

MyWindow.Instance.ShowDialog();

public class MyWindow : Window
{
    private static MyWindow _instance;

    public static MyWindow Instance
    {
        if( _instance == null )
        {
            _instance = new Window();
        }
        return _instance();
    }
}
4b9b3361

Ответ 1

Я полагаю, вы могли бы это сделать, если бы изменили видимость окна, а не закрыли его. Вам нужно будет сделать это в событии Закрытие(), а затем отменить закрытие. Если вы позволите закрыть ситуацию, вы, конечно же, не сможете открыть закрытое окно - от здесь:

Если событие закрытия не отменено, происходит следующее:

...

Устанавливаются неуправляемые ресурсы, созданные в окне.

После этого окно снова не будет действительным.

Я не думаю, что это стоит усилий, хотя - на самом деле это не так много из производительности, чтобы каждый раз создавать новое окно, и вы гораздо менее склонны вводить проблемы для отладки ошибок/утечек памяти. (Кроме того, вам нужно будет убедиться, что он закрыл и освободил ресурсы при закрытии приложения)


Просто прочитайте, что вы используете ShowDialog(), это сделает окно модальным и просто скрывает его, не вернет управление родительскому окну. Я сомневаюсь, что это можно сделать вообще с модальными окнами.

Ответ 2

Если я не ошибаюсь, вы можете отменить закрытие события этого окна и вместо этого установить видимость на скрытый

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
        this.Visibility = Visibility.Hidden;
    } 

Ответ 3

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

Ответ 4

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

"Невозможно установить видимость или вызвать Show, ShowDialog или WindowInteropHelper.EnsureHandle после закрытия окна."

Итак, чтобы обработать этот случай, было бы лучше, если бы мы использовали опцию Видимость. Нам нужно установить видимость окна Скрытый или Свернутый, а не закрывать его напрямую.

this.Visibility = System.Windows.Visibility.Collapsed или Hidden;

Если мы хотим снова показать его, просто установите видимость на Видимый

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

Ответ 5

если вы отмените событие закрытия и установите visibility = hidden, вы можете переопределить эту проблему.

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

Ответ 6

Вот как я обрабатываю:

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

Ответ 7

У меня была какая-то схожая проблема. Итак, модальный диалог, но в этом диалоговом окне у вас есть кнопка "Выбрать", которая должна переключиться на главную форму (желательно без закрытия модального диалога), выберите из нее какую-то область, а затем вернитесь в модальное диалоговое окно с информацией о выборе. Я попытался немного поиграть с немощными диалогами /show/hide и после того, как не смог найти никакого хорошего (легко кодируемого) решения, закодировал каким-то хакерским подходом с использованием собственных вызовов функций win32. То, что я тестировал, - это хорошо работает с winforms, а также с xaml.

Сама проблема также не нужна простая - пользователь нажимает "Выбрать", а затем он может забыть, что он что-то выбирает, и вернуться к тому же из другого диалогового окна выбора, что может привести к двум или более экземплярам того же диалога.

Я пытаюсь решить эту проблему, используя статические переменные (instance/parent) - если у вас есть чистые winforms или чистая технология wpf, вы можете получить родительский экземпляр экземпляра .Parent или 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

    ...

В той же парадигме могут возникнуть проблемы с модельным диалогом, поскольку каждая цепочка вызовов функции выбора потребляет стек, и в итоге вы можете получить переполнение стека, или вы можете столкнуться с проблемами с управлением состоянием родительского окна (включить/отключить его).

Итак, я думаю, что это довольно легкое решение проблемы, даже при этом выглядит довольно сложно.