2010-08-25 46 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 

    ... 

相同的範例可能有問題無模式對話框,因爲每個選擇的函數調用鏈吃堆棧,最終你可能會得到堆棧溢出,否則會產生問題,也有管理的父窗口狀態(啓用/禁用)。

所以我認爲這是相當輕量級的問題解決方案,即使它看起來相當複雜。