Eliminación de controles de usuario de WPF

119

He creado un control de usuario WPF personalizado que está destinado a ser utilizado por un tercero. Mi control tiene un miembro privado que es desechable, y me gustaría asegurarme de que su método de eliminación siempre sea llamado una vez que se cierre la ventana / aplicación que lo contiene. Sin embargo, UserControl no es desechable. Intenté implementar la interfaz IDisposable y suscribirme al evento Unloaded, pero no me llamaron cuando se cierra la aplicación host. Si es posible, no quiero depender de que los consumidores bajo mi control recuerden llamar a un método Dispose específico.

 public partial class MyWpfControl : UserControl
 {
     SomeDisposableObject x;

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

     }
 }

La única solución que he encontrado hasta ahora es suscribirme al evento ShutdownStarted del Dispatcher. ¿Es este un enfoque razonable?

this.Dispatcher.ShutdownStarted += Dispatcher_ShutdownStarted;
Mark Heath
fuente
¿Qué pasa con el evento de control de usuario descargado?
Akjoshi
2
@akjoshi: MSDN dice que: Es posible que el evento descargado no se genere en absoluto. Y también puede activarse más de una vez, es decir, cuando el usuario cambia de tema.
Dudu
Si bien puede implementar la interfaz IDisposable en su control de usuario, no hay garantía de que su tercero llame al método dispose de la implementación del patrón Dispose. Si está guardando recursos nativos (por ejemplo, un flujo de archivos), debería considerar usar un finalizador.
Philippe

Respuestas:

57

Publicación de blog interesante aquí:

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

Menciona la suscripción a Dispatcher.ShutdownStarted para disponer de sus recursos.

Ray Booysen
fuente
1
bueno, esperaba que hubiera una forma más limpia que esta, pero parece que por ahora esta es la mejor manera de hacerlo.
Mark Heath
35
Pero, ¿qué pasa si UserControl muere antes de que la aplicación muera? El Dispatcher solo se encogerá cuando la aplicación lo haga, ¿verdad?
Robert Jeppesen
15
Debido a que muchos controles reutilizan componentes COM u otros recursos no administrados que no fueron codificados con el fin de dejarlos colgando indefinidamente, o finalizados en un subproceso de grupo de subprocesos, y esperan / requieren una desasignación determinista.
Neutrino
1
En una aplicación de la Tienda Windows, ShutdownStarted no existe.
Cœur
7
O necesita eliminar la referencia a los controladores de eventos, o necesita detener los subprocesos iniciados en ese control, ...
DanW
40

Dispatcher.ShutdownStartedEl evento se dispara solo al final de la aplicación. Vale la pena llamar a la lógica de eliminación justo cuando el control deja de usarse. En particular, libera recursos cuando el control se usa muchas veces durante el tiempo de ejecución de la aplicación. Por tanto , es preferible la solución de ioWint . Aquí está el código:

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
fuente
En una aplicación de la Tienda Windows, GetWindow () no existe.
Cœur
Bravo, la mejor respuesta.
Nic
8
Cœur: En una aplicación de la Tienda Windows, no estás usando WPF
Alan Baljeu
3
¿Qué pasa si hay más ventanas involucradas y la principal nunca se cierra? ¿O su control está alojado en una página que se carga / descarga varias veces? ver: stackoverflow.com/a/14074116/1345207
L.Trabacchin
1
Es posible que la ventana no se cierre con mucha frecuencia. Si el control es parte de un elemento de la lista, muchos se crearán / destruirán hasta que se cierre la ventana principal.
PERDIDO
15

Tienes que tener cuidado con el destructor. Esto se llamará en el subproceso GC Finalizer. En algunos casos, es posible que los recursos que libera no les gusten se liberen en un hilo diferente del que fueron creados.

Ade Miller
fuente
1
Gracias por esta advertencia. este fue mi caso exactamente! Aplicación: devenv.exe Framework Versión: v4.0.30319 Descripción: El proceso se terminó debido a una excepción no controlada. Información de excepción: System.InvalidOperationException Stack: en MyControl.Finalize () mi solución fue mover el código del finalizador a ShutdownStarted
itsho
10

Utilizo el siguiente comportamiento de interactividad para proporcionar un evento de descarga a WPF UserControls. Puede incluir el comportamiento en UserControls XAML. Para que pueda tener la funcionalidad sin colocar la lógica en cada UserControl.

Declaración XAML:

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

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

Controlador CodeBehind:

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

Código de conducta:

/// <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
fuente
5
Yo levantaría una bandera aquí ... ¿y si cualquier otra cosa cancela el cierre de la ventana (tal vez suscrito después de su control, por e.Cancello que sigue siendo falso cuando llega a su WindowClosingHandlerdelegado)? Su control se "descargará" y la ventana aún se abrirá. Definitivamente haría esto en el Closedevento, no en Closinguno.
Jcl
6

Mi escenario es un poco diferente, pero la intención es la misma.Me gustaría saber cuándo se cierra / cierra la ventana principal que aloja mi control de usuario, ya que la vista (es decir, mi control de usuario) debería invocar a los presentadores encloseView para ejecutar alguna funcionalidad y realizar la limpieza. (bueno, estamos implementando un patrón MVP en una aplicación WPF PRISM).

Me acabo de imaginar que en el evento Loaded del control de usuario, puedo conectar mi método ParentWindowClosing al evento de cierre de la ventana principal. De esta manera, mi Usercontrol puede ser consciente de cuándo se cierra la ventana principal y actuar en consecuencia.

ioWint
fuente
0

Creo que descargar se llama todo, pero difícilmente existe en 4.7. Pero, si está jugando con versiones anteriores de .Net, intente hacer esto en su método de carga:

e.Handled = true;

No creo que las versiones anteriores se descarguen hasta que se maneje la carga. Solo publico porque veo que otros todavía hacen esta pregunta y no he visto esta propuesta como una solución. Solo toco .Net algunas veces al año, y me encontré con esto hace unos años. Pero me pregunto si es tan simple como no llamar a unload hasta que finalice la carga. Parece que funciona para mí, pero nuevamente en .Net más reciente parece que siempre llama a unload incluso si la carga no está marcada como manejada.

Michael T.
fuente
-3

Un UserControl tiene un Destructor, ¿por qué no lo usas?

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

fuente
Esto no parece funcionar. Solo probé ese enfoque y nunca me llaman.
JasonD
9
Eso no es un destructor, es un finalizador. Siempre implementa un finalizador y Dispose como un par, de lo contrario corre el riesgo de fugas.
Mike Post
1
Y, en el finalizador, solo debe limpiar los objetos no administrados, pero no los objetos administrados, porque los finalizadores se ejecutan en un orden no especificado en subprocesos GC, por lo que los objetos administrados pueden finalizarse antes y su Dispose () puede tener afinidad por subprocesos.
Dudu
2
joeduffyblog.com/2005/04/08/… es la mejor explicación de Finalizar y Eliminar que he encontrado. Realmente vale la pena leerlo.
dss539