Eliminazione dei controlli utente WPF


119

Ho creato un controllo utente WPF personalizzato che deve essere utilizzato da una terza parte. Il mio controllo ha un membro privato che è usa e getta e vorrei assicurarmi che il suo metodo dispose venga sempre chiamato una volta chiusa la finestra / applicazione contenente. Tuttavia, UserControl non è usa e getta. Ho provato a implementare l'interfaccia IDisposable e ad iscrivermi all'evento Unloaded ma nessuno dei due viene chiamato quando l'applicazione host si chiude. Se possibile, non voglio fare affidamento sui consumatori sotto il mio controllo che si ricordano di chiamare uno specifico metodo Dispose.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

     // where does this code go?
     void Somewhere() 
     {
         if (x != null)
         {
             x.Dispose();
             x = null;
         }

     }
 }

L'unica soluzione che ho trovato finora è iscrivermi all'evento ShutdownStarted di Dispatcher's. È un approccio ragionevole?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

Che dire dell'evento Unloaded del controllo utente?
akjoshi

2
@akjoshi: MSDN dice che: L'evento Unloaded potrebbe non essere generato affatto. E potrebbe anche essere attivato più di una volta, ovvero quando l'utente cambia tema.
Dudu

Sebbene sia possibile implementare l'interfaccia IDisposable sul controllo utente, non vi è alcuna garanzia che la terza parte chiamerà il metodo dispose dell'implementazione del pattern Dispose. Se stai trattenendo risorse native (ad esempio un flusso di file), dovresti considerare l'utilizzo di un finalizzatore.
Philippe

Risposte:


57

Interessante post sul blog qui:

http://geekswithblogs.net/cskardon/archive/2008/06/23/dispose-of-a-wpf-usercontrol-ish.aspx

Menziona l'iscrizione a Dispatcher.ShutdownStarted per smaltire le tue risorse.


1
beh, speravo che ci sarebbe stato un modo più pulito di questo, ma per ora sembra che questo sia il migliore per farlo.
Mark Heath

35
Ma cosa succede se UserControl muore prima che l'app muoia? Il Dispatcher si spegnerà solo quando l'app lo fa, giusto?
Robert Jeppesen

15
Perché molti controlli riutilizzano componenti COM o altre risorse non gestite che non sono state codificate per essere lasciate in sospeso a tempo indefinito o finalizzate su un thread del pool di thread e prevedono / richiedono la deallocazione deterministica.
Neutrino

1
In un'app di Windows Store, ShutdownStarted non esiste.
Cœur

7
Oppure è necessario dereferenziare i gestori di eventi o interrompere i thread avviati in quel controllo, ...
DanW

40

Dispatcher.ShutdownStartedl'evento viene generato solo alla fine dell'applicazione. Vale la pena chiamare la logica di smaltimento solo quando il controllo diventa fuori uso. In particolare, libera risorse quando il controllo viene utilizzato molte volte durante il runtime dell'applicazione. Quindi la soluzione di ioWint è preferibile. Ecco il codice:

public MyWpfControl()
{
     InitializeComponent();
     Loaded += (s, e) => { // only at this point the control is ready
         Window.GetWindow(this) // get the parent window
               .Closing += (s1, e1) => Somewhere(); //disposing logic here
     };
}

In un'app di Windows Store, GetWindow () non esiste.
Cœur

Bravo, risposta migliore.
Nic

8
Cœur: In
un'app di

3
cosa succede se ci sono più finestre coinvolte e quella principale non si chiude mai? O il tuo controllo è ospitato in una pagina che viene caricata / scaricata più volte? vedi: stackoverflow.com/a/14074116/1345207
L.Trabacchin

1
La finestra potrebbe non chiudersi molto spesso. Se il controllo fa parte di un elemento di elenco, molti verranno creati / distrutti fino a quando la sua finestra padre potrebbe chiudersi.
PERSO

15

Devi stare attento usando il distruttore. Questo verrà chiamato sul thread di Finalizer GC. In alcuni casi le risorse per le quali la tua liberazione potrebbe non piacere essere rilasciate su un thread diverso da quello su cui sono state create.


1
Grazie per questo avvertimento. questo era esattamente il mio caso! Applicazione: devenv.exe Framework Versione: v4.0.30319 Descrizione: il processo è stato terminato a causa di un'eccezione non gestita. Informazioni sull'eccezione: System.InvalidOperationException Stack: su MyControl.Finalize () la mia soluzione era spostare il codice dal finalizzatore a ShutdownStarted
itsho

10

Utilizzo il seguente comportamento di interattività per fornire un evento di scaricamento a WPF UserControls. Puoi includere il comportamento in UserControls XAML. Quindi puoi avere la funzionalità senza inserire la logica in ogni singolo UserControl.

Dichiarazione XAML:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<i:Interaction.Behaviors>
    <behaviors:UserControlSupportsUnloadingEventBehavior UserControlClosing="UserControlClosingHandler" />
</i:Interaction.Behaviors>

Gestore CodeBehind:

private void UserControlClosingHandler(object sender, EventArgs e)
{
    // to unloading stuff here
}

Codice di comportamento:

/// <summary>
/// This behavior raises an event when the containing window of a <see cref="UserControl"/> is closing.
/// </summary>
public class UserControlSupportsUnloadingEventBehavior : System.Windows.Interactivity.Behavior<UserControl>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += UserControlLoadedHandler;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= UserControlLoadedHandler;
        var window = Window.GetWindow(AssociatedObject);
        if (window != null)
            window.Closing -= WindowClosingHandler;
    }

    /// <summary>
    /// Registers to the containing windows Closing event when the UserControl is loaded.
    /// </summary>
    private void UserControlLoadedHandler(object sender, RoutedEventArgs e)
    {
        var window = Window.GetWindow(AssociatedObject);
        if (window == null)
            throw new Exception(
                "The UserControl {0} is not contained within a Window. The UserControlSupportsUnloadingEventBehavior cannot be used."
                    .FormatWith(AssociatedObject.GetType().Name));

        window.Closing += WindowClosingHandler;
    }

    /// <summary>
    /// The containing window is closing, raise the UserControlClosing event.
    /// </summary>
    private void WindowClosingHandler(object sender, CancelEventArgs e)
    {
        OnUserControlClosing();
    }

    /// <summary>
    /// This event will be raised when the containing window of the associated <see cref="UserControl"/> is closing.
    /// </summary>
    public event EventHandler UserControlClosing;

    protected virtual void OnUserControlClosing()
    {
        var handler = UserControlClosing;
        if (handler != null) 
            handler(this, EventArgs.Empty);
    }
}

5
Sollevo una bandiera qui ... e se qualcos'altro annulla la chiusura della finestra (magari sottoscritta dopo il tuo controllo, quindi e.Cancelè ancora falsa quando raggiunge il tuo WindowClosingHandlerdelegato) ?. Il controllo verrebbe "scaricato" e la finestra ancora aperta. Lo farei sicuramente Closedsull'evento, non su Closingquello.
Jcl

6

Il mio scenario è leggermente diverso, ma l'intento è lo stesso che vorrei sapere quando la finestra principale che ospita il mio controllo utente si sta chiudendo / chiudendo poiché la vista (cioè il mio controllo utente) dovrebbe richiamare i relatori oncloseView per eseguire alcune funzionalità ed eseguire la pulizia. (beh, stiamo implementando un pattern MVP su un'applicazione WPF PRISM).

Ho appena immaginato che nell'evento Loaded di usercontrol, posso collegare il mio metodo ParentWindowClosing all'evento Parent windows Closing. In questo modo il mio controllo utente può essere a conoscenza della chiusura della finestra principale e agire di conseguenza!


0

Penso che unload sia chiamato all ma in hard exist in 4.7. Ma, se stai giocando con versioni precedenti di .Net, prova a farlo nel tuo metodo di caricamento:

e.Handled = true;

Non credo che le versioni precedenti verranno scaricate fino a quando il caricamento non sarà gestito. Sto solo postando perché vedo altri che fanno ancora questa domanda e non ho visto questa proposta come una soluzione. Tocco .Net solo poche volte all'anno e mi sono imbattuto in questo alcuni anni fa. Ma mi chiedo se sia semplice come unload non essere chiamato fino al termine del caricamento. Sembra che funzioni per me, ma di nuovo nella versione .Net più recente sembra sempre chiamare unload anche se il caricamento non è contrassegnato come gestito.


-3

Un UserControl ha un Destructor, perché non lo usi?

~MyWpfControl()
    {
        // Dispose of any Disposable items here
    }

Questo non sembra funzionare. Ho appena provato quell'approccio e non viene mai chiamato.
JasonD

9
Non è un distruttore, è un finalizzatore. Implementa sempre un finalizzatore e Dispose in coppia altrimenti rischi di perdite.
Mike Post

1
Inoltre, nel finalizzatore è necessario pulire solo gli oggetti non gestiti ma non gli oggetti gestiti, poiché i finalizzatori vengono eseguiti in un ordine non specificato nei thread GC, quindi gli oggetti gestiti potrebbero essere finalizzati in precedenza e il loro Dispose () potrebbe avere affinità di thread.
Dudu

2
joeduffyblog.com/2005/04/08/… è la migliore spiegazione di Finalize and Dispose che ho trovato. Vale davvero la pena leggerlo.
dss539
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.