Help us understand the problem. What is going on with this article?

Windows Forms で MVVM 2 (ReactiveProperty 編)

More than 3 years have passed since last update.

前書き

前回の Windows Forms で MVVM では PropertySetter, Command, Binder の三つのヘルパークラスを作って Windows Forms で MVVM を実装する方法をお伝えしました。
PropertySetterINotifyPropertyChanged を簡単に実装するためのクラス、Command は UI からのアクションを実装するクラス、Binder はコントロールとデータを簡単にバインドするためのクラスでした。

しかし、ReactiveProperty を使用すればもっとシンプルにできます。
ReactiveProperty を使うとプロパティ自体が IObservable<T> を実装するので、ViewModelINotifyPropertyChanged を実装する必要がありません。また ReactiveCommand が実装されているので、Command を作る必要がありません。
したがって、三つのヘルパークラスのうち、Binder を除いて二つが不要になるということです。

ReactiveProperty のインストール

NuGet パッケージマネージャーを開いて、Install-Package ReactiveProperty と実行してください。

Binder

Binder.cs

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Forms;
using Reactive.Bindings;

namespace FormsMvvm
{
    public static class Binder
    {
        public static void Bind<T, U>(Expression<Func<T>> item1, Expression<Func<U>> item2)
        {
            Tuple<object, string> ResolveLambda<V>(Expression<Func<V>> expression)
            {
                var lambda = expression as LambdaExpression;
                if (lambda == null) throw new ArgumentException();
                var property = lambda.Body as MemberExpression;
                if (property == null) throw new ArgumentException();
                var members = new List<MemberInfo>();
                var parent = property.Expression;
                return new Tuple<object, string>(Expression.Lambda(parent).Compile().DynamicInvoke(), property.Member.Name);
            }
            var tuple1 = ResolveLambda(item1);
            var tuple2 = ResolveLambda(item2);
            var control = tuple1.Item1 as Control;
            if (control == null) throw new ArgumentException();
            control.DataBindings.Add(new Binding(tuple1.Item2, tuple2.Item1, tuple2.Item2));
        }

        public static void Bind<T>(this Label label, Expression<Func<T>> expression)
        {
            Bind(() => label.Text, expression);
        }

        public static void Bind(this Button button, ReactiveCommand command)
        {
            command.CanExecuteChanged += (sender, args) => button.Enabled = command.CanExecute();
            button.Enabled = command.CanExecute();
            button.Click += (sender, args) => command.Execute();
        }
    }
}

Binder.cs は上記のようになります。
具体的にはボタンに対するバインド部分が変更されています。

ViewModel

ViewModel.cs

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

namespace WindowsFormsApp1
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ReactiveProperty<int> Counter { get; } = new ReactiveProperty<int>();

        public ReactiveCommand UpCommand { get; private set; }
        public ReactiveCommand DownCommand { get; private set; }

        public ViewModel()
        {
            UpCommand = Counter.Select(_ => Counter.Value < 10).ToReactiveCommand();
            UpCommand.Subscribe(() => Counter.Value++);
            DownCommand = Counter.Select(_ => Counter.Value > 0).ToReactiveCommand();
            DownCommand.Subscribe(() => Counter.Value--);
        }
    }
}

ReactiveProperty全然分からねぇ!って人向けのFAQ集【修正済】 を読んで初めて知りましたが、ViewModelINotifyPropertyChanged を実装していないとメモリリークを起こすそうです。そこで形だけ実装します。

Form1

Form1.cs

using FormsMvvm;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        protected ViewModel ViewModel { get; private set; } = new ViewModel();

        public Form1()
        {
            InitializeComponent();
            label1.Bind(() => ViewModel.Counter.Value);
            button1.Bind(ViewModel.UpCommand);
            button2.Bind(ViewModel.DownCommand);
        }
    }
}

Form1 はほとんど変更ありませんが、ViewModel.CounterViewModel.Counter.Value になりました。

Zuishin
視聴率重視のいい加減なバラエティー報道番組に文句が言える人がここに何人いるだろうか?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away