web-dev-qa-db-ja.com

WPF:ウィンドウを閉じた後は再利用できません

Windowのインスタンスを1つ保持し、必要に応じてShowDialogを呼び出します。これはwinformsで見つかりましたが、WPFではこの例外を受け取ります:

System.InvalidOperationException:Visibilityを設定したり、Windowが閉じた後に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();
    }
}
43

ウィンドウを閉じるのではなく、可視性を変更した場合、couldを行うと思います。 Closing()イベントでそれを実行し、クローズをキャンセルする必要があります。クローズを許可すると、閉じたウィンドウを再び開くことはできません- here から:

Closingイベントがキャンセルされない場合、以下が発生します。

...

ウィンドウによって作成された管理されていないリソースは破棄されます。

その後、ウィンドウは二度と有効になりません。

しかし、努力する価値はないと思います-毎回新しいウィンドウを作成することは実際にはそれほどパフォーマンスに影響しませんし、デバッグが難しいバグ/メモリリークを導入する可能性ははるかに低くなります。 (さらに、アプリケーションがシャットダウンされたときに、それが閉じてリソースを解放したことを確認する必要があります)


ShowDialog()を使用していることを読んでください。これにより、ウィンドウがモーダルになり、非表示にするだけで親ウィンドウに制御が戻りません。モーダルウィンドウでこれを行うことはまったく不可能だと思います。

45
Martin Harris

私が間違っていなければ、そのウィンドウのcloseイベントをキャンセルし、代わりに可視性を非表示に設定できます

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

閉じているウィンドウを表示しようとすると、次の例外が発生します。

"Visibilityを設定したり、ウィンドウが閉じた後にShow、ShowDialog、WindowInteropHelper.EnsureHandleを呼び出したりすることはできません。"

したがって、このケースを処理するには、ウィンドウのVisibilityオプションを使用する方が良いでしょう。ウィンドウを直接閉じるのではなく、ウィンドウの可視性をHiddenまたはCollapsedに設定する必要があります。

this.Visibility = System.Windows.Visibility.CollapsedまたはHidden;

再度表示したい場合は、可視性をVisibleに設定するだけです

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

2

closeイベントをキャンセルし、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
1
Shiyas

なんとなく似たような問題がありました。したがって、モーダルダイアログですが、そのダイアログにはメインフォームに切り替える必要がある「選択」ボタンがあります(モーダルダイアログを閉じずに)、そこからいくつかの領域を選択してから、選択情報とともにモーダルダイアログに戻ります。私はモードレスダイアログ/表示/非表示で少し遊んでみましたが、Win32ネイティブ関数呼び出しを使用して何らかの方法でコード化されたハッキン​​グアプローチをコーディングできませんでした。私がテストしたもの-winformsでもxamlでも動作します。

問題自体も簡単なものではありません-ユーザーが「選択」を押すと、何かを選択していることを忘れて、同じ選択ダイアログに戻り、同じダイアログの2つ以上のインスタンスが発生する可能性があります。 。

静的変数(インスタンス/親)を使用してこの問題に対処しようとしています-純粋なwinformsまたは純粋なwpfテクノロジがある場合、instance.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

    ...

同じパラダイムでは、モードレスダイアログで問題が発生する可能性があります。各選択関数の呼び出しチェーンがスタックを消費し、最終的にスタックオーバーフローが発生したり、親ウィンドウの状態管理(有効化/無効化)で問題が発生する可能性があるためです.

したがって、これは問題に対する非常に軽量なソリューションであり、かなり複雑に見えます。

0
TarmoPikaro

ここに私が扱う方法があります:

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