WPFアプリを楽に作りたい（XAML手書きは嫌！）

# 誰向け？
WPFを手書きでバインドして実行すると上手くバインドされてなくて誤字がないか1文字ずつ見つめるのに疲れた人向け

# あるある
WPFアプリを作ろう！
→データ用意してコントロールを配置してプロパティを設定して値をバインドして・・・よしできた実行だ！
→コントロールの見た目が想定と違う、バインドしたはずなのに値が表示されない、Xaml内の不具合はエラーも出ない・・・
→／(^o^)＼

# やりたいこと
Xamlを手で書くのは大変なうえにデバッグも辛い
なのでBlend for VisualStudioのGUIでレイアウトもバインドも両方やろう
（ViewとViewModelのバインドが今回のメイン）

# まずは下準備
・「VisualStudio2019」インストール
・Nugetで「ReactiveProperty」をプロジェクトに追加
・「WPFアプリ」プロジェクトを新規作成

## ViewModelとModelの準備
まずはViewModelとModelを用意

``` RXTestModel.cs
using System.ComponentModel;

namespace RXTest
{
    class RXTestModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private int _number;

        public int Number
        {
            get { return _number; }
            set
            {
                if (_number != value)
                {
                    _number = value;
                    OnPropertyChanged(nameof(Number));
                }
            }
        }
    }
}
```

``` MainWindowViewModel.cs
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.ComponentModel;
using System.Reactive.Disposables;

namespace RXTest
{
    class MainWindowViewModel : INotifyPropertyChanged, IDisposable
    {
        // メモリリーク対策。使用不可。
        public event PropertyChangedEventHandler PropertyChanged;

        private CompositeDisposable Disposable { get; } = new CompositeDisposable();

        public ReactiveProperty<int> Number { get; set; }
        public ReactiveCommand VMPlus1 { get; set; } = new ReactiveCommand();
        public ReactiveCommand MPlus10 { get; set; } = new ReactiveCommand();
        public ReactiveCommand SetNumber { get; set; } = new ReactiveCommand();

        RXTestModel model = new RXTestModel();

        public MainWindowViewModel()
        {
            Number = model.ToReactivePropertyAsSynchronized(m => m.Number).AddTo(Disposable);
            VMPlus1.Subscribe(_ => Number.Value += 1).AddTo(Disposable);
            MPlus10.Subscribe(_ => model.Number += 10).AddTo(Disposable);
            SetNumber.Subscribe(x => Number.Value = (int)x).AddTo(Disposable);
        }

        public void Dispose()
        {
            Disposable.Dispose();
        }
    }
}
```

Modelは変更通知付きの(int)Numberプロパティのみ
ViewModelは(int)NumberプロパティをModelと双方向バインドして、バインドが正常か確認用のコマンドを3つ用意
あとはXaml側でViewModelのNumberと双方向バインドを実装すれば、画面の変更がModelに反映されるし、Modelの変更が画面に反映されるはず

# 本題
編集するXamlを右クリックしてBlendでデザイン開始
![2020-04-11_11h41_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/8d6932a4-5944-6784-18a9-b7894f699d42.png)

## コントロール配置
まずは必要なコントロールを配置
アセットウィンドウのコントロール一覧の中に標準コントロールがある
ドラッグアンドドロップで画面の大体の位置に配置
![2020-04-11_11h50_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/33a1afb8-dae6-57b4-a90d-b2ef89e6adbe.png)

コントロールを4つ配置
以下の目的で使用する予定（まだ未実装）
ラベル：ViewModelのNumberとバインドして、値を表示
ボタン１：コマンドとバインドして、ViewModelのNumberの値を1増やす
ボタン２：コマンドとバインドして、ModelのNumberの値を10増やす
テキストボックス：コマンドとバインドして、MouseEnterイベント時にViewModelのNumberの値を入力された値に更新する

テキストボックスの仕様が意味不明なのは以下の練習のため
・クリック以外のイベントにコマンドをバインド
・コマンドパラメーターの値をユーザ入力で変更

## レイアウト調整
今回の主目的の1つ目
Xamlファイルのテキストを直接変更すると、慣れないうちは思わぬエラーで喘ぎ苦しむことになるので、プロパティウィンドウから変更する

![2020-04-11_12h03_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/6a9ea5a6-8c1d-51ce-77cb-44d55f5dddfe.png)
大体のサイズはマウスで調整して・・・

![2020-04-11_12h06_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/a6d26031-4c56-0d24-153f-d286fea84090.png)
各コントロールを選んでプロパティウィンドウからコントロールサイズを細かく調節して・・・

![2020-04-11_12h13_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/1ffab19b-6b5e-0a9f-ad16-c5e1df32b0b5.png)
同じくプロパティウィンドウから文字サイズや色を変更

**見た目は完成！**

## 恐怖のバインド
あとは各項目をバインドするだけ

``` MainWindow.xaml.cs
using RXTest;
using System.Windows;

namespace RxTest2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }
}
```
まずはコードビハインドでWindowのDataContextを設定

~~**ここまではいつも上手くいくんだよ！**~~

まずは一番簡単そうなところから
ラベルのContextをViewModelのNumberにバインドします
![2020-04-11_12h27_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/47d3a60c-ac78-76a5-e998-75c06567918d.png)
ラベルのプロパティウィンドウからContextの右にある四角をクリックして、「データバインディングの作成」を選択
バインディング作成画面が出るので、バインドの種類を「データコンテキスト」にすると、ViewModelのNumber変数が・・・
![2020-04-11_12h29_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/7d93862b-4bcb-9041-2a5a-f5e76f9aa557.png)
**ありません**

コンストラクタでDataContextを設定しているので、プログラムを「実行」したときはちゃんとDataContextが設定されて動作しますが、「編集画面」でどのViewModelをDataContextを使うのかを別に設定しないとだめです

なので一度バインディング画面は閉じ、先に表示された通りの手順でDataContext設定を行いましょう
まずは「オブジェクト」メニューを開きます
![2020-04-11_12h33_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/72737c0a-c4ef-0576-0939-4661f3771a20.png)
**そんなメニューないんだが？**
## デザイン時のDataContextを設定
過去何度も同じ挑戦をしてはここでつまずいて諦めていましたが、ついに設定の仕方がわかりました
メニューを開こうとする前に、まず**DataContextを追加するコントロールを選択**します
今回はWindowコントロールを選択
![2020-04-11_12h39_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/978b389f-614a-da2e-c18b-94d6739a5e54.png)
するとヘッダーメニューの項目が変わります！
Label選択時
![2020-04-11_12h40_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/ea35bba0-611f-44b1-2408-d4da935c2f65.png)
Window選択時
![2020-04-11_12h41_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/b2de8d99-5940-06ef-b02e-7ac2e88dd935.png)

Window選択時に追加で表示される「形式」を開くと、目的の「デザイン時のDataContextを設定」が選択できます
![2020-04-11_12h42_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/cad943df-d966-1613-91d0-e4ba1b8c4e1e.png)

デザイン時のDataContextを設定ウィンドウを開き、DataContextの種類を「DesignInstance」にします
すると、一覧に標準パッケージやNugetで追加したパッケージ等が表示され、その中に自分で作成したViewModel.csファイルが

**ありません！**

コードを書いて保存しただけでは、ファイルや変数にBlendからアクセスできません
一度ビルドを完了させる必要があります！

エラーがある場合はすべて解消し、ビルドを正常に完了させてから改めてDataContextの設定画面を開くと、
![2020-04-11_12h50_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/a7966f58-b306-9357-c2e1-32e11ca633ce.png)
やっと念願のMainWindowViewModelが見つかりました

## 今度こそバインド
山場は越えたのであとはバインドするだけです
少し前にやりかけた手順でラベルのContextのバインディングウィンドウを開きます
![2020-04-11_12h55_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/f24db37e-3cd2-9f38-ea52-4ab876db8d22.png)

するとちゃんとプロパティが表示されているので、NumberのValueを選んでOK
※Number自身じゃなくNumber.Valueをバインドしているのは、ReactivePropertyだからです

![2020-04-11_12h56_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/309fa255-5ae7-3803-a983-19312d2b5a22.png)
ラベルのContextに{Binding Number.Value}と入力されました！

~~こんなに手間かけてこれだけ？~~

### ボタンのコマンドにバインド
下準備はすでにできているので、ラベルの時と同様の手順でバインドできます
![2020-04-11_13h01_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/7e27033f-d979-91e3-46d3-98cc1186462e.png)
対象のボタンを選択→プロパティウィンドウのCommandのバインドメニューを開く→バインドの種類をデータコンテキストに→目的のコマンドプロパティを選択→OKボタン押下
![2020-04-11_13h03_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/b5a6437c-17f3-178c-eccd-b2f9407084ce.png)

2度目は楽
Modelで10増えるボタンも同様

### (オプション)テキストボックスのバインド
ボタンだけじゃなくテキストボックスにもコマンドをバインドしたい
マウスクリックだけじゃなく好きなイベント時の処理を書きたい
コマンドパラメータも渡したい
でもそもそもテキストボックスにはコマンドプロパティがない
どうすれば？

#### EventTrigerでコマンドバインド
全体の流れとしては、
１．テキストボックスにEventTrigerを追加して、好きなイベント（今回はマウスエンター）をフック
２．イベントフック時にコマンドを呼び出し
３．コマンドパラメーターを渡す

##### マウスエンターイベントをトリガーでフック
![2020-04-11_13h40_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/d944e86d-d7ea-b962-4cbb-c4936dbb82f8.png)
テキストボックスを選択し、画面左上のアセットウィンドウでビヘイビア一覧を表示
「EventToReactiveCommand」を選択して追加
![2020-04-11_13h46_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/927bfbe3-f667-6db9-3d4d-41748561640e.png)
追加したEventToReactiveCommandを選択し、プロパティウィンドウのEventNameを「MouseOver」に変更
※ここは直接プロパティウィンドウに文字を入力するしかなさそうなので、公式リファレンスからイベント名をコピペ推奨

##### イベントにコマンドを登録
![2020-04-11_13h49_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260302/1e54efde-df83-632e-7636-292b471584d3.png)

同プロパティウィンドウのCommandの右の四角からバインドウィンドウを開き、コマンドをバインド

##### コマンドパラメーターのバインド　ができなかった
調べた限り、EventToReactiveCommandのCommandのパラメーターは対応するEventArgsクラスで固定の模様
なのでXamlはこれで完成で、ViewModel側をEventArgsパラメーターを受け取って処理できるように修正します

``` MainWindowViewModel.cs
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System;
using System.ComponentModel;
using System.Reactive.Disposables;
using System.Windows.Controls;
using System.Windows.Input;

namespace RXTest
{
    class MainWindowViewModel : INotifyPropertyChanged, IDisposable
    {
        // メモリリーク対策。使用不可。
        public event PropertyChangedEventHandler PropertyChanged;

        private CompositeDisposable Disposable { get; } = new CompositeDisposable();

        public ReactiveProperty<int> Number { get; set; }
        public ReactiveCommand VMPlus1 { get; set; } = new ReactiveCommand();
        public ReactiveCommand MPlus10 { get; set; } = new ReactiveCommand();
        //public ReactiveCommand SetNumber { get; set; } = new ReactiveCommand();
        public ReactiveCommand<MouseEventArgs> SetNumber { get; set; } = new ReactiveCommand<MouseEventArgs>();

        RXTestModel model = new RXTestModel();

        public MainWindowViewModel()
        {
            Number = model.ToReactivePropertyAsSynchronized(m => m.Number).AddTo(Disposable);
            VMPlus1.Subscribe(_ => Number.Value += 1).AddTo(Disposable);
            MPlus10.Subscribe(_ => model.Number += 10).AddTo(Disposable);
            //SetNumber.Subscribe(x => Number.Value = (int)x).AddTo(Disposable);
            SetNumber.Subscribe(x => Number.Value = なんでも屋(x)).AddTo(Disposable);
        }

        private int なんでも屋(MouseEventArgs e)
        {
            string inputText = ((TextBox)e.Source).Text;
            return int.TryParse(inputText, out int result) ? result : -1;
        }

        public void Dispose()
        {
            Disposable.Dispose();
        }
    }
}
```

SetNumberプロパティ宣言時にMouseEventArgsを引数として設定
MouseEventArgsを数値に加工してくれる「なんでも屋」さんを作成
Subscribeで登録する処理を変更

## 完成
実行した結果がこちら
https://youtu.be/VT7C4x9zbp0
ボタンを押したら値が変わるし、テキストボックスに重ねたタイミングで値が変わります
NumberおよびCommandのバインドは正常にできました

ちょっとしたバインド程度なら直接文字を入力したほうが早いですが、誤字等を気にしなくていいのは楽だな～と思いました　まる