WPF
ReactiveExtensions
ReactiveProperty

RxとReactivePropertyを使って、WPFでUnidirection Data Flowを実現する

More than 3 years have passed since last update.

要約

ブラウザ上の MVC的な分割を Node Stream API で行うの WPF+Rx+ReactiveProperty 版です

背景

WPFで、真面目にコードビハインドとViewModelを分離して、MVVMを実現するには、INotifyPropertyChangedとICommandの実装が面倒です。
また、個人的に双方向バインディングより、Unidirectional Data Flowが好きです。

作戦

幸い、技術的要素は揃っています。

  • イベントをRxで処理
  • ViewModelからViewへの変更通知はReactivePropertyで実現
  • ViewはXamlで実現

次のような処理の流れを考えます。

  1. Rxでイベントを受け取る
  2. Rxストリーム中でイベントを処理
  3. 結果をReactivePropertyに反映
  4. Viewを更新

作るアプリケーション

ボタンをクリックしたらカウンターの数値を加算します。

out.gif

実装

View

TextBlockにカウンターの値をバインドします。

<Window x:Class="WpfApplication1.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button x:Name="button" Content="Button"  />
        <TextBlock x:Name="textBlock" Text="{Binding Count.Value}"/>
    </StackPanel>
</Window>

コードビハインド

using Reactive.Bindings;
using System;
using System.Reactive.Linq;
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // DataContextにViewModelをバインド
            var vm = new ViewModel();
            DataContext = vm;

            // イベントに処理をバインド
            Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(
                    h => (s, e) => h(e),
                    h => button.Click += h,
                    h => button.Click -= h
                )
                .Subscribe(_ =>
                {
                    // ViewModelを更新
                    vm.Count.Value++;
                });
        }
    }

    public class ViewModel
    {
        public ReactiveProperty<int> Count { get; } = new ReactiveProperty<int>();
    }
}
  • DataContextにReactivePropertyを使ったViewModelを設定して、変更通知をバインド
  • RxのObservable.FromEventを使って、ボタンクリックイベントに処理をバインド

感想

ModelとContorllerは分離されていません。
ですが、Subscribe内の処理をServiceクラスに分離すればなんとでもなりそうです。

JavaScriptより簡単に実装できました、WPF(とRxとReactiveProperty)すごいです。
良さげ:sushi:

参考