こちらを見て、そういえばさらっと書いてるだけだったなぁと思ったので…。
イベント引数を ViewModel で使いたい
マウス系イベントや選択系イベントは、イベント引数にしか入ってない値とかもあったりして使いたくなりますよね。 まぁ、イベントハンドラーを普通に書いて、そこからコマンド呼んでも大したことはないんですが、一応ライブラリーによっては、仕組みが用意されていたりします。
ReactiveProperty の場合
UWP と WPF 向けに用意しています。 EventToReactiveCommand と EventToReactiveProperty になります。EventToReactiveCommand がトリガーの引数を変換処理も挟みつつ ReactiveCommand の Execute メソッドの引数に渡します。EventToReactiveProperty がトリガーの引数を変換処理も挟みつつ ReactiveProeprty の Value に設定します。
これは、基本的に EventTrigger の下に置く Action として想定しています。別に他のトリガーでも動きますが。 何もしないと設定した ReactiveCommand と ReactiveProperty にトリガーに渡された引数をそのまま渡します。 ただ、引数がそのままわたるのは、ちょっとなぁ…という場合には引数を ViewModel のレイヤーのオブジェクトに変換するためのコンバーターが用意されています。
例として、マウスを動かすと、その座標を画面に表示するものを作ってみようと思います。
ViewModel の作成
とりあえず、ViewModel 側でマウス座標を表す MousePosition というクラスを作ります。
namespace ReactivePropertySample { publicclass MousePosition { publicdouble X { get; set; } publicdouble Y { get; set; } } }
そして MainWindow 用の ViewModel を作ります。
こいつは ReactiveCommand<MousePosition>
と、コマンドで受け取ったものを文字列に変換して ReadOnlyReactivePropertySlim
にしています。
using Reactive.Bindings; using System.ComponentModel; using System.Linq; using System.Reactive.Linq; namespace ReactivePropertySample { publicclass MainWindowViewModel : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; public ReactiveCommand<MousePosition> MouseMoveCommand { get; } public ReadOnlyReactivePropertySlim<string> Message { get; } public MainWindowViewModel() { MouseMoveCommand = new ReactiveCommand<MousePosition>(); Message = MouseMoveCommand.Select(x => $"({x.X}, {x.Y})") .ToReadOnlyReactivePropertySlim(); } } }
では、この ViewModel と View をつないでいきます。まず MouseMove イベントの引数の MouseEventArgs から MousePosition への変換処理を書きます。これは ReactiveConverter<変換元型名, 変換先型名>
クラスを継承して OnConvert
メソッドをオーバーライドして書きます。
こんな感じ。
using Reactive.Bindings.Interactivity; using System; using System.Linq; using System.Reactive.Linq; using System.Windows; using System.Windows.Input; namespace ReactivePropertySample { publicclass MouseMoveToMousePositionConverter : ReactiveConverter<MouseEventArgs, MousePosition> { protectedoverride IObservable<MousePosition> OnConvert(IObservable<MouseEventArgs> source) => source .Select(x => x.GetPosition((IInputElement)AssociateObject)) .Select(x => new MousePosition { X = x.X, Y = x.Y, }); } }
あとは、View で EventTrigger と EventToReactiveCommand と 先ほど作成した MouseMoveToMousePositionConverter を使ってイベントと ReactiveCommand をつなぎます。
<Windowx:Class="ReactivePropertySample.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.NET46"xmlns:local="clr-namespace:ReactivePropertySample"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"Title="MainWindow"Width="800"Height="450"mc:Ignorable="d"><i:Interaction.Triggers><i:EventTrigger EventName="MouseMove"><interactivity:EventToReactiveCommand Command="{Binding MouseMoveCommand}"><local:MouseMoveToMousePositionConverter /></interactivity:EventToReactiveCommand></i:EventTrigger></i:Interaction.Triggers><Window.DataContext><local:MainWindowViewModel /></Window.DataContext><Grid><TextBlockHorizontalAlignment="Center"VerticalAlignment="Center"Text="{Binding Message.Value}" /></Grid></Window>
実行してマウスを動かすと座標が画面に出ます。
Prism の場合
Prism の場合は WPF と Xamarin.Forms 用で似たような機能が提供されています。大体同じ使い方(WPF は Trigger の Action として、Xamarin.Forms は Behavior として提供されています)なので今回は WPF の使い方を試してみます。
Prism のクラスとして InvokeCommandAction があります。 これは Command プロパティに指定したコマンドを呼び出すアクションです。
この時コマンドの引数に渡すものを指定する方法として CommandParameter プロパティに指定する方法と、TriggerParameterPath に指定する方法の 2 通りがあります。CommandParameter は普通に Binding とかを指定できます。TriggerParameterPath は、トリガーのパラメーター(EventTrigger の場合はイベント引数)から、任意のプロパティを受け渡します。
今回は、イベント引数の GetPosition メソッドを呼んだ結果を受け渡したいので…無理じゃん!
どうしよう
逃げ道としては、イベント引数をそのまま受け取る方法ですが、今回は GetPosition を呼びたい。引数は View のクラス…辛い。おとなしくコードビハインドですね。何も問題ない。
でも、こういうケースがアプリ内でたくさんあるなら部品化する価値はある。ということで部品化するとしたら、Prism の InvokeCommandAction を参考にして間に任意の変換処理を挟むための IHogeHogeConverter Converter { get; set; }
プロパティでも追加してあげる感じでしょうか…?
いっそのことプルリクエストしてもいいかもしれませんね。
Livet の場合
こっちも特にイベント引数を加工するような仕組みはないように見えるので、皆どうしてるんだろう?機能欲しい?
まとめ
意外と ReactiveProperty のやつが何でもやりたい放題だった。