Entsorgen von WPF-Benutzersteuerelementen

119

Ich habe ein benutzerdefiniertes WPF-Benutzersteuerelement erstellt, das von Dritten verwendet werden soll. Mein Steuerelement hat ein privates Mitglied, das verfügbar ist, und ich möchte sicherstellen, dass seine Entsorgungsmethode immer aufgerufen wird, sobald das enthaltende Fenster / die enthaltende Anwendung geschlossen wird. UserControl ist jedoch nicht verfügbar. Ich habe versucht, die IDisposable-Schnittstelle zu implementieren und das Unloaded-Ereignis zu abonnieren, aber beide werden beim Schließen der Host-Anwendung nicht aufgerufen. Wenn möglich, möchte ich mich nicht darauf verlassen, dass Verbraucher meiner Kontrolle daran denken, eine bestimmte Dispose-Methode aufzurufen.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

Die einzige Lösung, die ich bisher gefunden habe, besteht darin, das ShutdownStarted-Ereignis des Dispatchers zu abonnieren. Ist das ein vernünftiger Ansatz?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
Mark Heath
quelle
Was ist mit dem entladenen Ereignis der Benutzersteuerung?
Akjoshi
2
@akjoshi: MSDN sagt Folgendes: Das entladene Ereignis wird möglicherweise überhaupt nicht ausgelöst. Es kann auch mehrmals ausgelöst werden, wenn der Benutzer das Thema wechselt.
Dudu
Obwohl Sie die IDisposable-Schnittstelle in Ihrem Benutzersteuerelement implementieren können, gibt es keine Garantie dafür, dass Ihr Drittanbieter die Dispose-Methode Ihrer Dispose-Pattern-Implementierung aufruft. Wenn Sie an nativen Ressourcen (z. B. einem Dateistream) festhalten, sollten Sie einen Finalizer verwenden.
Philippe

Antworten:

57

Interessanter Blogbeitrag hier:

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

Es wird erwähnt, dass Sie Dispatcher.ShutdownStarted abonniert haben , um Ihre Ressourcen zu entsorgen.

Ray Booysen
quelle
1
Nun, ich hatte gehofft, dass es einen saubereren Weg als diesen geben würde, aber es sieht so aus, als ob dies im Moment das Beste ist, dies zu tun.
Mark Heath
35
Aber was ist, wenn das UserControl stirbt, bevor die App stirbt? Der Dispatcher wird nur dann heruntergefahren, wenn die App dies tut, oder?
Robert Jeppesen
15
Da viele Steuerelemente COM-Komponenten oder andere nicht verwaltete Ressourcen wiederverwenden, die nicht codiert wurden, um unbegrenzt herumzuhängen oder in einem Thread-Pool-Thread finalisiert zu werden, und eine deterministische Freigabe erwarten / erfordern.
Neutrino
1
In einer Windows Store-App ist ShutdownStarted nicht vorhanden.
Cœur
7
Oder Sie müssen Ereignishandler dereferenzieren oder Threads stoppen, die in diesem Steuerelement gestartet wurden, ...
DanW
40

Dispatcher.ShutdownStartedEreignis wird erst am Ende der Anwendung ausgelöst. Es lohnt sich, die Entsorgungslogik nur dann aufzurufen, wenn die Steuerung außer Betrieb ist. Insbesondere werden Ressourcen frei, wenn die Steuerung während der Anwendungslaufzeit häufig verwendet wird. Daher ist die Lösung von ioWint vorzuziehen. Hier ist der Code:

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
     };
}
Ilia Barahovski
quelle
In einer Windows Store-App ist GetWindow () nicht vorhanden.
Cœur
Bravo, beste Antwort.
Nic
8
Cœur: In einer Windows Store App verwenden Sie WPF nicht
Alan Baljeu
3
Was ist, wenn mehr Fenster beteiligt sind und das Hauptfenster nie geschlossen wird? Oder wird Ihr Steuerelement auf einer Seite gehostet, die mehrmals geladen / entladen wird? siehe: stackoverflow.com/a/14074116/1345207
L.Trabacchin
1
Das Fenster wird möglicherweise nicht sehr oft geschlossen. Wenn das Steuerelement Teil eines Listenelements ist, werden viele erstellt / zerstört, bis das übergeordnete Fenster geschlossen wird.
VERLOREN
15

Sie müssen vorsichtig mit dem Destruktor sein. Dies wird im GC Finalizer-Thread aufgerufen. In einigen Fällen werden die Ressourcen, die Sie freigeben möchten, möglicherweise nicht in einem anderen Thread veröffentlicht als dem, in dem sie erstellt wurden.

Ade Miller
quelle
1
Vielen Dank für diese Warnung. das war genau mein Fall! Anwendung: devenv.exe Framework Version: v4.0.30319 Beschreibung: Der Prozess wurde aufgrund einer nicht behandelten Ausnahme beendet. Ausnahmeinfo: System.InvalidOperationException Stack: Bei MyControl.Finalize () bestand meine Lösung darin, Code vom Finalizer in ShutdownStarted zu verschieben
itsho
10

Ich verwende das folgende Interaktivitätsverhalten, um WPF UserControls ein Entladeereignis bereitzustellen. Sie können das Verhalten in die UserControls XAML aufnehmen. So können Sie die Funktionalität nutzen, ohne die Logik in jedes einzelne UserControl zu setzen.

XAML-Erklärung:

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

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

CodeBehind-Handler:

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

Verhaltenscode:

/// <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);
    }
}
alex.enjoy
quelle
5
Ich würde hier eine Flagge hissen ... was ist, wenn etwas anderes das Schließen des Fensters abbricht (möglicherweise nach Ihrer Kontrolle abonniert, so dass e.Canceles immer noch falsch ist, wenn es Ihren WindowClosingHandlerDelegierten erreicht)? Ihre Steuerung würde "entladen" und das Fenster immer noch geöffnet. Ich würde das definitiv auf der ClosedVeranstaltung tun , nicht auf der Closingeinen.
Jcl
6

Mein Szenario ist wenig anders, aber die Absicht ist dieselbe. Ich möchte wissen, wann das übergeordnete Fenster, in dem sich mein Benutzersteuerelement befindet, geschlossen / geschlossen wird, da die Ansicht (dh meine Benutzersteuerung) die Präsentatoren oncloseView aufrufen sollte, um einige Funktionen auszuführen und eine Bereinigung durchzuführen. (Nun, wir implementieren ein MVP-Muster in einer WPF PRISM-Anwendung.)

Ich habe gerade herausgefunden, dass ich im Loaded-Ereignis der Benutzersteuerung meine ParentWindowClosing-Methode mit dem Closing-Ereignis des übergeordneten Fensters verbinden kann. Auf diese Weise kann meine Benutzersteuerung erkennen, wann das übergeordnete Fenster geschlossen wird, und entsprechend handeln!

ioWint
quelle
0

Ich denke, das Entladen wird in 4.7 als alles andere als schwer bezeichnet. Wenn Sie jedoch mit älteren Versionen von .Net herumspielen, versuchen Sie dies in Ihrer Lademethode:

e.Handled = true;

Ich glaube nicht, dass ältere Versionen entladen werden, bis das Laden erledigt ist. Ich poste nur, weil ich sehe, dass andere diese Frage immer noch stellen und dies nicht als Lösung angesehen haben. Ich berühre .Net nur ein paar Mal im Jahr und bin vor ein paar Jahren darauf gestoßen. Aber ich frage mich, ob es so einfach ist, dass das Entladen erst nach Abschluss des Ladevorgangs aufgerufen wird. Scheint, als würde es bei mir funktionieren, aber auch in neueren .Nets scheint es immer Unload aufzurufen, selbst wenn das Laden nicht als behandelt markiert ist.

Michael T.
quelle
-3

Ein UserControl hat einen Destruktor. Warum benutzt du das nicht?

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

quelle
Das scheint nicht zu funktionieren. Ich habe gerade diesen Ansatz ausprobiert und er wird nie aufgerufen.
JasonD
9
Das ist kein Destruktor, es ist ein Finalizer. Sie implementieren immer einen Finalizer und entsorgen ihn als Paar, da sonst die Gefahr von Undichtigkeiten besteht.
Mike Post
1
Und in Finalizer sollten Sie nur nicht verwaltete Objekte bereinigen, aber keine verwalteten Objekte, da Finalizer in GC-Threads in nicht angegebener Reihenfolge ausgeführt werden, sodass verwaltete Objekte möglicherweise früher finalisiert werden und Dispose () möglicherweise eine Thread-Affinität aufweist.
Dudu
2
joeduffyblog.com/2005/04/08/… ist die beste Erklärung für Finalize and Dispose, die ich gefunden habe. Es ist wirklich lesenswert.
dss539