[WPF] BehaviorのOnDetaching
ビヘイビアで、以下のようにOnAttached/OnDetachingメソッドで初期化/終了処理をしているコードをよく見かける。
protected override void OnAttached() { AssociatedObject.SelectionChanged += ListBox_SelectionChanged; } protected override void OnDetaching() { AssociatedObject.SelectionChanged -= ListBox_SelectionChanged; }
一見、問題無さそうに見えるが、ビヘイビアのOnDetachingはコールされないことがあるため、メモリやリソースのリークが発生する可能性がある。
確実に初期化/終了処理を行いたいのであれば、AssociatedObjectのLoad/Unloadイベントを使った方がよい。
public class BehaviorBase<T> : Behavior<T> where T : FrameworkElement { #region フィールド /// <summary> /// セットアップ状態 /// </summary> private bool isSetup = false; /// <summary> /// Hook状態 /// </summary> private bool isHookedUp = false; /// <summary> /// 対象オブジェクト /// </summary> private WeakReference weakTarget; #endregion // フィールド #region メソッド /// <summary> /// Changedハンドラ /// </summary> protected override void OnChanged() { base.OnChanged(); //==== 関連オブジェクト有無判定 ====// var target = AssociatedObject; if (target != null) { //-==- 有り -==-// //==== Hook開始 ====// HookupBehavior(target); } else { //-==- 無し -==-// //==== Hook解除 ====// UnHookupBehavior(); } } /// <summary> /// ビヘイビアをHookする。 /// </summary> /// <param name="target"></param> private void HookupBehavior(T target) { //==== Hook状態判定 ====// if (isHookedUp) { //-==- Hook中 -==-// return; } //==== Hook開始 ====// weakTarget = new WeakReference(target); isHookedUp = true; target.Unloaded += OnTargetUnloaded; target.Loaded += OnTargetLoaded; //==== ビヘイビアのセットアップ ====// SetupBehavior(); } /// <summary> /// ビヘイビアをUnhookする。 /// </summary> private void UnHookupBehavior() { //==== Hook状態判定 ====// if (!isHookedUp) { //-==- 未Hook -==-// return; } //==== Hook解除 ====// isHookedUp = false; var target = AssociatedObject ?? (T)weakTarget.Target; if (target != null) { target.Unloaded -= OnTargetUnloaded; target.Loaded -= OnTargetLoaded; } //==== ビヘイビアのクリーンアップ ====// CleanupBehavior(); } /// <summary> /// [関連オブジェクト] Loadedハンドラ /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnTargetLoaded(object sender, RoutedEventArgs e) { //==== ビヘイビアのセットアップ ====// SetupBehavior(); } /// <summary> /// [関連オブジェクト] Unloadedハンドラ /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnTargetUnloaded(object sender, RoutedEventArgs e) { //==== ビヘイビアのクリーンアップ ====// CleanupBehavior(); } /// <summary> /// セットアップ時の処理を行う。 /// </summary> protected virtual void OnSetup() { } /// <summary> /// クリーンアップ時の処理を行う。 /// </summary> protected virtual void OnCleanup() { } /// <summary> /// ビヘイビアのセットアップを行う。 /// </summary> private void SetupBehavior() { if (isSetup) { return; } isSetup = true; OnSetup(); } /// <summary> /// ビヘイビアのクリーンアップを行う。 /// </summary> private void CleanupBehavior() { if (!isSetup) { return; } isSetup = false; OnCleanup(); } #endregion // メソッド }
使い方は簡単。
派生クラスを作って、そのなかでOnSetup/OnCleanupメソッドをoverride。
このメソッド内に初期化/終了処理を実装する。
public class HogeBehaviour : BehaviorBase<ListBox> { /// <summary> /// セットアップ時の処理を行う。 /// </summary> protected override void OnSetup() { base.OnSetup(); AssociatedObject.SelectionChanged += ListBox_SelectionChanged; } /// <summary> /// クリーンアップ時の処理を行う。 /// </summary> protected override void OnCleanup() { AssociatedObject.SelectionChanged -= ListBox_SelectionChanged; base.OnCleanup(); } }