オブジェクトの有効期間イベント (WPF .NET)

Microsoft .NET マネージド コードのすべてのオブジェクトは、その有効期間中に "作成"、"使用"、"破棄" というステージを経ます。 Windows Presentation Foundation (WPF) には、これらのステージがオブジェクトで発生したときに、有効期間イベントを生成することで通知する機能があります。 WPF フレームワークレベル要素 (ビジュアル オブジェクト) に対して、WPF は InitializedLoadedUnloaded の有効期間イベントを実装しています。 開発者は、要素を含むコードビハインド操作のフックとしてこれらの有効期間イベントを使用できます。 この記事では、ビジュアル オブジェクトの有効期間イベントについて説明します。次に、他の有効期間イベント (特にウィンドウ要素、ナビゲーション ホスト、またはアプリケーション オブジェクトに適用されるもの) について紹介します。

重要

.NET 7 と .NET 6 用のデスクトップ ガイド ドキュメントは作成中です。

前提条件

この記事は、WPF 要素レイアウトをツリー形式で概念化できるしくみについて基本的な知識があり、「ルーティング イベントの概要」を読んでいることを前提としています。 この記事の例について理解するには、Extensible Application Markup Language (XAML) を使い慣れていて、WPF アプリケーションの記述方法を理解していると役に立ちます。

ビジュアル オブジェクトの有効期間イベント

WPF フレームワークレベル要素は FrameworkElement または FrameworkContentElementから派生します。 InitializedLoadedUnloaded の有効期間イベントは、すべての WPF フレームワークレベル要素に共通です。 次の例は、主に XAML で実装された要素ツリーを示しています。 この XAML は、入れ子になった要素を含む親 Canvas 要素を定義しています。各要素は XAML 属性構文を使って InitializedLoadedUnloaded の有効期間イベント ハンドラーをアタッチしています。

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

XAML 要素の 1 つはカスタム コントロールであり、コードビハインドで有効期間イベント ハンドラーを割り当てる基底クラスから派生しています。

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.

このプログラム出力は、各ツリー オブジェクトに対する InitializedLoadedUnloaded の有効期間イベントの呼び出し順を示しています。 これらイベントについては、各ツリー オブジェクトで発生した順に以下のセクションで説明します。

初期化された有効期間イベント

WPF イベント システムは、次のタイミングで、ある要素に対して Initialized イベントを生成します。

  • 要素のプロパティが設定されたとき。
  • コンストラクターの呼び出しによってオブジェクトが初期化されたときとほぼ同じ頃。

Panel.Children などの一部の要素プロパティには、子要素を含めることができます。 子要素が初期化されるまで、その親要素は初期化を報告できません。 そのため、要素ツリーで最も深い階層の入れ子になった要素から始まり、アプリケーション ルートまでの一連の親要素に対して、プロパティ値が設定されます。 要素のプロパティが設定されると Initialized イベントが発生するため、マークアップで定義された最も深い階層の入れ子になった要素から始まり、アプリケーション ルートまでの一連の親要素に対して、そのイベントが呼び出されます。 コードビハインドでオブジェクトを動的に作成すると、それらの初期化が順序どおりにならないことがあります。

WPF イベント システムは、要素ツリー内のすべての要素が初期化されるのを待たずに、ある要素に対して Initialized イベントを生成します。 そのため、どの要素に対して Initialized イベント ハンドラーを記述する場合でも、論理ツリーまたはビジュアル ツリー内の周囲の要素 (特に親要素) が作成されていない可能性があることに注意してください。 また、そのメンバー変数とデータ バインディングが初期化されていない可能性があります。

Note

ある要素に関する Initialized イベントが発生した場合、その要素の式の使用 (動的リソースやバインディングなど) は評価されません。

読み込まれた有効期間イベント

WPF イベント システムは、次のタイミングで、ある要素に対して Loaded イベントを生成します。

  • 要素を含む論理ツリーが完成し、プレゼンテーション ソースに接続されたとき。 プレゼンテーション ソースはウィンドウ ハンドル (HWND) とレンダリング画面を提供します。
  • 他のプロパティや直接定義されたデータ ソースなど、ローカル ソースへのデータ バインディングが完了したとき。
  • レンダリングに必要なすべての値をレイアウト システムが計算した後。
  • 最終的なレンダリングの前。

論理ツリー内の "すべての" 要素が読み込まれるまで、要素ツリーのどの要素でも Loaded イベントは発生しません。 WPF イベント システムは、まず要素ツリーのルート要素に対して、次に最も深い階層の入れ子になった要素までの一連の各子要素に対して Loaded イベントを生成します。 このイベントはトンネリング ルーティング イベントに似ているようですが、Loaded イベントはイベント データをある要素から別の要素に伝達しないので、イベントを処理済みとマークしても効果はありません。

Note

WPF イベント システムを使っても、Loaded イベントの前に非同期データ バインディングが完了していることを保証できません。 非同期データ バインディングは、外部または動的なソースにバインドするものです。

読み込まれていない有効期間イベント

WPF イベント システムは、次のタイミングで、ある要素に対して Unloaded イベントを生成します。

  • そのプレゼンテーション ソースを削除したとき、または
  • そのビジュアルの親を削除したとき

WPF イベント システムは、まず要素ツリーのルート要素に対して、次に最も深い階層の入れ子になった要素までの一連の各子要素に対して Unloaded イベントを生成します。 このイベントはトンネリング ルーティング イベントに似ているようですが、Unloaded イベントはイベント データを要素間で伝達しないので、イベントを処理済みとマークしても効果はありません。

ある要素に対して Unloaded イベントが発生すると、その要素の要素や、論理ツリーやビジュアル ツリーで上位の要素が既に "未設定" にされている可能性があります。 未設定とは、要素のデータ バインディング、リソース参照、スタイルが、通常の値または最後の既知のランタイム値に現在は設定されていないことを意味します。

その他の有効期間イベント

有効期間イベントの観点から説明すると、主に 4 種類の WPF オブジェクトがあります。一般的な要素、ウィンドウ要素、ナビゲーション ホスト、アプリケーション オブジェクトです。 InitializedLoadedUnloaded の有効期間イベントは、すべてのフレームワークレベル要素に適用されます。 その他の有効期間イベントは、特にウィンドウ要素、ナビゲーション ホスト、またはアプリケーション オブジェクトに適用されます。 このようなその他の有効期間イベントについては、以下を参照してください。

関連項目