2009-02-02 18 views
99

Ho creato un controllo utente WPF personalizzato che è destinato ad essere utilizzato da una terza parte. Il mio controllo ha un membro privato che è usa e getta, e mi piacerebbe assicurarmi che il suo metodo di eliminazione venga sempre chiamato una volta chiusa la finestra o l'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 del mio controllo ricordando di chiamare un metodo Dispose specifico.Smaltimento dei controlli utente WPF

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 è iscriversi all'evento ShutdownStarted del Dispatcher. È un approccio ragionevole?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted; 
+0

Cosa sull'evento a vuoto di controllo utente? – akjoshi

+2

@akjoshi: MSDN afferma che: L'evento non scaricato non può essere generato affatto.E potrebbe anche essere attivato più di una volta, cioè quando l'utente cambia tema. – Dudu

+0

Mentre è possibile implementare l'interfaccia IDisposable sul proprio controllo utente, non è garantito che la terza parte chiamerà il metodo di smaltimento dell'implementazione del modello Dispose. Se stai mantenendo le risorse native (ad esempio un flusso di file), dovresti considerare l'utilizzo di un finalizzatore. – Philippe

risposta

51

interessante post sul blog qui:

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

si menziona la sottoscrizione di Dispatcher_ShutDownStarted di disporre delle vostre risorse.

+0

beh speravo che ci sarebbe stato un modo più pulito di questo, ma sembra che per ora questo è il migliore per farlo. –

+28

Ma cosa succede se UserControl muore prima che l'app muoia? Il Dispatcher si limiterà solo quando l'app lo fa, giusto? –

+0

Completamente d'accordo, ma non capisco perché l'OP debba disporre di controlli. Suoni ... dispari –

-3

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

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

Questo non sembra funzionare. Ho appena provato questo approccio e non viene mai chiamato. – JasonD

+8

Questo non è un distruttore, è un finalizzatore. Si implementa sempre un finalizzatore e si disfa in coppia altrimenti si rischiano perdite. –

+1

E, nel finalizzatore, è necessario solo ripulire gli oggetti non gestiti ma non gli oggetti gestiti, poiché i finalizzatori vengono eseguiti in ordine non specificato nei thread GC, pertanto gli oggetti gestiti possono essere finalizzati in precedenza e il loro Dispose() potrebbe avere affinità di thread. – Dudu

10

bisogna essere attenti usando il distruttore. Questo verrà chiamato sul thread di GC Finalizer. In alcuni casi, le risorse a cui la tua liberazione potrebbe non piacere vengono pubblicate su un thread diverso da quello in cui sono state create.

+1

Grazie per questo avvertimento. questo era esattamente il mio caso! _Applicazione: devenv.exe Versione quadro: v4.0.30319 Descrizione: il processo è stato interrotto a causa di un'eccezione non gestita. Eccezione Info: System.InvalidOperationException Stack: a MyControl.Finalize() _ la mia soluzione era quella di spostare il codice da finalizzatore in ShutdownStarted – itsho

4

Il mio scenario è leggermente diverso, ma l'intento è lo stesso vorrei sapere quando la finestra padre che ospita il mio controllo utente è chiusa/chiusa come La vista (cioè il mio usercontrol) dovrebbe invocare i presentatori oncloseView per eseguire alcune funzionalità e eseguire la pulizia. (beh stiamo implementando un pattern MVP su un'applicazione PRISM WPF).

Ho appena capito che nell'evento Loaded dell'usercontrol, posso collegare il mio metodo ParentWindowClosing all'evento Closing di Windows padre. In questo modo il mio Usercontrol può essere consapevole quando la finestra Genitore viene chiusa e agisce di conseguenza!

26

Dispatcher.ShutdownStarted l'evento viene generato solo alla fine dell'applicazione. Vale la pena di chiamare la logica di smaltimento proprio quando il controllo diventa inutilizzabile. In particolare, libera le risorse quando il controllo viene utilizzato più volte durante il runtime dell'applicazione. Quindi è preferibile la soluzione ioWint. 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 
    }; 
} 
+0

In un Windows App Store, GetWindow() non esiste. –

+0

Bravo, la migliore risposta. – MDDDC

+4

Cœur: In un'app di Windows Store, non si utilizza WPF –

8

Io uso il seguente comportamento interattività per fornire un evento di scarico per controlli utente WPF. È possibile includere il comportamento in UserControls XAML. Così puoi avere la funzionalità senza posizionarla 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); 
    } 
} 
+3

Vorrei alzare una bandiera qui ... cosa succede se qualcos'altro annulla la chiusura della finestra (forse sottoscritto dopo il tuo controllo quindi 'e.Cancel' è ancora falso quando raggiunge il tuo delegato 'WindowClosingHandler') ?. Il tuo controllo verrebbe "scaricato" e la finestra ancora aperta. Lo farei sicuramente sull'evento 'Closed', non su quello di Closing'. – Jcl

+0

Oh sì, ho perso questo punto. Grazie. –

Problemi correlati