Eventos de duración de objetos (WPF .NET)

Durante su vigencia, todos los objetos del código administrado de Microsoft .NET pasan por fases de creación, uso y destrucción. Windows Presentation Foundation (WPF) proporciona una notificación de estas fases, a medida que se producen en un objeto, mediante la generación de eventos de duración. Para los elementos de nivel de marco de WPF (objetos visuales), WPF implementa los eventos de duración Initialized, Loaded y Unloaded. Los desarrolladores pueden usar estos eventos de duración como enlaces para operaciones de código subyacente en las que intervienen elementos. En este artículo se describen los eventos de duración para objetos visuales y, a continuación, se presentan otros eventos de duración que se aplican específicamente a elementos de ventana, hosts de navegación u objetos de aplicación.

Importante

La documentación de la guía de escritorio para .NET 7 y .NET 6 está en proceso de elaboración.

Prerrequisitos

En este artículo se da por supuesto un conocimiento básico de cómo se puede conceptualizar el diseño de elementos WPF como árbol, y que ha leído Información general sobre eventos enrutados. Para seguir los ejemplos de este artículo, resultará útil que esté familiarizado con el lenguaje XAML y sepa cómo escribir aplicaciones WPF.

Eventos de duración para objetos visuales

Los elementos de nivel de marco de WPF derivan de FrameworkElement o FrameworkContentElement. Los eventos de duración Initialized, Loadedy Unloaded son comunes a todos los elementos de nivel de marco de WPF. En el ejemplo siguiente se muestra un árbol de elementos que se implementa principalmente en XAML. El XAML define un elemento Canvas primario que contiene elementos anidados, cada uno de los cuales usa la sintaxis de atributo XAML para adjuntar los controladores de eventos de duración Initialized, Loaded y Unloaded.

<Canvas x:Name="canvas">
    <StackPanel x:Name="outerStackPanel" Initialized="InitHandler" Loaded="LoadHandler" Unloaded="UnloadHandler">
        <custom:ComponentWrapper x:Name="componentWrapper" Initialized="InitHandler" Loaded="LoadHandler" Unloaded="UnloadHandler">
            <TextBox Name="textBox1" Initialized="InitHandler" Loaded="LoadHandler" Unloaded="UnloadHandler" />
            <TextBox Name="textBox2" Initialized="InitHandler" Loaded="LoadHandler" Unloaded="UnloadHandler" />
        </custom:ComponentWrapper>
    </StackPanel>
    <Button Content="Remove canvas child elements" Click="Button_Click"/>
</Canvas>

Uno de los elementos XAML es un control personalizado, que deriva de una clase base que asigna controladores de eventos de duración en el código subyacente.

public partial class MainWindow : Window
{
    public MainWindow() => InitializeComponent();

    // Handler for the Initialized lifetime event (attached in XAML).
    private void InitHandler(object sender, System.EventArgs e) => 
        Debug.WriteLine($"Initialized event on {((FrameworkElement)sender).Name}.");

    // Handler for the Loaded lifetime event (attached in XAML).
    private void LoadHandler(object sender, RoutedEventArgs e) => 
        Debug.WriteLine($"Loaded event on {((FrameworkElement)sender).Name}.");

    // Handler for the Unloaded lifetime event (attached in XAML).
    private void UnloadHandler(object sender, RoutedEventArgs e) =>
        Debug.WriteLine($"Unloaded event on {((FrameworkElement)sender).Name}.");

    // Remove nested controls.
    private void Button_Click(object sender, RoutedEventArgs e) => 
        canvas.Children.Clear();
}

// Custom control.
public class ComponentWrapper : ComponentWrapperBase { }

// Custom base control.
public class ComponentWrapperBase : StackPanel
{
    public ComponentWrapperBase()
    {
        // Assign handler for the Initialized lifetime event (attached in code-behind).
        Initialized += (object sender, System.EventArgs e) => 
            Debug.WriteLine($"Initialized event on componentWrapperBase.");

        // Assign handler for the Loaded lifetime event (attached in code-behind).
        Loaded += (object sender, RoutedEventArgs e) => 
            Debug.WriteLine($"Loaded event on componentWrapperBase.");

        // Assign handler for the Unloaded lifetime event (attached in code-behind).
        Unloaded += (object sender, RoutedEventArgs e) => 
            Debug.WriteLine($"Unloaded event on componentWrapperBase.");
    }
}

/* Output:
Initialized event on textBox1.
Initialized event on textBox2.
Initialized event on componentWrapperBase.
Initialized event on componentWrapper.
Initialized event on outerStackPanel.

Loaded event on outerStackPanel.
Loaded event on componentWrapperBase.
Loaded event on componentWrapper.
Loaded event on textBox1.
Loaded event on textBox2.

Unloaded event on outerStackPanel.
Unloaded event on componentWrapperBase.
Unloaded event on componentWrapper.
Unloaded event on textBox1.
Unloaded event on textBox2.
*/
Partial Public Class MainWindow
    Inherits Window

    Public Sub New()
        InitializeComponent()
    End Sub

    ' Handler for the Initialized lifetime event (attached in XAML).
    Private Sub InitHandler(sender As Object, e As EventArgs)
        Debug.WriteLine($"Initialized event on {CType(sender, FrameworkElement).Name}.")
    End Sub

    ' Handler for the Loaded lifetime event (attached in XAML).
    Private Sub LoadHandler(sender As Object, e As RoutedEventArgs)
        Debug.WriteLine($"Loaded event on {CType(sender, FrameworkElement).Name}.")
    End Sub

    ' Handler for the Unloaded lifetime event (attached in XAML).
    Private Sub UnloadHandler(sender As Object, e As RoutedEventArgs)
        Debug.WriteLine($"Unloaded event on {CType(sender, FrameworkElement).Name}.")
    End Sub

    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        ' Remove nested controls.
        canvas.Children.Clear()
    End Sub
End Class

' Custom control.
Public Class ComponentWrapper
    Inherits ComponentWrapperBase
End Class

' Custom base control.
Public Class ComponentWrapperBase
    Inherits StackPanel

    Public Sub New()
        ' Attach handlers for the lifetime events.
        AddHandler Initialized, AddressOf InitHandler
        AddHandler Loaded, AddressOf LoadHandler
        AddHandler Unloaded, AddressOf UnloadHandler
    End Sub

    ' Handler for the Initialized lifetime event (attached in code-behind).
    Private Sub InitHandler(sender As Object, e As EventArgs)
        Debug.WriteLine("Initialized event on componentWrapperBase.")
    End Sub

    ' Handler for the Loaded lifetime event (attached in code-behind).
    Private Sub LoadHandler(sender As Object, e As RoutedEventArgs)
        Debug.WriteLine("Loaded event on componentWrapperBase.")
    End Sub

    ' Handler for the Unloaded lifetime event (attached in code-behind).
    Private Sub UnloadHandler(sender As Object, e As RoutedEventArgs)
        Debug.WriteLine("Unloaded event on componentWrapperBase.")
    End Sub
End Class

'Output:
'Initialized event on textBox1.
'Initialized event on textBox2.
'Initialized event on componentWrapperBase.
'Initialized event on componentWrapper.
'Initialized event on outerStackPanel.

'Loaded event on outerStackPanel.
'Loaded event on componentWrapperBase.
'Loaded event on componentWrapper.
'Loaded event on textBox1.
'Loaded event on textBox2.

'Unloaded event on outerStackPanel.
'Unloaded event on componentWrapperBase.
'Unloaded event on componentWrapper.
'Unloaded event on textBox1.
'Unloaded event on textBox2.

La salida del programa muestra el orden de invocación de los eventos de duración Initialized, Loaded y Unloaded en cada objeto de árbol. Esos eventos se describen en las secciones siguientes, en el orden en que se generan en cada objeto de árbol.

Evento de duración Initialized

El sistema de eventos de WPF genera el evento Initialized en un elemento:

  • Cuando se establecen las propiedades del elemento.
  • Más o menos al mismo tiempo en que se inicializa el objeto a través de una llamada a su constructor.

Algunas propiedades de elemento, como Panel.Children, pueden contener elementos secundarios. Los elementos primarios no pueden notificar la inicialización hasta que se inicializan sus elementos secundarios. Por lo tanto, los valores de propiedad se establecen a partir de los elementos más profundamente anidados en un árbol de elementos, seguidos de elementos primarios sucesivos hasta la raíz de la aplicación. Dado que el evento Initialized se produce cuando se establecen las propiedades de un elemento, ese evento se invoca por primera vez en los elementos más profundamente anidados tal y como se define en el marcado, seguidos de elementos primarios sucesivos hasta la raíz de la aplicación. Cuando los objetos se crean dinámicamente en el código subyacente, su inicialización puede estar fuera de secuencia.

El sistema de eventos de WPF no espera a que se inicialicen todos los elementos de un árbol de elementos antes de generar el evento Initialized en un elemento. Por lo tanto, al escribir un controlador de eventos Initialized para cualquier elemento, tenga en cuenta que es posible que no se hayan creado elementos adyacentes en el árbol lógico o visual, especialmente los elementos primarios. O bien, sus variables miembro y enlaces de datos podrían no inicializarse.

Nota

Cuando se genera el evento Initialized en un elemento, los usos de expresiones del elemento, como, por ejemplo, recursos dinámicos o enlaces, no se evaluarán.

Evento de duración Loaded

El sistema de eventos de WPF genera el evento Loaded en un elemento:

  • Cuando el árbol lógico que contiene el elemento está completo y conectado a un origen de presentación. El origen de la presentación proporciona el identificador de ventana (HWND) y la superficie de representación.
  • Cuando se completa el enlace de datos a orígenes locales, como otras propiedades u orígenes de datos directamente definidos.
  • Después de que el sistema de diseño haya calculado todos los valores necesarios para la representación.
  • Antes de la representación final.

El evento Loaded no se genera en ningún elemento de un árbol de elementos hasta que se cargan todos los elementos del árbol lógico. El sistema de eventos de WPF genera primero el evento Loaded en el elemento raíz de un árbol de elementos y, a continuación, en cada elemento secundario sucesivo hasta los elementos más profundamente anidados. Aunque este evento puede recordar a un evento enrutado de tunelización, el evento Loaded no lleva datos de evento de un elemento a otro, por lo que marcar el evento como controlado no tiene ningún efecto.

Nota

El sistema de eventos de WPF no puede garantizar que los enlaces de datos asincrónicos se hayan completado antes del evento Loaded. Los enlaces de datos asincrónicos se enlazan a orígenes externos o dinámicos.

Evento de duración Unloaded

El sistema de eventos de WPF genera el evento Unloaded en un elemento:

  • Al eliminar su origen de presentación, o bien
  • su elemento primario visual.

El sistema de eventos de WPF genera primero el evento Unloaded en el elemento raíz de un árbol de elementos y, a continuación, en cada elemento secundario sucesivo hasta los elementos más profundamente anidados. Aunque este evento puede recordar a un evento enrutado de tunelización, el evento Unloaded no propaga datos de evento de un elemento a otro, por lo que marcar el evento como controlado no tiene ningún efecto.

Cuando el evento Unloaded se genera en un elemento, es posible que su elemento primario o cualquier elemento superior del árbol lógico o visual ya se haya anulado. Anular significa que los enlaces de datos, las referencias de recursos y los estilos de un elemento ya no se establecen en su valor en tiempo de ejecución normal o último conocido.

Otros eventos de duración

Desde la perspectiva de los eventos de duración, hay cuatro tipos principales de objetos de WPF: elementos en general, elementos de ventana, hosts de navegación y objetos de aplicación. Los eventos de duración Initialized, Loadedy Unloaded se aplican a todos los elementos de nivel de marco. Otros eventos de duración se aplican específicamente a elementos de ventana, hosts de navegación u objetos de aplicación. Para obtener información sobre esos otros eventos de duración, consulte:

Vea también