UWP 版の Prism の Prism.Uno を試してみよう

XAML 系プラットフォーム（WPF、Xamarin.Forms、UWP）でよく使われてる MVVM フレームワークの Prism ですが、UWP 版の Prism は一時期削除されましたが、Prism v8 (現在はプレビュー段階) で WPF 版の Prism をベースに UWP に移植されました。削除された昔の UWP 用 Prism は、どちらかというと Xamarin.Forms の Prism を踏襲していましたが、WPF のほうに寄せてきました。

## 試してみよう

UWP プロジェクトを新規作成します。そして `Prism.Unity.Uno` のプレビュー版のパッケージを追加します。
そして、`Views` 名前空間に `Shell` という名前でページを作成します。XAML は以下のようにして、とりあえず ContentRegion が 1 つあるだけにしました。

```xml:Shell.xaml
<Page
    x:Class="HelloPrismApp.Views.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloPrismApp.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:regions="using:Prism.Regions"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid Padding="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <TextBlock Text="MyApp"
                   Style="{StaticResource HeaderTextBlockStyle}" />
        <ContentControl Grid.Row="1" 
                        regions:RegionManager.RegionName="ContentRegion" />
    </Grid>
</Page>
```

`MainPage.xaml` は不要なので削除して `App.xaml` を以下のように `PrismApplication` を継承するようにします。

```xml:App.xaml
<prism:PrismApplication
    x:Class="HelloPrismApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="using:Prism.Unity"
    xmlns:local="using:HelloPrismApp">

</prism:PrismApplication>
```

`App.xaml.cs` も `PrismApplication` を継承するようにします。`CreateShell` メソッドで Shell をコンテナから取得するようにすれば OK です。

```csharp:App.xaml.cs
using HelloPrismApp.Views;
using Prism.Ioc;
using Prism.Unity;
using Windows.UI.Xaml;

namespace HelloPrismApp
{
    sealed partial class App : PrismApplication
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override UIElement CreateShell() => Container.Resolve<Shell>();

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }
    }
}
```

実行すると以下のようになります。Shell が表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/a157b9ca-8081-81ef-2c42-6f4daa62283f.png)

## 画面の中身を作ろう

Prism は Shell の Region に対して View をモジュールから差し込むことが出来ます。やってみましょう。UWP のクラスライブラリを `HelloPrismApp.Main` という名前で作って Prism.Uno パッケージ（プレビュー）を追加します。

クラスライブラリ側に ViewModels フォルダーを作って適当に VM を作ります。

```csharp:TopViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using System;

namespace HelloPrismApp.Main.ViewModels
{
    public class TopViewModel : BindableBase
    {
        private string _message;
        public string Message
        {
            get { return _message; }
            set { SetProperty(ref _message, value); }
        }

        private DelegateCommand _updateMessageCommand;
        public DelegateCommand UpdateMessageCommand =>
            _updateMessageCommand ?? (_updateMessageCommand = new DelegateCommand(ExecuteUpdateMessageCommand));

        private void ExecuteUpdateMessageCommand()
        {
            Message = DateTime.Now.ToString();
        }
    }
}
```

View も作ります。UserControl です。ViewModelLocator を設定して ViewModel が DataContext に設定されるようにします。

```xml:TopView.xaml
<UserControl
    x:Class="HelloPrismApp.Main.Views.TopView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloPrismApp.Main.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModels="using:Prism.Mvvm"
    viewModels:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">

    <StackPanel>
        <TextBlock Text="{x:Bind ViewModel.Message, Mode=OneWay}"
                   Style="{StaticResource BodyTextBlockStyle}" />
        <Button Content="Update message"
                Command="{x:Bind ViewModel.UpdateMessageCommand}" />
    </StackPanel>
</UserControl>
```

TopView.xaml.cs に ViewModel プロパティも生やしておきます。

```csharp:TopView.xaml.cs
using HelloPrismApp.Main.ViewModels;
using Windows.UI.Xaml.Controls;

namespace HelloPrismApp.Main.Views
{
    public sealed partial class TopView : UserControl
    {
        private TopViewModel ViewModel => (TopViewModel)DataContext;
        public TopView()
        {
            this.InitializeComponent();
        }
    }
}
```

最後に `MainModule` クラスを作って View の登録と View を Region に追加します。

```csharp:MainModule.cs
using HelloPrismApp.Main.ViewModels;
using HelloPrismApp.Main.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;

namespace HelloPrismApp.Main
{
    public class MainModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RequestNavigate("ContentRegion", "TopView");
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<TopView, TopViewModel>();
        }
    }
}
```

モジュールが出来たのでアプリ本体のほうに参照を追加して `App.xaml.cs` にモジュールを登録します。

```csharp:App.xaml.cs
using HelloPrismApp.Main;
using HelloPrismApp.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Unity;
using Windows.UI.Xaml;

namespace HelloPrismApp
{
    sealed partial class App : PrismApplication
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override UIElement CreateShell() => Container.Resolve<Shell>();

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
        }

        protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
        {
            base.ConfigureModuleCatalog(moduleCatalog);
            moduleCatalog.AddModule<MainModule>();
        }
    }
}
```

実行すると `Shell` に `TopView` が表示されています。コマンドもちゃんと動いているので ViewModel もちゃんと設定されてますね。

![prism.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/a14c751a-987d-2121-1da2-2e6e55b4449c.gif)

## まとめ

個人的な予想ですが順当に進化していくと Windows UI Library 3.0 にも対応していくと思います。
そうなってくると理想的には UWP の他に Win32 の WinUI 3.0 にも対応してくれるのではないかと期待しています。

そうなると ViewModel レイヤーより下のレイヤーは WPF/UWP/WinUI3.0 で共有化して View だけ差し替えとかもできるかもしれません。特に WPF と WinUI 3.0 の Win32 はかなり共有できないかなぁと期待してます。

ということで、これからもちょくちょく Prism の状態をウォッチしていこうと思います。