WPF

[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();
        }
        
	}

 
 

-WPF
-,