Ereignisse zur Objektlebensdauer (WPF .NET)

Während ihrer Lebensdauer durchlaufen alle Objekte in verwaltetem Microsoft .NET-Code die Phasen Erstellung, Verwendung und Zerstörung. Windows Presentation Foundation (WPF) stellt Benachrichtigungen über diese Phasen bereit, wenn sie in einem Objekt auftreten, indem Lebensdauerereignisse ausgelöst werden. Für WPF-Elemente auf Frameworkebene (visuelle Objekte) implementiert WPF die Lebensdauerereignisse Initialized, Loaded und Unloaded. Entwickler können diese Lebensdauerereignisse als Hooks für CodeBehind-Vorgänge verwenden, die Elemente einbeziehen. In diesem Artikel werden die Lebensdauerereignisse für visuelle Objekte beschrieben, und anschließend werden andere Lebensdauerereignisse vorgestellt, die speziell auf Fensterelemente, Navigationshosts oder Anwendungsobjekte angewendet werden.

Wichtig

Der Desktopleitfaden zu .NET 7 und .NET 6 ist in Bearbeitung.

Voraussetzungen

Dieser Artikel setzt voraus, dass Sie wissen, wie das WPF-Elementlayout als Struktur konzeptualisiert werden kann, und dass Sie die Übersicht über Routingereignisse gelesen haben. Um den Beispielen in diesem Artikel zu folgen, ist es hilfreich, wenn Sie mit Extensible Application Markup Language (XAML) vertraut sind und wissen, wie WPF-Anwendungen geschrieben werden.

Lebensdauerereignisse für visuelle Objekte

WPF-Elemente auf Frameworkebene werden von FrameworkElement oder FrameworkContentElement abgeleitet. Die Lebensdauerereignisse Initialized, Loadedund Unloaded gelten für alle WPF-Elemente auf Frameworkebene. Das folgende Beispiel zeigt eine Elementstruktur, die in erster Linie in XAML implementiert ist. Der XAML-Code definiert ein übergeordnetes Canvas-Element, das geschachtelte Elemente enthält, die jeweils eine XAML-Attributsyntax verwenden, um Initialized-, Loaded- und Unloaded-Ereignishandler für die Lebensdauer anzufügen.

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

Eines der XAML-Elemente ist ein benutzerdefiniertes Steuerelement, das von einer Basisklasse abgeleitet ist, die Ereignishandler für die Lebensdauer in CodeBehind zuweist.

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.

Die Programmausgabe zeigt die Reihenfolge des Aufrufs von Initialized-, Loaded- und Unloaded-Lebensdauerereignissen für jedes Strukturobjekt an. Diese Ereignisse werden in den folgenden Abschnitten in der Reihenfolge beschrieben, in der sie für jedes Strukturobjekt ausgelöst werden.

Initialisiertes Lebensdauerereignis

Das WPF-Ereignissystem löst das Initialized-Ereignis für ein Element in folgenden Situationen aus:

  • Die Eigenschaften des Elements werden festgelegt.
  • Etwa zur gleichen Zeit, zu der das Objekt durch einen Aufruf seines Konstruktors initialisiert wird.

Einige Elementeigenschaften, z. B. Panel.Children, können untergeordnete Elemente enthalten. Übergeordnete Elemente können die Initialisierung erst melden, wenn ihre untergeordneten Elemente initialisiert sind. Die Eigenschaftswerte werden also beginnend mit dem am tiefsten geschachtelten Element(en) in einer Elementstruktur festgelegt, gefolgt von aufeinander folgenden übergeordneten Elementen bis hin zum Anwendungsstamm. Da das Initialized-Ereignis eintritt, wenn die Eigenschaften eines Elements festgelegt werden, wird dieses Ereignis zuerst auf dem am tiefsten geschachtelten Element bzw. den am tiefsten geschachtelten Elementen gemäß der Definition im Markup aufgerufen, gefolgt von den nachfolgenden übergeordneten Elementen bis hin zum Anwendungsstamm. Wenn Objekte in CodeBehind dynamisch erstellt werden, kann ihre Initialisierung außerhalb der Reihenfolge erfolgen.

Das WPF-Ereignissystem wartet nicht darauf, dass alle Elemente in einer Elementstruktur initialisiert werden, bevor das Initialized-Ereignis für ein Element ausgelöst wird. Wenn Sie also einen Initialized-Ereignishandler für jedes Element schreiben, denken Sie daran, dass umgebende Elemente in der logischen oder visuellen Struktur, insbesondere übergeordnete Elemente, möglicherweise nicht erstellt wurden. Oder ihre Membervariablen und Datenbindungen können nicht initialisiert werden.

Hinweis

Wenn das Initialized-Ereignis für ein Element ausgelöst wird, werden die Ausdrucksverwendungen des Elements, z. B. dynamische Ressourcen oder Bindung, nicht ausgewertet.

Geladenes Lebensdauerereignis

Das WPF-Ereignissystem löst das Loaded-Ereignis für ein Element in folgenden Situationen aus:

  • Die logische Struktur, die das Element enthält, ist vollständig und mit einer Präsentationsquelle verbunden. Die Präsentationsquelle stellt das Fensterhandle (HWND) und die Renderingoberfläche bereit.
  • Die Datenbindung an lokale Quellen, z. B. andere Eigenschaften oder direkt definierte Datenquellen, ist abgeschlossen.
  • Nachdem das Layoutsystem alle erforderlichen Werte für das Rendering berechnet hat.
  • Vor dem endgültigen Rendering.

Das Loaded-Ereignis wird für ein Element in einer Elementstruktur erst ausgelöst, wenn alle Elemente innerhalb der logischen Struktur geladen wurden. Das WPF-Ereignissystem löst zuerst das Loaded-Ereignis für das Stammelement einer Elementstruktur aus, und anschließend für jedes aufeinander folgende untergeordnete Element bis zu den am tiefsten geschachtelten Elementen. Obwohl dieses Ereignis einem Routingereignis vom Typ Tunneling ähnelt, überträgt das Loaded-Ereignis keine Ereignisdaten von einem Element in ein anderes, sodass das Markieren des Ereignisses als behandelt keine Auswirkungen hat.

Hinweis

Das WPF-Ereignissystem kann nicht garantieren, dass asynchrone Datenbindungen vor dem Loaded-Ereignis abgeschlossen wurden. Asynchrone Datenbindungen sind an externe oder dynamische Quellen gebunden.

Entladenes Lebensdauerereignis

Das WPF-Ereignissystem löst das Unloaded-Ereignis für ein Element in folgenden Situationen aus:

  • Beim Entfernen der Präsentationsquelle.
  • Beim Entfernen des visuellen übergeordneten Elements.

Das WPF-Ereignissystem löst zuerst das Unloaded-Ereignis für das Stammelement einer Elementstruktur aus, und anschließend für jedes aufeinander folgende untergeordnete Element bis zu den am tiefsten geschachtelten Elementen. Obwohl dieses Ereignis einem Routingereignis vom Typ Tunneling ähnelt, verteilt das Unloaded-Ereignis keine Ereignisdaten von Element zu Element, sodass das Markieren des Ereignisses als behandelt keine Auswirkungen hat.

Wenn das Unloaded-Ereignis für ein Element ausgelöst wird, kann für das übergeordnete Element oder ein höheres Element in der logischen oder visuellen Struktur bereits die Festlegung aufgehoben worden sein. „Festlegung aufgehoben“ bedeutet, dass die Datenbindungen, Ressourcenreferenzen und Stile eines Elements nicht mehr auf ihren normalen oder zuletzt bekannten Laufzeitwert festgelegt sind.

Andere Lebensdauerereignisse

Aus Sicht der Lebensdauerereignisse gibt es vier Haupttypen von WPF-Objekten: allgemeine Elemente, Fensterelemente, Navigationshosts und Anwendungsobjekte. Die Lebensdauerereignisse Initialized, Loaded und Unloaded gelten für alle Elemente auf Frameworkebene. Andere Lebensdauerereignisse gelten speziell für Fensterelemente, Navigationshosts oder Anwendungsobjekte. Informationen zu diesen anderen Lebensdauerereignissen finden Sie unter:

Siehe auch