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.