Usuwanie formantów użytkownika WPF


119

Utworzyłem niestandardową kontrolkę użytkownika WPF, która jest przeznaczona do użytku przez inną firmę. Mój formant ma prywatnego członka, który jest jednorazowy i chciałbym mieć pewność, że jego metoda usuwania będzie zawsze wywoływana po zamknięciu zawierającego okno / aplikacji. Jednak UserControl nie jest jednorazowy. Próbowałem zaimplementować interfejs IDisposable i subskrybować zdarzenie Unloaded, ale żadne z nich nie jest wywoływane po zamknięciu aplikacji hosta. Jeśli to w ogóle możliwe, nie chcę polegać na konsumentach mojej kontroli, którzy pamiętają o wywołaniu określonej metody Dispose.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

Jedynym rozwiązaniem, jakie do tej pory znalazłem, jest zasubskrybowanie zdarzenia ShutdownStarted Dispatchera. Czy to rozsądne podejście?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;

A co ze zdarzeniem Unloaded kontroli użytkownika?
akjoshi

2
@akjoshi: MSDN mówi, że: Zdarzenie wyładowane może w ogóle nie zostać zgłoszone. Może być również uruchamiany więcej niż raz, to znaczy, gdy użytkownik zmienia motyw.
Dudu

Chociaż można zaimplementować interfejs IDisposable w kontrolce użytkownika, nie ma gwarancji, że firma zewnętrzna wywoła metodę usuwania implementacji wzorca Dispose. Jeśli zatrzymujesz zasoby natywne (np. Strumień plików), powinieneś rozważyć użycie finalizatora.
Philippe

Odpowiedzi:


57

Ciekawy wpis na blogu tutaj:

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

Wspomina o subskrybowaniu Dispatcher.ShutdownStarted w celu pozbycia się zasobów.


1
cóż, miałem nadzieję, że będzie czystszy sposób niż ten, ale na razie wygląda na to, że najlepiej to zrobić.
Mark Heath

35
Ale co, jeśli UserControl zginie przed śmiercią aplikacji? Dyspozytor przestanie działać tylko wtedy, gdy robi to aplikacja, prawda?
Robert Jeppesen

15
Ponieważ wiele kontrolek ponownie używa składników COM lub innych niezarządzanych zasobów, które nie zostały zakodowane z myślą o pozostawieniu ich w nieskończoność lub sfinalizowaniu w wątku puli wątków i oczekują / wymagają deterministycznego cofnięcia alokacji.
Neutrino

1
W aplikacji Windows Store ShutdownStarted nie istnieje.
Cœur,

7
Lub musisz
wyłuskać programy

40

Dispatcher.ShutdownStartedzdarzenie jest uruchamiane tylko na końcu aplikacji. Warto wywołać logikę usuwania właśnie wtedy, gdy kontrola przestanie być używana. W szczególności zwalnia zasoby, gdy sterowanie jest używane wielokrotnie podczas działania aplikacji. Dlatego preferowane jest rozwiązanie ioWint . Oto kod:

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
     };
}

W aplikacji ze Sklepu Windows funkcja GetWindow () nie istnieje.
Coœur,

Brawo, najlepsza odpowiedź.
Nic

8
Cœur: W aplikacji Windows Store nie używasz WPF
Alan Baljeu,

3
co, jeśli zaangażowanych jest więcej okien, a główne nigdy nie zostanie zamknięte? Czy Twoja formant jest hostowana na stronie, która jest wielokrotnie ładowana / usuwana? patrz: stackoverflow.com/a/14074116/1345207
L.Trabacchin

1
Okno może nie zamykać się zbyt często. Jeśli formant jest częścią elementu listy, wiele z nich zostanie utworzonych / zniszczonych do czasu zamknięcia okna nadrzędnego.
LOST

15

Z destruktorem trzeba uważać. Zostanie to wywołane w wątku GC Finalizer. W niektórych przypadkach zasoby, które uwolniłeś, mogą nie być zwolnione w innym wątku niż ten, w którym zostały utworzone.


1
Dziękuję za to ostrzeżenie. to był dokładnie mój przypadek! Aplikacja: devenv.exe Framework Wersja: v4.0.30319 Opis: Proces został zakończony z powodu nieobsługiwanego wyjątku. Informacje o wyjątku: System.InvalidOperationException Stack: at MyControl.Finalize () moim rozwiązaniem było przeniesienie kodu z finalizera do ShutdownStarted
itsho

10

Używam następującego zachowania interaktywności, aby zapewnić zdarzenie zwalniania do WPF UserControls. Możesz uwzględnić zachowanie w kodzie XAML UserControls. Możesz więc mieć tę funkcjonalność bez umieszczania jej logiki w każdym pojedynczym UserControl.

Deklaracja XAML:

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

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

Procedura obsługi CodeBehind:

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

Kod zachowania:

/// <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
Podniósłbym tutaj flagę ... a co, jeśli cokolwiek innego anuluje zamknięcie okna (może zasubskrybowano po twojej kontroli, więc e.Cancelnadal jest fałszywe, gdy dotrze do twojego WindowClosingHandlerdelegata) ?. Twoja kontrola zostanie „rozładowana”, a okno nadal będzie otwarte. Zdecydowanie zrobiłbym to na Closedimprezie, a nie na Closingjednej.
Jcl

6

Mój scenariusz jest trochę inny, ale intencja jest taka sama. Chciałbym wiedzieć, kiedy okno nadrzędne, w którym znajduje się moja kontrolka użytkownika, jest zamykane / zamykane, ponieważ widok (tj. Moja kontrola użytkownika) powinien wywoływać osoby prowadzące oncloseView, aby wykonać pewne funkcje i przeprowadzić czyszczenie. (cóż, implementujemy wzorzec MVP w aplikacji WPF PRISM).

Właśnie doszedłem do wniosku, że w zdarzeniu Loaded kontrolki użytkownika mogę podłączyć moją metodę ParentWindowClosing do zdarzenia Zamykanie okien nadrzędnych. W ten sposób moja kontrola użytkownika może być świadoma, kiedy okno rodzica jest zamykane i odpowiednio działać!


0

Myślę, że rozładowanie nazywa się wszystko, ale ciężko istnieje w 4.7. Ale jeśli bawisz się starszymi wersjami .Net, spróbuj zrobić to w swojej metodzie ładowania:

e.Handled = true;

Nie sądzę, aby starsze wersje rozładowały się, dopóki ładowanie nie zostanie obsłużone. Po prostu publikuję, ponieważ widzę, że inni nadal zadają to pytanie i nie widziałem tego proponowanego jako rozwiązania. Dotykam .Net tylko kilka razy w roku i wpadałem na to kilka lat temu. Ale zastanawiam się, czy jest to tak proste, jak nie wywoływanie wyładowania przed zakończeniem ładowania. Wydaje się, że to działa dla mnie, ale znowu w nowszej .Net wydaje się, że zawsze wywołuje unload, nawet jeśli ładowanie nie jest oznaczone jako obsługiwane.


-3

UserControl ma Destructor, dlaczego tego nie użyjesz?

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

To nie działa. Po prostu wypróbowałem to podejście i nigdy nie zostało to wywołane.
JasonD

9
To nie jest destruktor, to finalizator. Zawsze wdrażasz finalizator i usuwasz jako parę, w przeciwnym razie ryzykujesz wycieki.
Mike

1
A w finalizatorze należy czyścić tylko niezarządzane obiekty, ale nie obiekty zarządzane, ponieważ finalizatory są uruchamiane w nieokreślonej kolejności w wątkach GC, w ten sposób zarządzane obiekty mogą być sfinalizowane wcześniej, a ich Dispose () może mieć koligację wątków.
Dudu

2
joeduffyblog.com/2005/04/08/… to najlepsze wyjaśnienie, jakie znalazłem na temat Finalizuj i Usuń. Naprawdę warto przeczytać.
dss539
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.