Prism.Wpf 7.2でプログレスバーのついたダイアログを作る

## はじめに
　Prismを使ってプログレスバーの付いたダイアログを作りました。
　進捗の値0~100をダイアログに送り、100のときにダイアログを閉じることができるという仕様にしました。

![prism2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135472/da85bd9f-e86c-5898-d133-5d554617eec7.gif)


### 環境

.Net Core 3.1
Prism.Wpf 7.2.0.1422

## 実装

### View

![xaml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135472/34adf945-30f4-4a2d-f451-7e353f27d8e3.png)

```xml:ProgressDialog.xaml
<UserControl x:Class="Module1.Views.ProgressDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Module1.Views"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             Height="100"
             Width="200"
             mc:Ignorable="d" 
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             xmlns:viewModels="clr-namespace:Module1.ViewModels"
             d:DataContext="{d:DesignInstance viewModels:ProgressDialogViewModel}">
    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="ShowInTaskbar" Value="False"/>
            <Setter Property="SizeToContent" Value="WidthAndHeight"/>
            <Setter Property="WindowStyle" Value="None"/>
            <Setter Property="BorderThickness" Value="1"/>
        </Style>
    </prism:Dialog.WindowStyle>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <WrapPanel Margin="10,15">
            <Label Width="30" HorizontalContentAlignment="Right" Content="{Binding Progress}"/>
            <ProgressBar Height="15" Width="130" Value="{Binding Progress}" />
        </WrapPanel>
        <Button Grid.Row="1" Height="20" Width="100" VerticalAlignment="Bottom" Margin="0,12" Content="閉じる" Command="{Binding CloseCommand}"/>
    </Grid>
</UserControl>
```

　ダイアログ用にプログレスバーと閉じるボタンが付いたViewを作成します。
　`prism:Dialog.WindowStyle`タグでダイアログとして呼び出された際のWindowStyleを制御しています。今回はできるだけダイアログを閉じられたくなかったので`WindowStyle`に`None`を指定しウィンドウの閉じるボタンを非表示にしています。

### ViewModel

```csharp:ProgressDialogViewModel
    public class ProgressDialogViewModel : BindableBase, IDialogAware
    {
        private int progress;

        public event Action<IDialogResult> RequestClose;

        public int Progress
        {
            get { return progress; }
            set { SetProperty(ref progress, value); }
        }

        public DelegateCommand CloseCommand { get; }

        public string Title => "Dialog1";

        public ProgressDialogViewModel(ProgressDialogModel model)
        {
            this.Progress = model.Progress;

            model.PropertyChanged += (_, e) =>
            {
                if (e.PropertyName == "Progress")
                {
                    this.Progress = model.Progress;
                }
            };

            this.CloseCommand =
                new DelegateCommand
                (
                    () => this.CloseDialog(),
                    () => this.Progress >= 100
                )
                .ObservesProperty(() => this.Progress);
        }

        public bool CanCloseDialog() => true;

        public void OnDialogClosed() => this.Progress = 0;

        public void OnDialogOpened(IDialogParameters parameters) { }

        private void CloseDialog()
            => RequestClose?.Invoke(new DialogResult(ButtonResult.OK));
    }
```

　ViewModelではViewのプログレスバーにバインドする項目として`Progress`プロパティを定義しています。
　`CloseCommad`ではダイアログを閉じるときの動作を担当しています。使用可否条件として`Progress`の値が100以上であることを指定しています。
　ダイアログとして利用するために`IDialogAware`を実装しています。

### Model

```csharp:ProgressDialogModel
    public class ProgressDialogModel : BindableBase
    {
        private int progress;

        public int Progress
        {
            get { return progress; }
            set { SetProperty(ref progress, value); }
        }

        public ProgressDialogModel(IEventAggregator eventAggregator)
        {
            this.Progress = 0;

            eventAggregator
                .GetEvent<ProgressMessage>()
                .Subscribe(x => this.Progress = x);
        }
    }
```

```csharp:ProgressMessage
    public class ProgressMessage : PubSubEvent<int>
    {
    }
```
　ModelではViewModelとバインドする項目として`Progress`プロパティを定義しています。
　外部からの値受け取りは`IEventAggregator`を介して行うものとし`ProgressMessage`が発行されたときに`Progress`の値が変化するようにしています。

### Module

```csharp:Module1Module
    public class Module1Module : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            _ = containerProvider.Resolve<ProgressDialogModel>();
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterSingleton<ProgressDialogModel>();
            containerRegistry.RegisterDialog<ProgressDialog>();
        }
    }
```

　Moduleでは他のプロジェクトでダイアログを利用できるようDIコンテナにViewとModelを登録しています。ModelはシングルトンとしてDIコンテナに登録しました。

### ダイアログ呼び出し部分

```csharp
        public DelegateCommand ShowDialogCommand { get; }

        public MainWindowViewModel(IDialogService dialogService, IEventAggregator eventAggregator)
        {
            this.ShowDialogCommand =
                new DelegateCommand(() =>
                {
                    _ = Task.Run(async () =>
                    {
                        eventAggregator.GetEvent<ProgressMessage>().Publish(0);
                        await Task.Delay(1000);
                        eventAggregator.GetEvent<ProgressMessage>().Publish(33);
                        await Task.Delay(1000);
                        eventAggregator.GetEvent<ProgressMessage>().Publish(66);
                        await Task.Delay(1000);
                        eventAggregator.GetEvent<ProgressMessage>().Publish(100);
                    })
                    .ContinueWith(x => x.Exception /* エラー処理 */, TaskContinuationOptions.OnlyOnFaulted);

                    dialogService.ShowDialog(nameof(ProgressDialog), new DialogParameters(), null);
                });
        }
```
　ダイアログのプロジェクトとは別の画面を作り、そのViewModelに`ShowDialogCommand`というダイアログの動作確認をするためのコマンドを用意しました。
　`ShowDialogCommand`では、非同期処理呼び出しとダイアログ呼び出しの2つを実行しています。
　非同期処理では処理中に`IEventAggregator`を介して`ProgressMessage`を発行しModelに進捗を表す数値を送っています。ここでは1秒間隔でModelの進捗の値を変更しています。`ContinueWith`の第一引数部分には非同期処理でエラーになった際の処理を書く予定です。
　ダイアログ呼び出しは`IDialogService`の`ShowDialog`を使って定義したダイアログを呼び出しています。

## おわりに
　プログレスバーの付いたダイアログを作ることができました。他の画面から呼び出せることとプログレスバーの値を処理の進み具合によって制御できることを確認できました。

・ソースコード
https://github.com/ttlatex/PrismProgressDialog