TreeViewからSelectedItemを取り出す(C# WPF)

C#

TreeViewのSelectedItemはsetterがないため、Bindingできません。
DataGridのSelectedItemsみたいなものですね。

これを解消するには大別して3つあります。
Behaviorで別の依存関係プロパティを設定し、SelectedItemChangedを購読して移し替えること。
TreeViewを継承し、SelectedItemをBindできる別のコントロールを作ってしまう。
EventToReactiveCommand等を使ってEventArgsを取り出す方法があります。

今回は EventToReactiveCommand を使ってSelectedItemを取り出したいと思います。

最小構成

ViewModel

ViewModel

public ReactiveCommand<RoutedPropertyChangedEventArgs<object>> SelectionChangedCommand { get; set; }

TreeViewのSelectedItemChangedイベントはRoutedPropertyChangedEventArgs<object>を使うのでこれをReactiveCommandの<T>に指定します。

TreeView.SelectedItemChanged イベント (System.Windows.Controls)
SelectedItem が変更されたときに発生します。Occurs when the SelectedItem changes.

RoutedPropertyChangedEventArgs のNewValueプロパティに新たに選択されたアイテムが格納されています。

Xaml

XAML

// xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NET46"

<TreeView ItemsSource="{Binding Directories}">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedItemChanged">
            <interactivity:EventToReactiveCommand Command="{Binding SelectionChangedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</TreeView>

Interaction.TriggersからEventToReactiveCommandまでは定型文なのでこのまま覚えましょう。
EventNameだけは購読したいイベント名を書いてください。
CommandのBindingも至って普通です。
これでCommandのparameterとして、EventArgsを取得できます。

xmlnsについて

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

2010/Interactivityのnamespaceで解説されている記事が多かったですが、自分の環境(Prism&Rx)だと下記のエラーが出て動作しませんでした。

「型”EventToReactiveCommand”の値は、型”TriggerActionCollection”のコレクションまたは辞書に追加できません。」
「指定された値をコレクションに割り当てることができません。次の型の値を指定してください: “TriggerAction”」

試しに下記に置き換えてみたところ、無事に動作しました。

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

EventArgsをそのまま渡すのに抵抗がある場合

ReactiveConverterを使って値を成形できます。

Converter

public class TreeViewItemConverter : ReactiveConverter<RoutedPropertyChangedEventArgs<object>, FolderTreeViewItem>
{
    protected override IObservable<FolderTreeViewItem> OnConvert(IObservable<RoutedPropertyChangedEventArgs<object>> source)
        => source.Select(x => (FolderTreeViewItem)x.NewValue);
}

ReactiveConverterを継承したConverterを作成し、OnConvertをoverrideします。
上記の例はEventArgsが長くて見にくいですが、

IObservable<T> OnConvert(EventArgs e)

のメソッドを定義してあげるだけです。

XAML

<i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectedItemChanged">
        <interactivity:EventToReactiveCommand Command="{Binding SelectionChangedCommand}">
            <vm:TreeViewItemConverter />
        </interactivity:EventToReactiveCommand>
    </i:EventTrigger>
</i:Interaction.Triggers>

XamlはEventToReactiveCommandの間にConverterを挟んだだけです。
これでCommandのparameterに変換後の値が入ります。

コメント