コントロールの初期化の実行順
About
WPFのコントロール(FrameworkElement)では、コンストラクタの他に、OnInitialized、BeginInit、EndInitメソッドがオーバーライドできますが、これらの実行順について確認しておきます。
条件によって変動するので、最後まで目を通したほうが良いと思われます。公式のいずこかに載っていればよいのですが。
コードからインスタンスを生成して追加されるコントロールではEndInitメソッドが呼ばれないので、現実的にはOnInitializedメソッドで初期化処理を行い、必要ならさらにEndInitメソッドでも処理を実装する必要があるようです。
コンストラクタと各メソッドの実行順
次のようなコードとブレークポイントで実験しました。ブレークポイントは「●」です。
public コンストラクタ() ● :base() { ● Console.WriteLine("Constructor : "+this.Width); } #endregion Constructor protected override void OnInitialized(EventArgs e) { ● base.OnInitialized(e); ● Console.WriteLine("OnInitialized : " + this.Width); } public override void BeginInit() { ● base.BeginInit(); ● Console.WriteLine("BeginInit : " + this.Width); } public override void EndInit() { ● base.EndInit(); ● Console.WriteLine("EndInit : " + this.Width); }
ブレークポイントを打って確認しますがイマイチ要領を得ない結果になりました。実装時にbaseメソッドを記述する位置によって、それぞれのメソッド移動の順が制御されてしまうので、初期化手順に注意が必要なタイミングがあるかもしれません。
- コンストラクタ:base()
- コンストラクタ の処理
- BeginInit:base()
- BeginInit の処理
- EndInit():base()
- OnInitialized:base()
- OnInitialized の処理
- EndInit の処理
プロパティ値の代入
先の実験用ソースコードを見ればわかりますが、XAMLなどから代入することができるプロパティの値が、どのタイミングで与えられるのかを確認します。ここではWidth=100を与えてそれを出力することで確認しました。結果は次の通りです。OnInitializedが呼ばれた時点で値が入っていることが確認できます。
Constructor : NaN (非数値) BeginInit : NaN (非数値) OnInitialized : 100 EndInit : 100
コンポーネントの追加による順序の変動
先の項目で解説した順序には当てはまらない場合があります。例外的に、コンストラクタ内で新たなコンポーネント(FrameworkElement)を追加する場合などには、実行順序が変動することが確認できました。
ここでは例にCanvasなどを想定して、コンストラクタ内で、その子要素にEllipseを新たに追加しています。追加に限らずコンポーネントの設定値の変更、削除、その他の操作でも実行順が変更されるかもしれません。全てを確認するのは大変な作業になるので、ここではこれ以外の例については扱いません。
public コンストラクタ() ● :base() { ● this.Children.Add(new Ellipse());//※追加された実装 ● Console.WriteLine("Constructor : "+this.Width); } #endregion Constructor protected override void OnInitialized(EventArgs e) { ● base.OnInitialized(e); ● Console.WriteLine("OnInitialized : " + this.Width); } public override void BeginInit() { ● base.BeginInit(); ● Console.WriteLine("BeginInit : " + this.Width); } public override void EndInit() { ● base.EndInit(); ● Console.WriteLine("EndInit : " + this.Width); }
実行順は次のように変化しました。大きく変わっていることが確認できます。
- コンストラクタ:base()
- コンポーネントの追加/Children.Add
- OnInitialized:base()
- OnInitialized の処理
- コンストラクタ の処理
- BeginInit:base()
- BeginInit の処理
- EndInit():base()
- EndInit の処理
プロパティへの代入のタイミングも変化する
出力結果を次に示します。先の例ではOnInitializedメソッド内でWidthの値を取得することができていましたが、実行順が前に移動することによって取得することができなくなっている点に注意する必要があります。
つまり設定されたプロパティの値(ここではWidth)を利用した処理を実装する場合には、EndInitメソッドをオーバライドしたメソッド内で実装することが確実な手段である、ということになります。
OnInitialized : NaN (非数値) Constructor : NaN (非数値) BeginInit : NaN (非数値) EndInit : 100
OnApplyTemplateについて
CustomControlなどを実装する場合に重要となってくる、OnApplyTemplateについて、少しだけメモをしておきます。必要になり次第詳細をまとめるつもりではいますが。
GetTemplateChildメソッドを利用して子要素を取得することができるタイミングは、OnApplyTemplateメソッド中であるようです。OnApplyTemplateメソッドはEndInitメソッドよりも後に実行されるようなので注意する必要があります。EndInitメソッド中ではGetTemplatedChildメソッドはnullが返ることを確認しています(CustomControlで検証)。