for WPF developers
添付ビヘイビアとは、既存のコントロールに後付けで新たに添付することができ、 特定のイベントに対する振る舞いを定義することができるものです。
サンプルプロジェクトはここからダウンロードできます。
ページ内リンク
ここでは、ファイルを開くためのボタンを作ることを考えます。 ファイルを開く方法はいろいろありますが、 ここではコモンダイアログを使用してファイルを開くことを考えます。 コモンダイアログを使うためには次のようなコードが必要になります。
var dlg = new Microsoft.Win32.OpenFileDialog(); var result = dlg.ShowDialog(); if ((result != null) && (result == true)) { // ファイルのフルパスを取得 var filename = dlg.FileName; // ファイルを開く処理 }
ところで、このようなコードは一体どのに書くべきなのでしょうか。 ShowDialog() メソッドでコモンダイアログを開いているため、 MVVM パターンに則って考えると、View の範疇となります。 しかし、その後の処理は View の範疇を超えるため、 得られたファイルのフルパスは ViewModel 側に通知して、 その後の処理は ViewModel もしくは Model にまで掘り下げる必要があるかもしれません。 このような場合、添付ビヘイビアを用いることで非常にスマートに記述できます。
それでは次に添付ビヘイビアの作成方法についてみていきます。
添付ビヘイビアを作成するには、まず添付プロパティを持つクラスを作成します。 ここでは ReadFileBehavior という名前のクラスに IsAttached 添付プロパティを作成します。
namespace CustomBehavior.Views.Behaviors { using System; using System.Windows; using System.Windows.Controls; public class ReadFileBehavior { #region IsAttached 添付プロパティ定義 public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(ReadFileBehavior), new FrameworkPropertyMetadata(false, OnIsAttachedChanged)); public static bool GetIsAttached(DependencyObject target) { return (bool)target.GetValue(IsAttachedProperty); } public static void SetIsAttached(DependencyObject target, bool value) { target.SetValue(IsAttachedProperty, value); } #endregion IsAttached 添付プロパティ定義 /// <summary> /// IsAttached 添付プロパティ値変更イベントハンドラ /// </summary> /// <param name="d">イベント発生元</param> /// <param name="e">イベント引数</param> private static void OnIsAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } } }
ここで、添付プロパティの定義中の FrameworkPropertyMetadata に対する第 2 引数に着目します。 FrameworkPropertyMetadata のコンストラクタはいくつかオーバーライドがありますが、 ここでは OnIsAttachedChanged という静的メソッドへのデリゲートを指定しています。 このようにデリゲートを指定することで、そのプロパティが変更されたときのイベントハンドラを登録することができます。
ここでは IsAttached 添付プロパティの既定値が false となっているため、 true となったときに OnIsAttached イベントハンドラが呼び出されることになります。 もちろんその後 IsAttached 添付プロパティがもう一度 false になったらまた OnIsAttached イベントハンドラが呼ばれます。
このイベントハンドラに対して次のようなコードを記述します。
/// <summary> /// IsAttached 添付プロパティ値変更イベントハンドラ /// </summary> /// <param name="d">イベント発生元</param> /// <param name="e">イベント引数</param> private static void OnIsAttachedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var button = d as Button; if (button != null) { if (GetIsAttached(button)) { // IsAttached 添付プロパティが true の場合はイベントハンドラを登録する button.Click += OnClick; } else { // IsAttached 添付プロパティが false の場合はイベントハンドラを登録解除する button.Click -= OnClick; } } } /// <summary> /// Click イベントハンドラ /// </summary> /// <param name="sender">イベント発生元</param> /// <param name="e">イベント引数</param> private static void OnClick(object sender, RoutedEventArgs e) { }
ここではファイルを読み込むためのボタンを作りたいので、Code 1 に示したコードをこの Click イベントハンドラに記述します。
/// <summary> /// Click イベントハンドラ /// </summary> /// <param name="sender">イベント発生元</param> /// <param name="e">イベント引数</param> private static void OnClick(object sender, RoutedEventArgs e) { var dlg = new Microsoft.Win32.OpenFileDialog(); var result = dlg.ShowDialog(); if ((result != null) && (result == true)) { // ファイルのフルパスを取得 var filename = dlg.FileName; // ファイルを開く処理 System.Console.WriteLine(filename); } }
上記のコードでコモンダイアログによってファイルのフルパスを取得するところまでできましたが、 そのフルパスは出力ウィンドウに表示されるだけで、ViewModel に伝える手段がありません。 そこで、次のような添付プロパティを追加します。
#region Callback 添付プロパティ定義 public static readonly DependencyProperty CallbackProperty = DependencyProperty.RegisterAttached("Callback", typeof(Action<string>), typeof(ReadFileBehavior), new FrameworkPropertyMetadata(null)); public static Action<string> GetCallback(DependencyObject target) { return (Action<string>)target.GetValue(CallbackProperty); } public static void SetCallback(DependencyObject target, Action<string> value) { target.SetValue(CallbackProperty, value); } #endregion Callback 添付プロパティ定義
この Callback 添付プロパティを追加した Click イベントハンドラのコードは次のようになります。
/// <summary> /// Click イベントハンドラ /// </summary> /// <param name="sender">イベント発生元</param> /// <param name="e">イベント引数</param> private static void OnClick(object sender, RoutedEventArgs e) { var button = sender as Button; if (button != null) { var dlg = new Microsoft.Win32.OpenFileDialog(); var result = dlg.ShowDialog(); if ((result != null) && (result == true)) { // ファイルのフルパスを取得 var filename = dlg.FileName; // ファイルを開く処理 var callback = GetCallback(button); if (callback != null) callback(filename); } } }
上記で作成した添付ビヘイビアを実際に Button コントロールに追加してみましょう。
<Window x:Class="CustomBehavior.Views.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:CustomBehavior.Views.Behaviors" Title="MainView" Height="300" Width="300"> <StackPanel> <Button Content="Read File" b:ReadFileBehavior.IsAttached="True" b:ReadFileBehavior.Callback="{Binding ReadFileCallback}" /> </StackPanel> </Window>
namespace CustomBehavior.ViewModels { using System; public class MainViewModel { /// <summary> /// ファイル読込のコールバックを取得します。 /// </summary> public Action<string> ReadFileCallback { get { return OnReadFile; } } /// <summary> /// ファイルを読み込みます。 /// </summary> /// <param name="filename">ファイル名を指定します。</param> private void OnReadFile(string filename) { System.Console.WriteLine("ViewModel : " + filename); } } }
実行すると、 Button コントロールを押すとコモンダイアログが表示され、 適当なファイルを選択すると、 出力ウィンドウに "ViewModel : (ファイルのフルパス)" が表示されます。
このように添付ビヘイビアは任意のコントロールに対して後から外付けでその振る舞いを追加できるため、 再利用性が高いというメリットがあります。
Designed by CSS.Design Sample