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 と実行してください。



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 は上記のようになります。



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 を実装していないとメモリリークを起こすそうです。そこで形だけ実装します。



using FormsMvvm;
using System.Windows.Forms;

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

        public Form1()
            label1.Bind(() => ViewModel.Counter.Value);

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

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