2024年8月23日金曜日

Prism + DryIoc 入門②(お作法編)

どんなフレームワークにも決まったお作法というか、こう使ってほしいという想定があります。逆に言えば、それをマスターすることがそのフレームワークを自由自在に使いこなせるようになる近道というわけです。今回はそのようなPrismのお作法を紹介していきます。

1. プロジェクトテンプレート

Prismを使ったWPFアプリを作る場合は、プロジェクトテンプレートを使いましょう。Visual Studio 2022の場合は、メニューの「拡張機能」→「拡張機能の管理」からPrismと検索するとPrism Template Packが出てきてインストールすることができます。

インストールをすると、プロジェクトの新規作成からPrismを選択できるようになります。今回は「Prism Blank App (WPF)」を選択します。

そうするといつも通りプロジェクトの名前と保存名が聞かれるので適当に入力します。

ここで「作成」を押すと、PrismのDIコンテナを選択するダイアログが出てきますので、「DryIoc」を選択します。

これにて無事Prism+DryIocのプロジェクトが出来上がりました。

最後にお好みで、.NETのバージョンを最新に上げたり、Nullableを有効化したり、Nugetからパッケージを最新にしたりしておきましょう。ビルドすると空のWPFアプリが立ち上がるはずです。

ちなみに、Prismはバージョン8まではMITライセンスですが、バージョン9からはPrism Community License / Prism Commercial Licenseという独自の商用ライセンスに切り替わっているようです。バージョンアップには要注意です。

2. 領域(Region)の作成と配置

ウィンドウにUI要素(コントロールなど)を配置するとき、MainWindowに直接配置することもできますが、もう少し小さいパーツ単位でUI要素を作って配置したいときがあると思います。そうしないとMainWindowに全部入りになってしまいますもんね。そんな時にはRegionと呼ばれるものを作ると良いです。

ちなみに、Regionの実体はただのUserControlです。 新しい項目の追加から、Prismテンプレートの"Prism UserControl (WPF)"を選択します。

今回は、MainWindowの左半分と右半分のRegionということで、LeftRegionとRightRegionの2つを追加しました。UserControlを新規作成すると、対応するViewModelも自動的に作ってくれます。

MainWindow.xamlでは次のようにContentControlにてRegionを配置します。

<Window x:Class="PrismTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>

        <ContentControl Grid.Column="0" prism:RegionManager.RegionName="LeftRegion" />
        <ContentControl Grid.Column="1" prism:RegionManager.RegionName="RightRegion" />
    </Grid>
</Window>

これだけでは実は不十分で、Region名と実際のUserControl型を紐づけなければなりません。

public class MainWindowViewModel : BindableBase
{
    private string _title = "Prism Application";
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    public MainWindowViewModel(IRegionManager regionManager)
    {
        regionManager.RegisterViewWithRegion("LeftRegion", typeof(LeftRegion));
        regionManager.RegisterViewWithRegion("RightRegion", typeof(RightRegion));
    }
}

MainWindowViewModelのコンストラクタにIRegionManager型の引数を設定します。こうするとDIコンテナが良い感じにIRegionManagerの実体を割り振ってくれるので、ここでRegisterViewWithRegionメソッドを呼び出してRegion名と実際のUserControl型を結び付けておきます。

これでこのようにRegionが所定の場所に割り付けられるようになりました。

ちなみに上の画像に"Left Region"と"Right Region"と表示しているのは、空っぽだと見た目でよくわからないので、そういうコードを追加したからです。

<UserControl x:Class="PrismTest.Views.LeftRegion"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Border BorderThickness="1" BorderBrush="Black" >
        <TextBlock Text="Left Region" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
</UserControl>
<UserControl x:Class="PrismTest.Views.RightRegion"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Border BorderThickness="1" BorderBrush="Black" >
        <TextBlock Text="Right Region" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
</UserControl>

3. デザイン時のデータ

実装を進めているとあることに気が付きます。ViewのXAMLを編集する際、ViewModelのプロパティのサジェストがVisual Studioで働かないのです。これはViewModelLocator.AutoWireViewModelにてViewModelが実行時に動的に紐づけられるからで、XAMLにはどこにもViewModelの型を明記していないからです。それはさすがにサジェストが働かなくて当然ですね。

このようなときに便利なのがデザイン時のデータという機能です。

一番大元の要素に以下のような呪文を追加します。

<Window x:Class="PrismTest.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:PrismTest.ViewModels"
        mc:Ignorable="d"
        d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel}"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="350" Width="525" >
    <!-- 中略 -->
</Window>

5から9行目が呪文部分です。これを入力することによって、Visual Studioでのデザイン時のみDataContextをMainWindowViewModelとみなすことができ、プロパティなどのサジェストが働くようになります。

4. インターフェースとクラスのマッピング

最後にインターフェースとクラスのマッピングです。前回の記事でも説明した通り、DIを使用した実装では、各クラス間の依存性を抑えるため、コンストラクタでインターフェースを受け取るようにし、DIコンテナがそれに対応するクラスをインスタンス化して渡してくれます。

それを実現するためには、インターフェースとクラスの対応付けをあらかじめ設定しておく必要があります。その設定は、App.xaml.cs内で記述することになります。

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

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        containerRegistry.Register<IMainWindowModel, MainWindowModel>();
    }
}

RegisterTypesメソッドをオーバーライドして、そのなかでRegisterメソッドを呼び出すことでインターフェースとクラスのマッピングをすることができます。

public class MainWindowViewModel : BindableBase
{
    private string _title = "Prism Application";
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    public MainWindowViewModel(IRegionManager regionManager, IMainWindowModel model)
    {
        regionManager.RegisterViewWithRegion("LeftRegion", typeof(LeftRegion));
        regionManager.RegisterViewWithRegion("RightRegion", typeof(RightRegion));

        Title = model.Title;
    }
}

マッピングしてさえいれば、コンストラクタに引数を追加することで、DIコンテナにて対応するインスタンスを作ってくれます。

なお、グローバルなデータ、設定値などはRegisterSingletonメソッドでDIコンテナに登録することによって、同じインターフェースには唯一のインスタンス(実体が同じもの)が返されるようになります。

***

以上で必要最低限のお作法は紹介できたはずです。 ここから自分の作りたいアプリをどんどん実装していきましょう。

0 件のコメント:

コメントを投稿