【改訂版】PrismとReactivePropertyで簡単MVVM！

# はじめに

本記事は、3年前に書いた以下の記事を大幅に見直して書き改めたものです。

[PrismとReactivePropertyで簡単MVVM！](https://qiita.com/hiki_neet_p/items/e381c687b0644c0e4978)

上記の記事は、私の古い認識のもとに書き上げられました。
私の当時の認識で、`Prism`の機能のごく一部を使って`WPF`アプリケーションを構成することが「簡単」だと思っていましたが、時が過ぎ、その認識が変化しました。
即ち、`Prism`の機能にもう少し乗っかった方が、結果的に「簡単」だと思い直したのです。
また、当時の未熟さから、誤った考えのもとに書かれた部分も見受けられます。
古い記事に「いいね」をいただく度、そのことをお伝えし直したいという気持ちになっていましたが、この度、執筆するやる気を確保できましたので、本記事を投稿するに至りました。

`Prism`の機能は多彩ですが、本記事では覚えておくと開発が簡単になる、効果の高い部分のみをピックアップしてお伝えできればなと思います。

## `.NET Core 3.0`

今まで、`WPF`は`.NET Framework`固有のものでした。
しかし、2019年秋にリリースされるであろう`.NET Core 3.0`には、新たに`WPF`の機能が乗せられており、`.NET Framework`と同じように`WPF`アプリケーションを作成できるようになります。

世間では、`Javascript`を中心に、新たなGUIフレームワークが台頭しつつありますが、まだ`WPF`の人口も残っているのではないでしょうか。

本記事の執筆時点では正式リリースがまだですが、以下では`.NET Core 3.0-preview`を使用し、簡単な`WPF`のサンプルアプリの作成を通して、`Prism`と`ReactiveProperty`の使用方法をご紹介します。

# 本記事の開発環境

- Windows 10 Home
- dotnetcore-sdk 3.0.100-preview5-011568
- Prism.Unity 7.2.0.1233-pre
- ReactiveProperty 5.5.1
- Visual Studio Code

# GitHub

https://github.com/tana-gh/PrismSample

# 各ライブラリについて

## `Prism`について

`Nuget`を見ると、`Prism`関連のパッケージがたくさんあります。
ここで、各パッケージの意味を見てみましょう。

|パッケージ名|説明|
|---|---|
|Prism.Core|前提パッケージ|
|||
|Prism.Wpf  |WPF用パッケージ    |
|Prism.Forms|Xamarin用パッケージ|
|||
|Prism.Autofac     |WPF用（Autofac使用）     |
|Prism.DryIoc      |WPF用（DryIoc使用）      |
|Prism.Mef         |WPF用（Mef使用）         |
|Prism.Ninject     |WPF用（Ninject使用）     |
|Prism.StructureMap|WPF用（StructureMap使用）|
|Prism.Unity       |WPF用（Unity使用）       |
|||
|Prism.Autofac.Forms|Xamarin用（Autofac使用）|
|Prism.DryIoc.Forms |Xamarin用（DryIoc使用） |
|Prism.Unity.Forms  |Xamarin用（Unity使用）  |

`Prism.Core`は前提パッケージで、プラットフォーム共通の機能が入っています。
`Prism.Wpf`と`Prism.Forms`は、それぞれ`WPF`用と`Xamarin`用です。
その下の6つのパッケージは`WPF`用で、DIコンテナとしてそれぞれの外部パッケージを使用するものです。
最後の3つは`Xamarin`用で、DIコンテナとしてそれぞれの外部パッケージを使用します。

`WPF`アプリを作るうえでは、`WPF`用でコンテナありの6つの中から選択することになります。
今回は、`.NET Core`対応ありで公式サンプルにも使用されている、`Prism.Unity`を使用します。

`Prism.Unity`は`Prism.Wpf`に依存し、`Prism.Wpf`は`Prism.Core`に依存するので、`Prism.Unity`だけインストールすれば大丈夫です。

## `Unity`について

`Unity`はDI（依存性の注入）ライブラリです。
`Unity`という同名のゲームエンジンが存在しますが、全く関係はありません。
DIライブラリは沢山ありますが、`C#`においてはこの`Unity`が有名どころです。

`Prism.Unity`は`Unity`に依存するので、`Prism.Unity`だけインストールするようにしましょう。

## `Prism-Samples-Wpf`

`Prism.Unity`を使用した、公式サンプルです。

[Prism-Samples-Wpf](https://github.com/PrismLibrary/Prism-Samples-Wpf)

29種類のサンプルがあり、各サンプルは非常に小さなプロジェクトから構成されているので、簡単に機能の把握ができます。

正直、このサンプルを見れば本記事で説明することはあまりありませんが、本記事では、重要な機能をかいつまんでご紹介するようにします。

## `Prism`の機能概観

|機能|名前空間|
|---|---|
|DIコンテナの補助|Prism.Ioc, Prism.Unity|
|Region         |Prism.Regions         |
|Navigation     |Prism.Navigation      |
|EventAggregator|Prism.Events          |
|Dialog         |Prism.Services.Dialogs|
|MVVMの補助      |Prism.Mvvm            |
|Module         |Prism.Modularity      |

- DIコンテナの補助
    - 外部のDIライブラリを使用し、自動的にクラスをコンテナに登録したり、コンストラクタインジェクション、セッターインジェクションを行ないます。

- Region
    - 名前をつけたViewの領域に、別のViewを追加するなどの管理ができます。

- Navigation
    - Regionのヒストリ機能や、ライフサイクル管理ができます。
    - 本記事では扱いません。

- EventAggregator
    - ViewModelやViewのイベントをPubSubパターンで統合管理します。

- Dialog
    - ダイアログの作成と呼び出しを行ないます。
    - `Prism 7.2`からの機能です。
    - 本記事では扱いません。

- MVVMの補助
    - `INotifyPropertyChanged`の実装補助、コマンドの実装補助、バリデーションの実装補助を行ないます。
    - 本記事では扱いません（`BindableBase`クラスが登場するのみ）。

- Module
    - 別のアセンブリにあるクラスを`Prism`アプリケーションに含めるために使用します。
    - 本記事では扱いません。

## `ReactiveProperty`について

前章の「MVVMの補助」の部分は、`Prism`よりも`ReactiveProperty`に任せるほうが簡単だという認識です。

Reactiveとは、Observerパターンによる、時系列データ（イベントなど）の監視、およびそのデータ群に対する演算（LINQのような）を扱うものです。
`System.Reactive`という外部ライブラリに依存しています。

GUIアプリはイベントに対処しなくてはならないので、このReactiveの考え方は有効です。
`ReactiveProperty`は、主にMVVMにおけるViewModelのプロパティとコマンドを、Reactiveに扱うことを可能とします。

# サンプルプロジェクトの概要

これから作るアプリの画面を以下に示します。
数値を入力すると、2乗を計算するアプリです。

![complete-window.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/b1bf9815-c90a-8389-ff28-e9d70e6c524c.jpeg)

入力は即座に反映されます。

![complete-window2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/29daeb66-c58e-8dd3-3ec9-cecd63a0c9b9.jpeg)

入力のバリデーションも行います。

![invalid-window.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/f7ecdd27-efc7-a5f9-7e7b-42a706d125ba.jpeg)

ボタンを押すと、ダイアログが表示されます。

![dialog.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/6e539b09-c342-a521-9584-be9e9e28c92c.jpeg)

![dialog2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/7f4dfaf5-6317-7b4b-8cb0-d15c8fec0071.jpeg)

# プロジェクトの構造

プロジェクト群を以下のように作成しました。
プロジェクトを小分けにしない方が作るのは簡単ですが、今回は分けて作成しました。

```powershell:PowerShell
# ディレクトリの作成
> mkdir PrismSample
> cd PrismSample

# プロジェクト群の作成
> dotnet new sln
> dotnet new wpf -o PrismSample.App.Main
> dotnet new classlib -o PrismSample.Lib.Views
> dotnet new classlib -o PrismSample.Lib.ViewModels
> dotnet new classlib -o PrismSample.Lib.Models

# ソリューションへの追加
> dotnet sln add .\PrismSample.App.Main\ .\PrismSample.Lib.Views\ .\PrismSample.Lib.ViewModels\ .\PrismSample.Lib.Models\

# プロジェクト参照の追加
> dotnet add .\PrismSample.App.Main\ reference .\PrismSample.Lib.Views\
> dotnet add .\PrismSample.App.Main\ reference .\PrismSample.Lib.ViewModels\
> dotnet add .\PrismSample.Lib.ViewModels\ reference .\PrismSample.Lib.Views\
> dotnet add .\PrismSample.Lib.ViewModels\ reference .\PrismSample.Lib.Models\

# パッケージの追加
> dotnet add .\PrismSample.App.Main\ package Prism.Unity -v 7.2.0.1233-pre
> dotnet add .\PrismSample.Lib.Views\ package Prism.Unity -v 7.2.0.1233-pre
> dotnet add .\PrismSample.Lib.ViewModels\ package Prism.Unity -v 7.2.0.1233-pre
> dotnet add .\PrismSample.Lib.ViewModels\ package ReactiveProperty
```

![initial-projects.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/607ab73e-294a-7e18-d267-7f85cfa2ed93.jpeg)

## `.csproj`の修正

`.NET Core 3.0`に対応させるため、下記のように修正しました。

```xml:PrismSample.Lib.Views.csproj
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> <!--修正-->

  <!--修正-->
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Prism.Unity" Version="7.2.0.1233-pre" />
  </ItemGroup>

</Project>
```

```xml:PrismSample.Lib.ViewModels.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <!--修正-->
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Prism.Unity" Version="7.2.0.1233-pre" />
    <PackageReference Include="ReactiveProperty" Version="5.5.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\PrismSample.Lib.Models\PrismSample.Lib.Models.csproj" />
  </ItemGroup>

</Project>
```

```xml:PrismSample.Lib.Models.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <!--修正-->
  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>

</Project>
```

## 実行方法

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

ビルドの警告が出なければ成功です。

# `Prism`アプリケーション

以下では、Visual Studio Codeにより編集を行ないます。
執筆時点では、`C#`の拡張機能に不具合があり、シンタックスエラーの赤が表示される場合がありますが、コンパイルが通れば問題ありません。
また、`XAML`の編集機能は現時点で無いため、便利に開発したい場合はVisual Studioを使用した方が良いかもしれません。

## `App.xaml`の編集

```diff:App.xaml
- <Application x:Class="PrismSample.App.Main.App"
+ <prism:PrismApplication x:Class="PrismSample.App.Main.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+              xmlns:prism="http://prismlibrary.com/"
             xmlns:local="clr-namespace:PrismSample.App.Main"
-              StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
- </Application>
+ </prism:PrismApplication>
```

標準の`Application`クラスを`PrismApplication`クラスに置き換えています。
また、`StartupUri`が不要になっています。

この作業により、ビルド時に自動生成される`App`クラスが`PrismApplication`を継承するように変化します。

## `App.xaml.cs`の編集

```diff:App.xaml.cs
- using System;
- using System.Collections.Generic;
- using System.Configuration;
- using System.Data;
- using System.Linq;
- using System.Threading.Tasks;
using System.Windows;
+ using Prism.Ioc;
+ using Prism.Unity;

namespace PrismSample.App.Main
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
-     public partial class App : Application
+     public partial class App : PrismApplication
    {
+         protected override Window CreateShell()
+         {
+             return Container.Resolve<MainWindow>();
+         }
+
+         protected override void RegisterTypes(IContainerRegistry containerRegistry)
+         {
+         }
    }
}
```

標準の`Application`ではなく、`Prism.Unity.PrismApplication`を継承します。

`CreateShell`メソッドは、アプリケーション開始時に起動する`Window`を返す必要があります。
コンテナから`MainWindow`を生成して返しておきます。
ここで、何もしていないのに`MainWindow`がDIコンテナに登録されているのがポイントです。
同じアセンブリ（プロジェクト）内のViewとViewModelが自動的に登録されるのです。
`Prism`の便利さの本質はここに集約されるのではないでしょうか。

その下にある`RegisterTypes`メソッドは、コンテナに手動でクラスやインスタンスを登録する際に使用します。

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![initial-window.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/4e37d8b6-1745-4aad-60cd-2ddc3b60a758.jpeg)

うまく`MainWindow`が表示されました。

# プロジェクトをまたいだクラスの登録

## View

`PrismSample.App.Main`の`MainWindow.xaml`と`MainWindow.xaml.cs`を削除します。

`PrismSample.Lib.Views`に、以下の`MainWindow.xaml`と`MainWindow.xaml.cs`を作成します。

```xml:MainWindow.xaml
<Window x:Class="PrismSample.Lib.Views.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:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:PrismSample.Lib.Views"
        mc:Ignorable="d"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="MainWindow" Width="800" Height="450" FontSize="40">
    <Grid>
        <TextBlock Text="{Binding Text.Value}"/>
    </Grid>
</Window>
```

```csharp:MainWindow.xaml.cs
using System.Windows;

namespace PrismSample.Lib.Views
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}
```

Viewの`prism:ViewModelLocator.AutoWireViewModel="True"`という記述は、関連付けられたViewModelがDIコンテナ内に存在する場合、自動的に`DataContext`として登録することを指定しています。
これを行なうのは`ViewModelLocator`というものです。

Viewの`TextBlock.Text`のBindingを`Text.Value`としている点に注意してください。
以下で登場する`ReactiveProperty`から値を取得するには、`Value`プロパティを参照する必要があります。

## ViewModel

`PrismSample.Lib.ViewModels`に、以下の`MainWindowViewModel.cs`を作成します。

```csharp:MainWindowViewModel.cs
using Prism.Mvvm;
using Reactive.Bindings;

namespace PrismSample.Lib.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        public ReactiveProperty<string> Text { get; } = new ReactiveProperty<string>("Hello, Prism!");
    }
}
```

ViewModelには`Prism.Mvvm.BindableBase`を継承させるのを忘れないでください。

`Text`は`Reactive.Bindings.ReactiveProperty`型にしています。
`ReactiveProperty`は`INotifyPropertyChanged`を実装しているため、双方向バインディングが可能です。

## App

`App.xaml.cs`に以下の記述を追加します。

```diff:App.xaml.cs
using System.Windows;
using Prism.Ioc;
+ using Prism.Mvvm;
using Prism.Unity;
+ using PrismSample.Lib.Views;
+ using PrismSample.Lib.ViewModels;

namespace PrismSample.App.Main
{
    public partial class App : PrismApplication
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }

+         protected override void ConfigureViewModelLocator()
+         {
+             base.ConfigureViewModelLocator();
+ 
+             ViewModelLocationProvider.Register<MainWindow , MainWindowViewModel>();
+         }
    }
}
```

`ConfigureViewModelLocator`メソッド内で、ViewとViewModelを関連付けています。

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![hello-window.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/f6f55038-d40e-1b5a-1d76-72484cb9b126.jpeg)

`MainWindow`と`MainWindowViewModel`が関連付けられています。

# Regionの表示

## View

前述の`N^2`を求めるための表示領域を用意します。
`N`の値を入力するほうを`OperandView`、計算結果を表示するほうを`AnswerView`とします。

```xml:OperandView.xaml
<UserControl x:Class="PrismSample.Lib.Views.OperandView"
        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:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:PrismSample.Lib.Views"
        mc:Ignorable="d"
        prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="N ="
                HorizontalAlignment="Left" VerticalAlignment="Center"/>
            <TextBox Grid.Column="1"
                HorizontalAlignment="Stretch" VerticalAlignment="Center"
                Text="{Binding Operand.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </Grid>
        <Button Grid.Row="1" Content="Show Dialog"
            Command="{Binding ShowDialogCommand}"/>
    </Grid>
</UserControl>
```

```csharp:OperandView.xaml.cs
using System.Windows.Controls;

namespace PrismSample.Lib.Views
{
    public partial class OperandView : UserControl
    {
        public OperandView()
        {
            InitializeComponent();
        }
    }
}
```

```xml:Answer.xaml
<UserControl x:Class="PrismSample.Lib.Views.AnswerView"
        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:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:PrismSample.Lib.Views"
        mc:Ignorable="d"
        prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="N^2 ="
                HorizontalAlignment="Left" VerticalAlignment="Center"/>
            <TextBlock Grid.Column="1"
                HorizontalAlignment="Left" VerticalAlignment="Center"
                Text="{Binding Answer.Value}"/>
        </Grid>
        <Button Grid.Row="1" Content="Show Dialog"
            Command="{Binding ShowDialogCommand}"/>
    </Grid>
</UserControl>
```

```csharp:AnswerView.xaml.cs
using System.Windows.Controls;

namespace PrismSample.Lib.Views
{
    public partial class AnswerView : UserControl
    {
        public AnswerView()
        {
            InitializeComponent();
        }
    }
}
```

## ViewModel

上で作成したViewに対応するViewModelを作成します。

```csharp:OpearndViewModel.cs
using System;
using System.ComponentModel.DataAnnotations;
using Prism.Mvvm;
using Reactive.Bindings;

namespace PrismSample.Lib.ViewModels
{
    public class OperandViewModel : BindableBase
    {
        [Required, Range(-10000, 10000)]
        public ReactiveProperty<string> Operand { get; }

        public OperandViewModel()
        {
            Operand = new ReactiveProperty<string>("2")
                .SetValidateAttribute(() => Operand);
        }
    }
}
```

```csharp:AnswerViewModel.cs
using Prism.Mvvm;
using Reactive.Bindings;

namespace PrismSample.Lib.ViewModels
{
    public class AnswerViewModel : BindableBase
    {
        public ReactiveProperty<string> Answer { get; }

        public AnswerViewModel()
        {
            Answer = new ReactiveProperty<string>("4");
        }
    }
}
```

まだ計算機能はありません。
`SetValidateAttribute`メソッドは、`System.ComponentModel.DataAnnotations`の属性から、対応するエラーが存在するかどうかを検出可能にします。

## MainWindowにRegionを確保

先程のViewを表示するためのRegionを`MainWindow`に作成します。

```xml:MainWindow.xaml
<Window x:Class="PrismSample.Lib.Views.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:prism="http://prismlibrary.com/"
        xmlns:local="clr-namespace:PrismSample.Lib.Views"
        mc:Ignorable="d"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="MainWindow" Width="800" Height="450" FontSize="40"
        Loaded="OnLoaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ContentControl Grid.Column="0" Margin="20"
            prism:RegionManager.RegionName="OperandRegion"/>
        <ContentControl Grid.Column="1" Margin="20"
            prism:RegionManager.RegionName="AnswerRegion"/>
    </Grid>
</Window>
```

`prism:RegionManager.RegionName`にRegionの名前を指定します。

## RegionにViewを追加

`MainWindow`のコードビハインドを修正します。

```csharp:MainWindow.xaml.cs
using System.Windows;
using Prism.Ioc;
using Prism.Regions;
using Unity.Attributes;

namespace PrismSample.Lib.Views
{
    public partial class MainWindow : Window
    {
        [Dependency]
        public IContainerExtension ContainerExtension { get; set; }

        [Dependency]
        public IRegionManager RegionManager { get; set; }

        public MainWindow()
        {
            InitializeComponent();
        }

        public void OnLoaded(object sender, RoutedEventArgs e)
        {
            RegionManager.AddToRegion("OperandRegion", ContainerExtension.Resolve<OperandView>());
            RegionManager.AddToRegion("AnswerRegion" , ContainerExtension.Resolve<AnswerView>());
        }
    }
}
```

Regionの追加に必要な`Prism.Ioc.IContainerExtension`と`Prism.Regions.IRegionManager`を、DIにより取得しています。
これは、セッターインジェクションという手法で、`Unity.Attributes.Dependency`属性をセッターに付加することで実現します。

## ViewとViewModelの関連付け

先程と同様に、`App.xaml.cs`の`ConfigureViewModelLocator`メソッドでViewとViewModelを関連付けを行ないます。

```diff:App.xaml.cs
        protected override void ConfigureViewModelLocator()
        {
            base.ConfigureViewModelLocator();

            ViewModelLocationProvider.Register<MainWindow , MainWindowViewModel>();
+             ViewModelLocationProvider.Register<OperandView, OperandViewModel   >();
+             ViewModelLocationProvider.Register<AnswerView , AnswerViewModel    >();
        }
```

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![complete-window.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/d994b189-c0ea-acc6-6a8f-fdc36106ed8b.jpeg)

Regionに2つのViewが表示されました。

# EventAggregatorによるViewModel間通信

## ViewModel

EventAggregatorは、PubSubパターンでイベントの通知と購読を管理することで、ViewModel間の通信を実現します。
EventAggregatorは、ViewModelが自動生成される際にDIにより取得可能です。
先程と同様、セッターインジェクションで取得できますが、ここではコンストラクタインジェクションで取得してみましょう。

`OperandViewModel`と`AnswerViewModel`を改変します。

```csharp:OperandViewModel.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Reactive.Linq;
using Prism.Events;
using Prism.Mvvm;
using Reactive.Bindings;

namespace PrismSample.Lib.ViewModels
{
    public class OperandViewModel : BindableBase
    {
        [Required, Range(-10000, 10000)]
        public ReactiveProperty<string> Operand { get; }

        public OperandViewModel(IEventAggregator eventAggregator)
        {
            Operand = new ReactiveProperty<string>("2")
                .SetValidateAttribute(() => Operand);

            Observable.WithLatestFrom
            (
                Operand,
                Operand.ObserveHasErrors,
                (o, e) => (o, e)
            )
            .Where(z => !z.e)
            .Subscribe(z =>
            {
                eventAggregator
                    .GetEvent<PubSubEvent<double>>()
                    .Publish(double.Parse(z.o));
            });
        }
    }
}
```

```csharp:AnswerViewModel.cs
using Prism.Events;
using Prism.Mvvm;
using Reactive.Bindings;
using Unity.Attributes;

namespace PrismSample.Lib.ViewModels
{
    public class AnswerViewModel : BindableBase
    {
        public ReactiveProperty<string> Answer { get; }

        public ReactiveCommand<object> ShowDialogCommand { get; }

        public AnswerViewModel(IEventAggregator eventAggregator)
        {
            Answer = new ReactiveProperty<string>("4");
            
            eventAggregator
                .GetEvent<PubSubEvent<double>>()
                .Subscribe(CalculateAnswer);
        }

        private void CalculateAnswer(double operand)
        {
            Answer.Value = (operand * operand).ToString();
        }
    }
}
```

コンストラクタの引数にEventAggregatorが注入されます。

`GetEvent<PubSubEvent<double>>`メソッドにより、`double`型のイベントを取得し、`Publish`で値を発行、`Subscribe`で発行された値を購読します。
ここでは`double`型を使用していますが、EventAggregatorは複数のViewModelで共有されるため、通信用に独自の型を定義した方が良いかもしれません。

この`Subscribe`はちょっとくせ者で、購読メソッドを**弱参照**するという特徴があります。
なぜなら、強参照を持ってしまうと参照の依存関係が生じ、後々解放する必要が出てくるからです。
弱参照にしておけば、その手間が省けるのですが、代わりに注意すべきことがあります。

以下のように、

```csharp
eventAggregator
    .GetEvent<PubSubEvent<double>>()
    .Subscribe(o => Answer.Value = (o * o).ToString(););
```

と、ラムダ式を使って購読した場合、このラムダ式はこの場で生成され、誰も参照を持っていない宙ぶらりんの状態になります。
そうすると、GCにより回収の対象になってしまいます。
結果、イベントが通知されない！ ということになります。
ですから、ここはラムダ式を使わず、メソッドへの参照を直接指定しましょう。

```csharp
eventAggregator
    .GetEvent<PubSubEvent<double>>()
    .Subscribe(CalculateAnswer);
```

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![complete-window2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/45285075-e544-2056-e038-17b27f45a499.jpeg)

ViewModel間で値のやり取りができています。

# Modelの追加

## Model

`N^2`の計算をModelに持たせるようにしてみましょう。
テスト可能なコードにするために、インターフェイスも用意しておきます。

```csharp:IModel.cs
namespace PrismSample.Lib.Models
{
    public interface IModel
    {
        double Calculate(double operand);
    }
}
```

```csharp:Model.cs
namespace PrismSample.Lib.Models
{
    public class Model : IModel
    {
        public double Calculate(double operand)
        {
            return operand * operand;
        }
    }
}
```

## App

`IModel`と`Model`を関連付けておきましょう。

```diff:App.xaml.cs
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
+             containerRegistry.Register<IModel, Model>();
        }
```

## ViewModel

`IModel`をインジェクションします。

```diff:AnswerViewModel.cs
+        [Dependency]
+        public IModel Model { get; set; }

+         private void CalculateAnswer(double operand)
+         {
+             Answer.Value = Model.Calculate(operand).ToString();
+         }
```

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![complete-window2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/56184b7b-b69b-86bb-51c2-68ed7564245f.jpeg)

Modelによる計算ができています。

# Dialog

Dialogの表示に関しては、`Prism 7.2`以降では`Prism.Services.Dialogs.IDialogService`を使用します。
しかし、メッセージダイアログをちょっと表示したいだけ、という場合、使い勝手は良くないです（DialgのViewとViewModelを自分で書かなくてはいけない）。

本記事では利便性を重視して、`WPF`標準の`MessageBox`を使用する方法をご紹介します。

## View

```csharp:IDialogHelper.cs
using System.Windows;

namespace PrismSample.Lib.Views
{
    public enum DialogButton
    {
        OK          = MessageBoxButton.OK,
        OKCancel    = MessageBoxButton.OKCancel,
        YesNo       = MessageBoxButton.YesNo,
        YesNoCancel = MessageBoxButton.YesNoCancel
    }

    public enum DialogImage
    {
        None        = MessageBoxImage.None,
        Information = MessageBoxImage.Information,
        Question    = MessageBoxImage.Question,
        Warning     = MessageBoxImage.Warning,
        Error       = MessageBoxImage.Error
    }

    public enum DialogResult
    {
        None   = MessageBoxResult.None,
        OK     = MessageBoxResult.OK,
        Cancel = MessageBoxResult.Cancel,
        Yes    = MessageBoxResult.Yes,
        No     = MessageBoxResult.No
    }

    public interface IDialogHelper
    {
        DialogResult ShowDialog
        (
            string message,
            string caption      = "Information",
            DialogButton button = DialogButton.OK,
            DialogImage  image  = DialogImage.None
        );
    }
}
```

```csharp:DialogHelper.cs
using System.Windows;

namespace PrismSample.Lib.Views
{
    public class DialogHelper : IDialogHelper
    {
        public DialogResult ShowDialog
        (
            string message,
            string caption,
            DialogButton button,
            DialogImage  image
        )
        {
            return (DialogResult)MessageBox.Show
            (
                message,
                caption,
                (MessageBoxButton)button,
                (MessageBoxImage )image
            );
        }
    }
}
```

Dialogの表示は思いっきり副作用なので、インターフェイスを定義しておきます。
今回は`PrismSample.Lib.Views`内に上記2つを定義しましたが、インターフェイスを別プロジェクトに分けることで、`ViewModels`から`Views`への参照を無くすることもできます。

## ViewModel

Dialogを表示するコマンドを、`Reactive.Bindings.ReactiveCommand`を使用して書きます。

```csharp:OperandViewModel
        [Dependency]
        public IDialogHelper DialogHelper { get; set; }

        public ReactiveCommand<object> ShowDialogCommand { get; }

        // コンストラクタ内
        ShowDialogCommand = new ReactiveCommand(Operand.ObserveHasErrors.Select(x => !x))
                .WithSubscribe(_ => DialogHelper.ShowDialog($"N = {Operand.Value}"));
```

```csharp:AnswerViewModel
        [Dependency]
        public IDialogHelper DialogHelper { get; set; }

        public ReactiveCommand<object> ShowDialogCommand { get; }

        // コンストラクタ内
        ShowDialogCommand = new ReactiveCommand()
                .WithSubscribe(_ => DialogHelper.ShowDialog($"N^2 = {Answer.Value}"));
```

`OperandViewModel`内で`ReactiveCommand`のコンストラクタに渡しているのは、`IObservable<bool>`です。
これは、`WPF`の`ICommand`における`CanExecute`と`CanExecuteChanged`を制御し、コマンドが実行可能かどうかを自動的に反映するようにしてくれるものです。

`AnswerViewModel`では、`ReactiveCommand`のコンストラクタに引数を渡していませんが、これは常に実行可能なコマンドを生成します。

## App

`IDialogHelper`と`DialogHelper`の関係性を登録します。

```diff:App.xaml
        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
+             containerRegistry.Register<IDialogHelper, DialogHelper>();
            containerRegistry.Register<IModel, Model>();
        }
```

## 実行

```powershell:PowerShell
> dotnet run -p .\PrismSample.App.Main\
```

![dialog.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53668/a3e255ed-e71d-2f69-25cb-7662e30f84e1.jpeg)

Dialogが表示できました。

# まとめ

無駄に長文になってしまいました。
本記事の内容をまとめます。

- `Prism.Unity`と`ReactiveProperty`をインストール。
- `App.xaml`を、`prism:PrismApplication`を使用するように変更。
- `App.xaml.cs`の`CreateShell`で`MainWindow`を返す。
- `App.xaml.cs`の`RegisterTypes`でDIコンテナに型を登録。
- `App.xaml.cs`の`ConfigureViewModelLocator`でViewとViewModelの関連を登録。
- ViewModelには`BindableBase`を継承させる。
- DIコンテナからインスタンスを生成する場合、コンストラクタインジェクションまたはセッターインジェクションで、登録された型のインスタンスを取得可能。
- EventAggregatorを使用してViewModel間の通信が可能。
- EventAggregatorは、コールバックを弱参照することに注意。
- Dialogの表示を`Prism`でやるのは大変なので、ヘルパークラスを定義。
- `ReactiveProperty`と`ReactiveCommand`で、ViewModelのプロパティを便利に管理。

以上です。
ありがとうございました。