【備忘録】WPFのPrismライブラリ

## 初めに
WPFで開発するとき、皆さんはどのようなライブラリを使用しますか？
もちろん、Prismですよね。今回そのPrismについてのメモです。

## Prismとはなにか
PrismはWPFアプリケーションを構築するための強力なライブラリであり、MVVM（Model-View-ViewModel）パターンを簡単に実装できるように設計されています。
例えば、
ViewModelのプロパティの値が変わった時、Viewも変更して欲しい。
このボタンはテキストボックスに入力されてから表示して欲しい。
処理が終わったら、影響している個所を更新して表示して欲しい。
など、手に届かない細々としたことをやってくれる機能を提供してくれます。

## 使用例
どのような機能があるのか、3つの例を元に紹介します。

### 例1 ViewModelのプロパティの値が変わった時、Viewも変更して欲しい

Prismを使用しない場合だと、INotifyPropertyChangedを使用して、自身でプロパティ変更通知を実装する必要があります。
```cs:INotifyPropertyChangedを使用した場合
using System.ComponentModel;
public class MainViewModel : INotifyPropertyChanged
{
    private string _inputText;
    public string InputText
    {
        get => _inputText;
        set
        {
            if (_inputText != value) // 値が変更された場合のみ処理
            {
                _inputText = value;
                OnPropertyChanged(nameof(InputText)); // プロパティ変更通知を発行
            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
```
* InputTextのSetに値が変更されたかを確認し、OnPropertyChanged(nameof(InputText))でプロパティ変更通知を出しています。
* また、OnPropertyChangedメソッドもViewModel内に自作する必要があります。
と、いろいろ用意やチェックが必要です。

では、Prismを使用するとどうなるか
Prismを使用する場合以下の様になります。

```cs:Prismを使用した場合
using Prism.Mvvm;
public class MainViewModel : BindableBase
{
    private string _inputText;
    public string InputText
    {
        get => _inputText;
        set => SetProperty(ref _inputText, value); // プロパティの変更と通知を簡素化
    }
}
```
* Prismを使用する場合、Prismが提供しているBindableBaseを継承する必要があります。

SetPropertyを使用することで、値の変更されたかの確認とプロパティ変更通知を行ってくれます。
コード記述量が圧倒的に減りながらも、行っている処理は同じです。

### 例2 このボタンはテキストボックスに入力されてから表示して欲しい
テキストボックに入力があった時にボタンが表示される例の場合以下のようなコードになります。

```xml:MainWindow.xaml
<Window x:Class="Namespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Namespace"
        Title="Input Example" Height="200" Width="300">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
    </Window.Resources>
    <StackPanel Margin="10">
        <TextBox Text="{Binding InputText, UpdateSourceTrigger=PropertyChanged}" 
                 Width="200" Margin="0,0,0,10"/>
        <Button Content="Submit" 
                Command="{Binding SubmitCommand}" 
                Visibility="{Binding SubmitCommand.CanExecute, Converter={StaticResource BoolToVisConverter}}"/>
    </StackPanel>
</Window>
```

```cs:MainWindow.cs
using Prism.Commands;
using Prism.Mvvm;
public class MainViewModel : BindableBase
{
    private string _inputText;
    private DelegateCommand _submitCommand;
    public string InputText
    {
        get => _inputText;
        set => SetProperty(ref _inputText, value); // ここでプロパティの変更を通知
    }
    public DelegateCommand SubmitCommand =>
        _submitCommand ?? (_submitCommand = new DelegateCommand(ExecuteSubmit, CanExecuteSubmit)
            .ObservesProperty(() => InputText)); // InputTextを監視
    private void ExecuteSubmit()
    {
        // ボタンが押された時の処理
    }
    private bool CanExecuteSubmit()
    {
        // InputTextが空でない場合にのみボタンを有効化
        return !string.IsNullOrWhiteSpace(InputText);
    }
}
```
 #### ポイント
* ボタンのコマンドでSubmitCommandをバインドしています。
SubmitCommandは、コマンド処理としての**ExecuteSubmit**とコマンドの実行可否を判定する**CanExecuteSubmit**を扱います。
　
* **ObservesProperty**は、InputTextの変更を監視です。
InputTextのSetが動くと**CanExecuteSubmit**の判定処理も一緒に動くようになります。
　
* **CanExecuteSubmit**は、bool型でTureでボタンを活性、Falseで非活性にします。
このままだと、単純にボタンが活性・非活性になるためXAMLではコンバータを通して表示・非表示にしています。

### 例3 処理が終わったら、影響している所を更新して表示して欲しい
ボタンを押したら、テキストボックスの記入内容をタイトルする処理を例の場合以下のコードになります。
```xml:MainWindow.xaml
<Window x:Class="Namespace.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Namespace"
        Title="{Binding Title}" Height="200" Width="300">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <StackPanel Margin="10">
        <TextBox Text="{Binding InputText, UpdateSourceTrigger=PropertyChanged}" 
                 Width="200" Margin="0,0,0,10"/>
        <Button Content="Submit" Command="{Binding SubmitCommand}" 
                IsEnabled="{Binding SubmitCommand.CanExecute}" />
    </StackPanel>
</Window>
```
```cs:MainViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
public class MainViewModel : BindableBase
{
    private string _inputText;
    private DelegateCommand _submitCommand;
    // テキストボックスに入力する内容
    public string InputText
    {
        get => _inputText;
        set
        {
            if (SetProperty(ref _inputText, value))
            {
                // InputTextが変更されたらTitleの変更通知を明示的に発行
                RaisePropertyChanged(nameof(Title));
            }
        }
    }
    // InputTextを返す
    public string Title => InputText;

    // ボタンのコマンド
    public DelegateCommand SubmitCommand =>
        _submitCommand ?? (_submitCommand = new DelegateCommand(ExecuteSubmit, CanExecuteSubmit)
            .ObservesProperty(() => InputText));
    private void ExecuteSubmit()
    {
        //Title（依存プロパティ）が変更されたことを通知
        RaisePropertyChanged(nameof(Title));
    }
    private bool CanExecuteSubmit()
    {
        // InputTextが空でない場合にのみボタンを有効化
        return !string.IsNullOrWhiteSpace(InputText);
    }
}
```
 #### ポイント
* TitleにはSetが無く、 InputTextをGetしています。
* InputTextが入力されたら、**RaisePropertyChanged(nameof(Title))** によってTitleを更新しています。
* **SetProperty**はboolを返しているため、ifの条件式に使えます。

この例は、RaisePropertyChangedの例に出すためにちょっと無駄な処理になっています。
感がいい方は、「Titleも例題1同様にSetPropertyにすればよいのでは」と気づいたでしょう。
**RaisePropertyChanged**は、何かしらの処理後にプロパティを更新かけたいときに使用します。
また、RaisePropertyChanged(nameof(**null**))にすると、ViewModel全体のプロパティを更新します。

## 最後に
今回は、Prismの一部機能について例を交えてメモしました。
この3つは私が便利だと思ったものです。他にもViewとViewModelを便利にする機能があるので、ぜひ試してみてください。