いつか必ず後始末(弐)

いつか必ず後始末(壱)の続き

添付プロパティの終了処理をいつか必ず行う為のクラスを作る事にした。なお、仕事で作ったものとはちょっと変えてみた。

やりたい事。
・Loadedイベント発生時に、WindowのClosedイベントへハンドラメソッドを登録する。
・Window.Closedイベントで必ず添付プロパティに関する終了処理を行う。
・終了処理とは、添付プロパティ値変更時に行った処理を打ち消す処理の事。
 例:値変更時にイベントへハンドラを登録→終了時にはイベントからハンドラを削除
・継承したクラスで、必要な処理も実装できるクラスになれれば良い。

クラスの名前は何にしようか。まあ、適当に「AttachedPropertyWorker」とでもつけるか。
以下、コードとその説明を載せておく(長い)。
using System;
using System.ComponentModel;
using System.Windows;

namespace TawamureDays {

using TawamureDays.Containers;

/// <summary>
/// TawamureDays Attachedプロパティ作業用クラス
/// </summary>
public abstract class AttachedPropertyWorker : DisposableObject {

#region [1]インスタンス生成

/// <summary>
/// 作業用オブジェクトを設定します。
/// </summary>
/// <param name="fwElement">作業対象となるFrameworkElementオブジェクト</param>
/// <param name="boundProperty">当オブジェクトを設定する添付プロパティ</param>
/// <returns>生成された作業用オブジェクト</returns>
public static TWorker SetWorker<TWorker>(
FrameworkElement fwElement,
DependencyProperty boundProperty)
where TWorker : AttachedPropertyWorker, new() {
var newWorker = new TWorker();
newWorker.Attach(fwElement, boundProperty);
return newWorker;
}

/// <summary>
/// コンストラクタ
/// </summary>
protected AttachedPropertyWorker() {
return;
}

#endregion

#region [2]内部プロパティ

/// <summary>
/// 対象となるFrameworkElementを取得|設定します。
/// </summary>
protected FrameworkElement AttachedElement {
get; set;
}

/// <summary>
/// 当クラスをバインドする添付プロパティを取得|設定します。
/// </summary>
protected DependencyProperty BoundProperty {
get; set;
}

/// <summary>
/// ウィンドウ終了時にDisposeが実行されるかどうかを取得します。
/// </summary>
protected bool DisposeWhenClosed {
get; private set;
}

/// <summary>
/// 現在Attach中かどうかを取得します。
/// </summary>
protected bool NowAttached {
get; private set;
}

#endregion

#region [3]protectedメソッド

/// <summary>
/// 作業対象となるFrameworkElementオブジェクトに作業用オブジェクトを設定します。
/// </summary>
/// <param name="fwElement">FrameworkElementオブジェクト</param>
/// <param name="boundProperty">当オブジェクトを設定する添付プロパティ</param>
protected void Attach(FrameworkElement fwElement,
DependencyProperty boundProperty) {
if (fwElement == null) {
throw new ArgumentNullException("fwElement");
}

fwElement.Loaded += new RoutedEventHandler(AttachedElement_Loaded);
fwElement.Unloaded += new RoutedEventHandler(AttachedElement_Unloaded);

if (fwElement.IsLoaded && fwElement.GetValue(this.BoundProperty) == null) {
//Load済
this.AttachedElement_Loaded(fwElement, new RoutedEventArgs());
}

this.AttachedElement = fwElement;
this.BoundProperty = boundProperty;

if (this.BoundProperty != null) {
fwElement.SetValue(this.BoundProperty, this);
this.NowAttached = true;
//プロパティ値の変更を検知します
var descriptor = DependencyPropertyDescriptor.
FromProperty(
this.BoundProperty,
this.AttachedElement.GetType());
descriptor.AddValueChanged(this.AttachedElement,
new EventHandler(OnBoundPropertyChanged));
}

this.OnWorkerSet();
return;
}

/// <summary>初期化処理</summary>
protected virtual void OnWorkerSet() {
return;
}

/// <summary>
/// FrameworkElementLoaded時に行う処理
/// </summary>
/// <param name="e">イベントデータ</param>
protected virtual void OnAttachedElementLoaded(RoutedEventArgs e) {
return;
}

/// <summary>
/// FrameworkElementLoaded時に行う処理
/// </summary>
/// <param name="e">イベントデータ</param>
protected virtual void OnAttachedElementUnloaded(RoutedEventArgs e) {
return;
}

#endregion

#region [4]DisposableObject メンバー

/// <summary>
/// 内部リソースを解放します。
/// </summary>
/// <param name="disposing">false:アンマネージリソースのみ解放します。</param>
protected override void OnDispose(bool disposing) {
if (this.IsDisposed) {
return;
}

if (disposing) {

if (this.AttachedElement != null) {

if (this.BoundProperty != null) {
var descriptor = DependencyPropertyDescriptor.
FromProperty(this.BoundProperty,
this.AttachedElement.GetType());
descriptor.RemoveValueChanged(this.AttachedElement,
new EventHandler(OnBoundPropertyChanged));
descriptor = null;
}

var currentObj = this.AttachedElement.GetValue(this.BoundProperty);

if (currentObj != null && currentObj == this) {
//プロパティの設定値が自身の時のみクリアする
this.AttachedElement.SetValue(this.BoundProperty, null);
}

this.AttachedElement.Loaded -=
new RoutedEventHandler(AttachedElement_Loaded);
this.AttachedElement.Unloaded -=
new RoutedEventHandler(AttachedElement_Unloaded);
this.AttachedElement = null;
}

this.BoundProperty = null;
}
this.NowAttached = false;
this.DisposeWhenClosed = false;

base.OnDispose(disposing);
return;
}

#endregion

#region [5]イベント用メソッド

/// <summary>
/// 依存関係プロパティ値 変更イベント用メソッド
/// </summary>
/// <param name="sender">イベントソース</param>
/// <param name="e">イベントデータ</param>
private void OnBoundPropertyChanged(object sender, EventArgs e) {

var fwElement = sender as FrameworkElement;
var currentObj = fwElement.GetValue(this.BoundProperty);

if (currentObj == null || currentObj != this) {
this.NowAttached = false;

if (this.DisposeWhenClosed) {
//プロパティ値を入替えられた
fwElement.Loaded -=
new RoutedEventHandler(AttachedElement_Loaded);
fwElement.Unloaded -=
new RoutedEventHandler(AttachedElement_Unloaded);
} else {
this.Dispose();
}

} else {
this.NowAttached = true;
fwElement.Loaded +=
new RoutedEventHandler(AttachedElement_Loaded);
fwElement.Unloaded +=
new RoutedEventHandler(AttachedElement_Unloaded);
}

return;
}

/// <summary>
/// Loadedイベント用メソッド
/// </summary>
/// <param name="sender">イベントソース</param>
/// <param name="e">イベントデータ</param>
private void AttachedElement_Loaded(object sender, RoutedEventArgs e) {
this.AttachedElement.Loaded -= new RoutedEventHandler(AttachedElement_Loaded);

var window = Window.GetWindow(this.AttachedElement);

if (window != null) {
window.Closed += new EventHandler(OwnerWindow_Closed);
this.DisposeWhenClosed = true;
}

this.OnAttachedElementLoaded(e);
return;
}

/// <summary>
/// Unloadedイベント用メソッド
/// </summary>
/// <param name="sender">イベントソース</param>
/// <param name="e">イベントデータ</param>
private void AttachedElement_Unloaded(object sender, RoutedEventArgs e) {
AttachedElement.Unloaded -= new RoutedEventHandler(AttachedElement_Unloaded);

var window = Window.GetWindow(this.AttachedElement);

if (window != null) {
window.Closed -= new EventHandler(OwnerWindow_Closed);
this.DisposeWhenClosed = false;
}

this.OnAttachedElementUnloaded(e);
return;
}

/// <summary>
/// ウィンドウ終了イベント用メソッド
/// </summary>
/// <param name="sender">イベントソース</param>
/// <param name="e">イベントデータ</param>
private void OwnerWindow_Closed(object sender, EventArgs e) {

var window = sender as Window;

if (window != null) {
window.Closed -= new EventHandler(OwnerWindow_Closed);
}

this.Dispose();
return;
}

#endregion
}
}
長いな…

[1]インスタンス生成
 new で素直に作ってたんだけど、使い方が微妙だった。

//...
new XXXXXWorker(fwElement, XXXXProperty);
みたいな感じになってしまう。なんかこうモヤッとするので、staticなメソッドで生成するようにした。

//...
AttachedPropertyWorker.SetWorker<XXXXXWorker>(fwElement, XXXXProperty);
うん。キモチの問題だ。
[2]内部プロパティ
 AttachedElement...処理対象のFrameworkElementオブジェクト。
  FrameworkElementがLoadedイベントを持っている。UIElementでは駄目なんだよな…Orz。まあ、Controlクラスでも良いといえば良いんだけど。
 BoundProperty ...当クラスを保管しておくプロパティ。
  この方式のやや難点な所で、作業対象のプロパティとは別にもう一つプロパティを用意する事になる。
 DisposeWhenClosed ... Window.Closedイベントをリッスンしてる状態かどうか。
  Unloadedされたら外すようにしているので。
 NowAttached...当インスタンス自身がプロパティに設定されているかどうか。
  インスタンスを入替えられることがあるかもしれないので。

[3]protectedメソッド
 内部でやっておきたい処理。virtualあり。なんとなくabstract無し。
 Attachメソッド...初期化処理。
  Loadedイベントにハンドラを登録したり、BoundPropertyの変更検知用イベントへハンドラを登録している。
  これが処理されないと始まらない。
 OnWorkerSetメソッド...(virtual)
  各継承クラスで、個別にイベント登録したりする時にoverrrideする。
 OnAttachedElementLoadedメソッド(virtual)
  Loadedイベント発生時に呼び出されるメソッド
 OnAttachedElementUnloadedメソッド(virtual)
  Unloadedイベント発生時に呼び出されるメソッド

[4]DisposableObject メンバー
 終了時に処理したいものを実装します。
 OnWorkerSetメソッドで実装した事を打ち消すように実装します。
 特にイベントへの登録なんかは削除必須。

[5]イベント用メソッド
 各々(Loaded, Unloaded, Window.Closed)のイベントに登録する為のハンドラメソッド。例外処理に手を抜きがちなコード。本来なら、ガチガチにtry/catchを組むべきか。

このクラスを実際に使って、前回に書いた、バイト制限の実装をしてみる。
つづく
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m