WPF:关闭窗口后无法重用窗口

时间:2010-08-25 16:37:44

标签: c# .net wpf singleton window

我正在尝试保留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();
    }
}

9 个答案:

答案 0 :(得分:45)

如果您更改窗口的可见性而不是关闭窗口,我认为可以执行此操作。您需要在Closing()事件中执行此操作,然后取消关闭。如果你允许关闭发生,你肯定无法重新打开一个关闭的窗口 - 来自here

  

如果未取消关闭事件,   发生以下情况:

     

...

     

窗口创建的非托管资源将被处理。

在那之后,窗口永远不再有效。

我认为这不值得付出努力 - 每次创建一个新窗口并没有太大的性能影响而且你不太可能引入难以调试的bug /内存泄漏。 (另外,当应用程序关闭时,您需要确保它关闭并释放它的资源)


只是读到你正在使用ShowDialog(),这将使窗口模态并简单地隐藏它将不会将控制返回到父窗口。我怀疑使用模态窗口可以做到这一点。

答案 1 :(得分:37)

如果我没错,你可以取消该窗口的结束事件,而不是设置隐藏

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

答案 2 :(得分:3)

尝试一下:

protected override void OnClosing(CancelEventArgs e)
{
    this.Visibility = Visibility.Hidden;
    e.Cancel = true;
}

答案 3 :(得分:2)

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 :(得分:2)

当我们尝试显示已关闭的窗口时,我们将得到以下异常。

“窗口关闭后,无法设置可见性或调用Show,ShowDialog或WindowInteropHelper.EnsureHandle。”

因此,为了处理这种情况,如果我们使用窗口的可见性选项会更好。我们需要将窗口的可见性设置为隐藏折叠,而不是直接关闭。

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

如果我们想再次展示,只需将可见性设置为可见

即可

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

答案 5 :(得分:1)

如果您取消关闭事件并设置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 :(得分:0)

我的处理方式如下:

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 :(得分:0)

我有某种类似的问题。所以模态对话框,但在该对话框中你有一个“Select”按钮需要切换到主窗体(最好没有关闭模态对话框),从那里选择一些区域然后返回到带有选择信息的模态对话框。我尝试使用无模式对话框/ show / hide进行一些操作,之后找不到任何好的(易于编码)解决方案,使用win32本机函数调用以某种方式编写hacky方法。我测试的内容 - 它适用于winforms和xaml。

问题本身也不是必须的 - 因此用户按下“选择”,然后他可能会忘记他正在选择某些东西,并返回到相同的不同选择对话框,这可能导致两个或更多实例相同的对话。

我试图通过使用静态变量(实例/父)来解决这个问题 - 如果你有纯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

    ...

相同的范例可能会出现无模式对话框问题,因为每个选择函数调用链都会占用堆栈,最终可能会出现堆栈溢出,或者您可能会遇到管理父窗口状态(启用/禁用它)的问题。

所以我认为这是一个非常轻量级的问题解决方案,即使它看起来相当复杂。

答案 8 :(得分:0)

这可能是我不明白的逻辑,但关闭窗口是不可逆的

如果你想“关闭”你的窗口并用一个按钮重新打开你可以隐藏它 像这样:

 private MyWindow myWindow;
 private void OpenMyWindow_OnClick(object sender, RoutedEventArgs e)
 {
     if (myWindow == null)
     {
         myWindow = new MyWindow();
     }

     if(!myWindow.IsVisible)
     {
         myWindow.Show();
     }
     else
     {
         myWindow.Hide();
     }
 }

如果您的窗口可以关闭,我建议您使用 Closed 事件处理它。 (这是我使用的解决方案)

private MyWindow myWindow;
private void OpenMyWindow_OnClick(object sender, RoutedEventArgs e)
{
    if (myWindow == null)
    {
        myWindow = new MyWindow();
        myWindow.Closed += OnMyWindowClosed;
    }

    if(!myWindow.IsVisible)
    {
        myWindow.Show();
    }
    else
    {
        myWindow.Hide();
    }
}

private void OnMyWindowClosed(object obj, EventArgs e)
{
    myWindow = null;
}

我希望我帮助了某人