2024年10月13日日曜日

WPFでReactivePropertyを使う際のお作法

今回はWPFでReactivePropertyを使う際のお作法を備忘録的に紹介していこうと思います。

ReactivePropertyとはReactive Extensions (Rx)ベースで作られたプロパティを提供するライブラリです。INotifyPropertyChangedを実装しているためViewModelにそのまま使用することができ、また、LINQを使って値の伝搬を表現することができるため、Statefulなアプリを簡単に実装することができる、非常に強力なライブラリです。

1. 導入

Nugetからダウンロードできます。

ReactiveProperty.WPFは入れなくても一応使えないことは無いのですが、あとでハマることになりますので、WPFアプリなら思考停止でとりあえず入れてしまいましょう。

2. UIスレッドへの転送設定

WPFには単一のスレッドからしかUI要素にアクセスできないという仕様があります。ですので、ViewModelにてイベントをそのスレッドに転送してやらねばならないのですが、ReactivePropertyには便利なことに特定のスレッドでイベントを発生させる機能があります。

さて、転送するにあたって、当然ながらどのスレッドがUIスレッドかということをライブラリに教え込まなければなりません。それは、App.xaml/App.xaml.csに以下のコードを追加することで行います。

<Application x:Class="WpfTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfTest"
             StartupUri="MainWindow.xaml"
             Startup="Application_Startup">
    
    <Application.Resources>
    </Application.Resources>
</Application>
public partial class App : Application
{        
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        ReactivePropertyScheduler.SetDefault(new ReactivePropertyWpfScheduler(Dispatcher));
    }
}

アプリのスタートアップ時に、Application.DispatcherをReactivePropertySchedulerとして登録するという作業になります。ReactivePropertyWpfSchedulerはReactiveProperty.WPFに入っているクラスで、WPF向けのスケジューラーです。

3. ReactivePropertyとReactivePropertySlim

さて、これで準備が整いましたので、実際に使用していきます。

ReactiveProperty(ライブラリ)には、ReactiveProperty(クラス)とReactivePropertySlim(クラス)があります。ReactivePropertyは色々な機能を持っているが故にパフォーマンスが悪めなのですが、ReactivePropertySlimは機能を絞ってパフォーマンスをかなり改善しています。そのReactivePropertySlimで削られた機能として、主に以下の2つがあります。

  • UIスレッドへの自動転送機能
  • 入力値のバリデーション機能

いずれも、ViewModelとしては必要な機能です。ですので、基本的にViewModelではReactivePropertyModelではReactivePropertySlimを使用すると良いでしょう。

public class MainWindowViewModel : BindableBase
{
    readonly IMainWindowModel model;
    public MainWindowViewModel(IMainWindowModel model)
    {
        this.model = model;

        Text = model.Text.ToReactivePropertyAsSynchronized(p => p.Value);
    }

    public ReactiveProperty<string> Text { get; }
}
public interface IMainWindowModel
{
    IReactiveProperty<string> Text { get; }
}
public class MainWindowModel : IMainWindowModel
{
    public MainWindowModel()
    {
        Text = new ReactivePropertySlim<string>();
        Text.Subscribe(p => System.Diagnostics.Debug.WriteLine(p));
    }

    public ReactivePropertySlim<string> Text { get; }
    IReactiveProperty<string> IMainWindowModel.Text => Text;
}

このコードでは、Prism前提でModelとViewModelを疎結合にするため、間にIMainWindowModelを挟んでいます。そこまでの抽象化が必要ない場合は適宜コードを読み替えてください。

ReactivePropertyとReactivePropertySlimを双方向に同期させるためにはToReactivePropertyAsSynchronized()を使います。片方向(Model→ViewModel)の場合はToReadOnlyReactiveProperty()で良いでしょう。

--------------

以上です。その他の細かい使い方は公式ドキュメントを参照ください。

0 件のコメント:

コメントを投稿