2015年12月8日火曜日

ListViewItemのイベントをMVVMスタイルのプログラムで受信する - 添付プロパティ編

さて、以前にReactive Propertyを使って苦し紛れにListViewItemのイベントを受信する方法を紹介しました。しかし、その方法は本当にかなり苦し紛れで、それらしいスタイルは曲がりなりにも美しいとは言えませんでした。

しかし、今回は別の方法でついにそれっぽい書き方をすることに成功しました。 その方法はずばり、添付プロパティを使う方法です。
WPFには添付プロパティという機構があり、本来そのコントロールが持っているプロパティではない任意のプロパティを作成し、コントロールに添付することができます。Grid.Rowなどのプロパティがその一つで、例えばTextBlockは当然Grid以外にも配置されますからGridの何行目何列目に位置するなんていうプロパティは持っていませんが、添付プロパティを使うことでそういう情報を付加できるようになるものです。

というわけで、添付プロパティで適当なコマンドをListViewItemに添付してしまえば、そこで上手くやることでそのコマンドを発火させることができるのではないかというアプローチになります。

public static class DoubleClickBehavior
{
    public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
        Regex.Replace(nameof(CommandProperty), "Property$", ""),    //末尾のPropertyを消す
        typeof(ICommand),
        typeof(DoubleClickBehavior),
        new FrameworkPropertyMetadata(null, (sender, e)=> {
            Control ctrl = sender as Control;

            if(ctrl != null) {
                ICommand oldCommand = (ICommand)e.OldValue;
                ICommand newCommand = (ICommand)e.NewValue;

                if((oldCommand != null) && (newCommand == null))    //購読を停止
                    ctrl.MouseDoubleClick -= Control_MouseDoubleClick_ForCommand;
                if((oldCommand == null) && (newCommand != null))    //購読を開始
                    ctrl.MouseDoubleClick += Control_MouseDoubleClick_ForCommand; ;
            }
        }));

    public static void SetCommand(DependencyObject obj, ICommand value)
    {
        obj.SetValue(CommandProperty, value);
    }

    public static ICommand GetCommand(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    private static void Control_MouseDoubleClick_ForCommand(object sender, MouseButtonEventArgs e)
    {
        ICommand com = GetCommand((Control)sender);

        if(com.CanExecute(e))
            com.Execute(e);
    }
}

こんな感じです。添付プロパティはDependencyProperty.RegisterAttached()メソッドを使って作った****Propertyという名前のプロパティで、セッターやゲッターはGet****()、Set****()という名前で作るというお作法になっています。そしていずれもstaticです。

ここでミソなのは、添付プロパティのコンストラクタに与えるFrameworkPropertyMetadataクラスは、コンストラクタに値の変更時にその通知を受け取るハンドラを渡すオーバーロードがあるということです。もちろんその通知のsenderは添付プロパティが取り付けられたコントロールになるので、それに対してイベントの購読を行えば、イベントを拾って添付プロパティに投げることが可能になります。

XAMLではこんな感じになります。

<ListView ItemsSource="{Binding Items}" >
    <ListView.View>
        <GridView>
            <!-- 中略 -->
        </GridView>
    </ListView.View>
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="b:DoubleClickBehavior.Command" Value="{Binding DoubleClickCommand}" />
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

いたって普通にItemsContainerStyleでプロパティを設定しています。しかしこれが添付プロパティで、上記の通りこれに値をバインディングすることでイベントを購読できるようになります。

それにしてもこのアイディアを知ったときは目から鱗だったなあ。

0 件のコメント:

コメントを投稿