tips - 添付ビヘイビアを作成する

 添付ビヘイビアとは、既存のコントロールに後付けで新たに添付することができ、 特定のイベントに対する振る舞いを定義することができるものです。

 サンプルプロジェクトはここからダウンロードできます。

ページ内リンク

概要

 ここでは、ファイルを開くためのボタンを作ることを考えます。 ファイルを開く方法はいろいろありますが、 ここではコモンダイアログを使用してファイルを開くことを考えます。 コモンダイアログを使うためには次のようなコードが必要になります。

var dlg = new Microsoft.Win32.OpenFileDialog();
var result = dlg.ShowDialog();
if ((result != null) && (result == true))
{
    // ファイルのフルパスを取得
    var filename = dlg.FileName;

    // ファイルを開く処理
}
Code 1 : コモンダイアログを使用する

 ところで、このようなコードは一体どのに書くべきなのでしょうか。 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)
        {
        }
    }
}
Code 2 : IsAttached 添付プロパティを持つ ReadFileBehavior クラスの定義

 ここで、添付プロパティの定義中の 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 3 : Button コントロールなら Click イベントにイベントハンドラを登録または登録解除する
この添付プロパティが Button コントロールに対して設定された場合、 IsAttached 添付プロパティが true なら Click イベントに対して何か処理をおこない、 false なら処理をおこなっていたイベントハンドラの登録を解除しています。 このようなコードを記述することで、 Button コントロールの Click イベントに対する振る舞いを ここで定義することができるようになります。 これが添付ビヘイビアです。

 ここではファイルを読み込むためのボタンを作りたいので、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);
    }
}
Code 4 : コモンダイアログでファイルを開く

 上記のコードでコモンダイアログによってファイルのフルパスを取得するところまでできましたが、 そのフルパスは出力ウィンドウに表示されるだけで、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 添付プロパティ定義
Code 5 : Callback 添付プロパティを追加する
Callback 添付プロパティは Action<string> という型で、string 型を引数に持つ void 型のメソッドへのデリゲートです。 このデリゲートを使って取得したファイルのフルパスを ViewModel に伝えようというわけです。

 この 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);
        }
    }
}
Code 6 : ファイルのフルパスを Callback 添付プロパティに渡している

使用方法

 上記で作成した添付ビヘイビアを実際に 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>
Code 7 : コントロールに添付ビヘイビアを追加する
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);
        }
    }
}
Code 8 : ViewModel 側のコード
ReadFileBehavior クラスが属する名前空間のエイリアスを定義して、 IsAttached 添付プロパティを Button コントロールに添付しています。 また、Callback 添付プロパティには ViewModel の ReadFileCallback プロパティをデータバインドしています。

実行すると、 Button コントロールを押すとコモンダイアログが表示され、 適当なファイルを選択すると、 出力ウィンドウに "ViewModel : (ファイルのフルパス)" が表示されます。

 このように添付ビヘイビアは任意のコントロールに対して後から外付けでその振る舞いを追加できるため、 再利用性が高いというメリットがあります。

Designed by CSS.Design Sample