2010-08-25 48 views
40

我正在尝试保留Window的一个实例,并在需要时调用ShowDialog。这工作的WinForms找到,但在WPF我收到此异常: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(); 
    } 
} 
+1

是否有一个特定的原因,你为什么不能每次都实例化一个新的?无论如何,我认为它更安全,更好。 – 2010-08-26 16:02:59

+0

@Alex问题的根源在于我正在使用的第三方控件。当投掷棱镜和团结时会变得更加复杂。我非常认真地相信,像winform这样的单身形式会更容易实现。在非模式对话框中尝试显示/隐藏时,性能非常棒。但是,要求声明对话框必须是模态的。 – 2010-08-26 16:11:10

+0

对话框的Show方法是否接受参数?我发现这个http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f3565f01-f972-4aaf-80cc-986488d25261,这可能会有所帮助。 – 2010-08-26 16:56:17

回答

40

我想你可能做,如果你改变了窗口的可见性,而不是关闭它。您需要在Closing()事件中执行此操作,然后取消关闭。如果允许接近发生你肯定到不能重新打开已关闭的窗口 - 从here

如果未取消Closing事件, 会发生以下情况:

...

处理由Window创建的非托管资源。

发生这种情况后,窗口永远不会再有效。我不认为这是值得的 - 尽管每次创建一个新窗口的性能并没有太大的提高,而且你也不太可能引入难以调试的bug /内存泄漏。 (加上你需要确保它没有关闭,释放它的资源,当应用程序被关闭)


刚才看了您正在使用的ShowDialog(),这将使窗口模式,只是隐藏它不会将控制权返回给父窗口。我怀疑用模式窗口可以做到这一点。

+9

实际上,一旦包含了所有布局,初始化等的开销,创建一个新窗口就相当昂贵。对于有些复杂的窗口,这可以明显提高性能 - 我试过了;-) 。 – 2011-07-14 10:52:41

1

如果取消关闭事件,并设置可见性=隐藏,那么你可以忽略这个问题

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

如果我没看错,你可以取消该窗口的关闭事件,而是设置可见隐藏

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

这与马丁哈里斯刚添加的代码一样。 – evanb 2011-08-19 21:46:11

+0

哦,是的,你是对的 – Rain 2012-04-09 05:58:04

+0

如果有人遇到类似的问题,我不得不在我的窗口标签中添加Closing =“Window_Closing”到XAML – nivekgnay 2016-06-29 15:33:26

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

当我们试图显示被关闭的窗口时,我们会得到以下例外。

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

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

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

如果我们想再次显示,刚刚可见性设置为可见

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

0

我有一些类似的问题。所以模态对话框,但在该对话框中你有按钮“选择”需要切换到主窗体(最好不关闭模态对话框),从那里选择一些区域,然后返回到模态对话框的选择信息。我试图用无模式对话框/显示/隐藏来玩一点,之后找不到任何好的(易于编码)的解决方案,使用win32本地函数调用以某种方式进行编码。我测试过的 - 它适用于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 

    ... 

相同的范例可能有问题无模式对话框,因为每个选择的函数调用链吃堆栈,最终你可能会得到堆栈溢出,否则会产生问题,也有管理的父窗口状态(启用/禁用)。

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