WPF + .NET Core (5以降は Core は省略される) で Prism を使ってみよう。
使用している Prism のバージョンは次のとおり:
- 8.1.97 (2021/05/25)
- GitHub - Prism Sample WPF
「Prism Full App (.NET Core) テンプレートを体験する」の記事も参考になると思います。
関連記事は以下:
7. Modules
Modules の考え方は、Prism Full App テンプレートを見ればわかるとおりですが、プロジェクトを分けることでもあります。
プロジェクトを分ける程度は、プログラムの規模にもよりますが、モジュールを利用するとプロジェクトを分ける分だけ面倒でもあります。「PrismのRegionをなるべく仰々しくない方法で使う」という記事もあり、これは実質のところプロジェクトからの Module 外しです。
サンプルやテストなど、ミニマムな状態で考えるべきテクニックと Modules はちょっとズレているかも、と留意するとよいと思います。
7.1 Modules - AppConfig
App.config から ModuleA とリンクする手法です。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" /> </configSections> <startup> </startup> <modules> <module assemblyFile="ModuleA.dll" moduleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleAModule" startupLoaded="True" /> </modules> </configuration>
ポイントは、Modules プロジェクト App.xaml.cs ファイルの内容です。
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override IModuleCatalog CreateModuleCatalog() { return new ConfigurationModuleCatalog(); } }
ConfigurationModuleCatalog を利用すると、App.config を読み込み、IModule を継承した ModuleAModule が実行され、ContentRegion
に ViewA が読み込まれる、といった流れのはずです。
実際やっていることはコーディングと大差ないことや、テキストとして処理しているので * の部分をタイプミスしてもエラーは実行時にしかわかりません。
<module assemblyFile="*.dll" moduleType="*, *, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="*" startupLoaded="True" />
それほどメリットのある記述ではないかもしれないです。
デバッグするときは、ModuleA プロジェクトを選択してあらかじめリビルドしてください。dll ファイルが先に生成されていないとデバッグでエラーになります。
7.2 Modules - Code
ModuleA とリンクする手法、その2。
単純に AddModule メソッドでモジュールを追加している。シンプルでわかりやすいです。
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { moduleCatalog.AddModule<ModuleA.ModuleAModule>(); } }
個人的には、AppConfig よりもスマートに思う。たしかに、AppConfig はコンパイル後もファイルのテキスト書き換えで修正できるメリットを享受しやすいものの、記述の内容が具体的すぎると思う。
Code でも設定ファイル等を事前に読み込むことで、およそのところは対応できると思いました。(どちらにしても、実装部分はコンパイルしないといけないので)
7.3 Modules - Directory
ModuleA とリンクする手法、その3。
Modules フォルダーに存在するモジュール群を読み込む手法です。
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override IModuleCatalog CreateModuleCatalog() { return new DirectoryModuleCatalog() { ModulePath = @".\Modules" }; } }
プロジェクトの構成に依存することになります。目に見えづらい設定になる部分が好みの分かれ目なのかな、と思います。
7.4 Modules - LoadManual
ModuleA とリンクする手法、その4。
モジュールをカタログに追加しておいて、後からモジュールを利用、設定する手法。
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) { var moduleAType = typeof(ModuleAModule); moduleCatalog.AddModule(new ModuleInfo() { ModuleName = moduleAType.Name, ModuleType = moduleAType.AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); } }
DI からモジュールマネージャーを取得して、モジュールを読み込んでいる。
最初からモジュールを読み込ませるときは InitializationMode を InitializationMode.WhenAvailable
に設定しておこう。
public partial class MainWindow : Window { IModuleManager _moduleManager; public MainWindow(IModuleManager moduleManager) { InitializeComponent(); _moduleManager = moduleManager; } private void Button_Click(object sender, RoutedEventArgs e) { _moduleManager.LoadModule("ModuleAModule"); } }
7.5 Modules - Xaml
ModuleA とリンクする手法、その5。
App.config と xaml を利用するパターンで、パターン1を拡張したような書き方になる。App.config はパターン1そっくり。
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="modules" type="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" /> </configSections> <startup> </startup> <modules> <module assemblyFile="ModuleA.dll" moduleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleAModule" startupLoaded="True" /> </modules> </configuration>
加えて、カタログを表す xaml を容易する。
<m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:m="clr-namespace:Prism.Modularity;assembly=Prism.Wpf"> <m:ModuleInfo ModuleName="ModuleAModule" ModuleType="ModuleA.ModuleAModule, ModuleA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </m:ModuleCatalog>
最後にコード上からカタログを読み込んでいる。Directory ではフォルダーのファイル構成で表していた部分を xaml で記述し、カタログを読み込む形になった。
public partial class App : PrismApplication { protected override Window CreateShell() { return Container.Resolve<MainWindow>(); } protected override void RegisterTypes(IContainerRegistry containerRegistry) { } protected override IModuleCatalog CreateModuleCatalog() { return new XamlModuleCatalog(new Uri("/Modules;component/ModuleCatalog.xaml", UriKind.Relative)); } }
個人的には、あまりしっくりとはこなかった。多くの場合では冗長ではないか、というのが一番のところ。
まとめ
Modules はメインのエントリープロジェクトに View や VM を読み込む手法。ウィンドウと疎結合の関係を保つことができる。
読み込む手法の例:
- App.config から読み込む
- コード上で AddModule して読み込む
- ディレクトリー上(フォルダー)から、まとめて読み込む
- カタログにマニュアルで追加して読み込む(細かくコーディングしやすい)
- Xaml を容易して 3 に近い手法で読み込む