2010-08-25 34 views
40

Sto cercando di mantenere una istanza di Window in giro e quando necessario chiamare ShowDialog. Questo ha funzionato trovare in WinForms, ma in WPF ricevo questo exeception:WPF: impossibile riutilizzare la finestra dopo che è stata chiusa

System.InvalidOperationException: Impossibile impostare Visibilità oppure chiamare Show, ShowDialog, o WindowInteropHelper.EnsureHandle dopo una finestra ha chiuso.

C'è un modo per fare qualcosa di simile in 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

Esiste un motivo specifico per cui non è possibile creare un'istanza nuova ogni volta? Secondo me è comunque più sicuro e migliore. –

+0

@Alex La radice del problema sta in un thrid party control che sto usando. Diventa ancora più complesso quando si lancia in Prisma e Unità. Credo sinceramente che una forma a singleton come nei giorni della winform sarebbe più facile da implementare. Quando si prova Show/Hide su una finestra di dialogo non modale, le prestazioni sono eccezionali. Tuttavia, il requisito stabilisce che la finestra di dialogo debba essere modale. –

+0

Il metodo Show della finestra di dialogo accetta un parametro? Ho trovato questo http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f3565f01-f972-4aaf-80cc-986488d25261 che potrebbe essere di aiuto. –

risposta

40

Suppongo che si potrebbe farlo se è stata modificata la visibilità della finestra, piuttosto che chiuderlo. Dovresti farlo nell'evento Closing() e quindi annullare la chiusura. Se si consente la chiusura accada non si può certo riaprire una finestra chiusa - da here:

Se l'evento di chiusura non viene annullato, si verifica quanto segue:

...

Le risorse non gestite create dalla Finestra sono eliminate.

Dopo ciò, la finestra non sarà più valida.

Non penso che valga la pena, però, non è tanto un colpo di prestazioni ogni volta che si crea una nuova finestra e molto meno è probabile introdurre deboli bug/perdite di memoria. (Più avresti bisogno di fare in modo che si chiudeva e rilasciarlo di risorse quando l'applicazione viene chiusa)


appena letto che si sta utilizzando ShowDialog(), questo renderà il modal finestra e semplicemente nascondere non restituirà il controllo alla finestra genitore. Dubito che sia possibile farlo affatto con Windows modale.

+9

In realtà, creare una nuova finestra è una proposta piuttosto costosa una volta incluso il costo di tutto il layout, l'inizializzazione ecc. Per finestre piuttosto complesse, questo può migliorare notevolmente le prestazioni - l'ho provato ;-) . –

1

se si annulla l'evento close e impostare la visibilità = nascosto allora si può ignorare questo problema

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

Se non sbaglio, è possibile annullare l'evento di quella finestra di chiusura e invece impostare visibilità nascosto

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

Questa è la stessa risposta di Martin Harris solo con il codice aggiunto. – evanb

+0

oh, sì, hai ragione – Rain

+0

Nel caso qualcuno si imbattesse in un problema simile, ho dovuto aggiungere Closing = "Window_Closing" a XAML nel tag della mia finestra – nivekgnay

0

Ecco come gestire:

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

Quando proviamo a mostrare la finestra che è chiusa, otterremo la seguente eccezione.

"Impossibile impostare Visibilità o chiamare Show, ShowDialog o WindowInteropHelper.EnsureHandle dopo che una finestra è stata chiusa."

Quindi, per gestire questo caso, sarebbe meglio se usiamo l'opzione Visibility della finestra.È necessario impostare la visibilità della finestra su Nascosto o Compresso anziché chiuderlo direttamente.

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

Se vogliamo mostrare di nuovo, basta impostare la visibilità per Visibile

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

0

Avevo in qualche modo un problema simile. Quindi, la finestra di dialogo modale, ma in quella finestra di dialogo è presente il pulsante "Seleziona" che deve passare alla forma principale (preferibilmente senza chiudere la finestra di dialogo modale), selezionare un'area da lì e quindi tornare alla finestra di dialogo modale con le informazioni di selezione. Ho provato a giocare un po 'con dialoghi non modali/show/hide e dopo non ho trovato nessuna buona soluzione (facile da codificare), codificata in qualche modo l'approccio hacky usando le chiamate di funzione native win32. Quello che ho testato - funziona bene con winforms e anche con xaml.

Il problema in sé non è nemmeno semplice: l'utente preme "Seleziona", quindi può dimenticare che stava selezionando qualcosa e tornare alla stessa finestra di selezione, che può comportare due o più istanze della stessa finestra di dialogo.

Sto provando ad affrontare questo problema utilizzando variabili statiche (istanza/genitore) - se si dispone di winform puro o pura tecnologia wpf, è possibile ottenere il genitore da instance.Parent o 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 

    ... 

stesso paradigma potrebbe avere problemi di dialogo non modale, dal momento che ogni selezione catena chiamata di funzione mangia pila e alla fine si potrebbe ottenere overflow dello stack, o si potrebbe ottenere anche problemi con la gestione di finestra padre di stato (abilitare/disabilitare).

Quindi penso che questa soluzione sia piuttosto leggera a un problema, anche se sembra piuttosto complessa.

Problemi correlati