< Excel の全シートの表示倍率を設定するマクロ | address 要素を自動的にリンクする >

October 28, 2009

TreeView の SelectedItemChanged に Command をバインドしたい。

遅ればせながら MVVM パターンの理解を深めるために WPF でエクスプローラーもどきを作っています。TreeView にディレクトリの木構造が表示され、任意のディレクトリを選択すると、その中のディレクトリとファイルの一覧が ListView に表示される、単純なやつです。

WPF の TreeView って初めて使ったんですけど、SelectedItem プロパティが読み取り専用になってて、Binding を設定したり出来ないんですよね。ViewModel 側で選択されたものを知りたいんだけど、なかなか一筋縄では行かない感じ。TreeView や TreeViewItem のイベントで設定すれば望みのことは出来るだろうけど、できれば View から ViewModel にアクセスせずに実現したい。

なんかいい方法ないかなーと思って調べたら、ちょうど TextBox で Enter キーを叩いたときに Command を実行するようなことをしてるのを見つけました。
添付ビヘイビアでTextBoxにCommandを実装してみた - SharpLab.
添付プロパティ内でイベントハンドラを設定したりするの、添付ビヘイビアって言うんですね。知らなかったです。手法自体は使ったことありましたが。

記事を参考にして、TreeView の SelectedItemChanged でコマンドを実行する添付ビヘイビアを定義してみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace WpfExplorerApp
{
    public class TreeViewBehaviors
    {
        public static ICommand GetOnSelectedItemChanged (DependencyObject d)
        {
            return (ICommand) d.GetValue (OnSelectedItemChangedProperty);
        }

        public static void SetOnSelectedItemChanged (DependencyObject d, ICommand value)
        {
            d.SetValue (OnSelectedItemChangedProperty, value);
        }

        public static readonly DependencyProperty OnSelectedItemChangedProperty =
            DependencyProperty.RegisterAttached ("OnSelectedItemChanged", typeof (ICommand), typeof (TreeViewBehaviors), new UIPropertyMetadata (null, OnSelectedItemChangedPropertyChanged));

        static void OnSelectedItemChangedPropertyChanged (DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            var treeView = d as TreeView;
            if (treeView == null)
                return;

            if (args.NewValue is ICommand)
                treeView.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object> (OnTreeViewSelectedItemChanged);
            else
                treeView.SelectedItemChanged -= new RoutedPropertyChangedEventHandler<object> (OnTreeViewSelectedItemChanged);
        }

        static void OnTreeViewSelectedItemChanged (object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var treeView = e.OriginalSource as TreeView;
            if (treeView == null)
                return;

            var command = GetOnSelectedItemChanged (treeView);
            if (command == null)
                return;

            command.Execute (treeView.SelectedItem);
        }
    }
}

で、ViewModel 側に SetSelectedItemCommand って Command を定義。

namespace WpfExplorerApp.ViewModel
{
    public class DirectoryTreeViewModel : ViewModelBase
    {
        DirectoryViewModel _selectedItem;
        RelayCommand _setSelectedItemCommand;

        public DirectoryViewModel SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (_selectedItem == value)
                    return;
                _selectedItem = value;
                OnPropertyChanged ("SelectedItem");
            }
        }

        public ICommand SetSelectedItemCommand
        {
            get
            {
                if (_setSelectedItemCommand == null)
                    _setSelectedItemCommand = new RelayCommand (
                        p => SetSelectedItem (p));
                return _setSelectedItemCommand;
            }
        }

        private void SetSelectedItem (object parametor)
        {
            var directory = parametor as DirectoryViewModel;
            SelectedItem = directory;
        }
    }
}

で、View 側で、この Command への Binding を記述します。

    <TreeView local:TreeViewBehaviors.OnSelectedItemChanged="{Binding Path=SetSelectedItemCommand}">
    </TreeView>

期待通り動いてるみたい。

トラックバック

このエントリーにトラックバック:
http://frog.raindrop.jp/cgi-bin/mt/mt-tb.cgi/2442

コメント

コメントする

※ コメントスパム対策のため、コメント本文はおはよう、こんにちわ、こんばんわのいずれかより始めるようにしてください。

name:
email:

※ 必要ですが、表示しません。

url:
情報を保存する ?