07-Modules-AppConfig

Description には Load modules using an App.config file と書かれています。

さて、これは何でしょうね?実行したら「View A」と表示されます。

ソリューションを見てみると、プロジェクト「Modules」と「ModuleA」があります。 おそらく「Modules」から「ModuleA」のView を使っているのでしょう。

App.config

<?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>

更新されたファイル単体では分かりにくいので、06-ViewActivationDeactivation の App.config を引用してみます。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<startup>
	</startup>
</configuration>

はい、見事に空っぽですね。

実際 ModuleA フォルダを bin を基準に掘っていくと ModuleA.dll があります。

では ModuleA にある肝心の変更部分を見てみましょう。

ModuleA\ModuleAModule.cs

using ModuleA.Views;

namespace ModuleA
{
    public class ModuleAModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry) { }
    }
}

他の変更は些細なものなので省略します。

うん…理屈はなんとなく分かったんだけど、ここまでしてプロジェクト分ける必要があるって、相当大規模なプロジェクトですよね?

少なくとも中規模クラスの開発では、使わないでしょう。 そして、この機能を使う程のプロジェクトであれば、あらかじめ設定されていると思います。

「こういう機能もある」と流して、次行きますよ次!

07-Modules-Code

Description には Load modules using code と書かれています。 これも実行してもわからない奴です。実行したら「View A」と表示されます。 まずは変更点を見てみましょう。

Views\ViewA.xaml.cs

前節と同じです。

App.xaml.cs

using System.Windows;
using Modules.Views;

namespace Modules
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    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>();
        }
    }
}

なんか ConfigureModuleCatalog オーバーライドメソッドが臭いですね?カタログに登録しているように見えます。

ざっくり「App.config を利用しないで他プロジェクトのモジュールを使う手法」と認識すればいいでしょうねコレ。 さ、次行きますよ次!

07-Modules-Directory

Description には Load modules from a directory と書かれています。 これも、もう予想通りといいますか「View A」と表示されるだけです。肝心なのは中身です。

ModuleA\ModuleAModule.cs

前節と同じです。

App.xaml.cs

using System.Windows;
using Modules.Views;

namespace Modules
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    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" };
        }
    }
}

なんか、ソース内にプロジェクトを指すようなパスが含まれてますね?

カタログに、モジュールのパスを追加する…?いや、俺が言うのも何ですが 「正気ですか?」 案件です。

ソースコード内に相対パスとはいえ、プロジェクトのパスを埋め込みますか? 俺から見たら マジックナンバーくらいにヤバい奴 ですよ!

まだ 07-Modules-Code の方が筋がいいと感じました。さ、こんなもの基本使わない方がいいです、次行きますよ次!

07-Modules-LoadManual

Description には Load modules manually using the IModuleManager と書かれています。

これも「View A」を表示するだけ…と言おうと思ったら、度肝を抜かれました。

これ 05-ViewInjection と同等機能ですよ。

Modules 内の App.xaml.cs

using System.Windows;
using ModuleA;
using Modules.Views;

namespace Modules
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    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
            });
        }
    }
}

Modules 内の Views\MainWindow.xaml

<Window
    x:Class="Modules.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/"
    Title="Shell"
    Width="525"
    Height="350">
    <DockPanel LastChildFill="True">
        <Button
            Click="Button_Click"
            Content="Load Module"
            DockPanel.Dock="Top" />
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
    </DockPanel>
</Window>

Modules 内の MainWindow.xaml.cs

using System.Windows;

namespace Modules.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly IModuleManager _moduleManager;

        public MainWindow(IModuleManager moduleManager)
        {
            InitializeComponent();
            _moduleManager = moduleManager;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            _moduleManager.LoadModule("ModuleAModule");
        }
    }
}

ModuleA 内の ViewA.xaml

前節までと同じです。

…さて、まあ先では 07-Modules-Code の方が筋が良いと言いましたが、こっちの方が全然良いでしょう。

というか、これは若干 IModuleCatalog が DI コンテナ的に動いているように見えますね。

ただ LoadModule での引数が文字列なのはいただけない…せっかくの C# なんだから、 今では CommunityToolkit.Mvvm のように C# の機能で実現する方がスマートです。

ただ、これは「モジュールを呼び出すサンプル」なので、あんまり言ってもしょうがないでしょう。

この作りには、若干の古くささを感じますけど…Prism 全盛の時期を考えると、これでも相当凄かったのでしょう。

07-Modules-Xaml

危ない危ない、このサンプルについて言及し損ねるところだった。

というか PrismのGitHub の 表に書いてないんだもの…

Module 内の ModuleCatalog.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>

正直、これを見るだけで「あ、はい…」ってなりますよね。だけど次を見てください。

Module 内の App.xaml.cs

using System.Windows;
using Modules.Views;

namespace Modules
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    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));
        }
    }
}

XAMLカタログへの登録…これは酷い、いや相対パスよりは URI 使ってるだけマシかも知れませんが…それでも酷い。

上の人が「これを使う」と決めた時以外は、関わらないのが吉な案件ですね…

一旦締めます

サンプルコードは次回から ViewModel に入るようなので、キリがいいここで締めます。

ちなみに追記しておきますと、これほど View で色々できるように作られているのは、Prism が WPF のためのフレームワークだからです。 CommunityToolkit.Mvvm ではここまでのことは できません 。逆に CommunityToolkit.Mvvm は 多くの環境で動きます

既に古くさくなった Prism が要件に合うなら止めませんが、 大規模プロジェクトでないなら…今のところ「薦める要素がほとんどない」となります。

CommunityToolkit.Mvvm で作るなら Frame 辺りを使えばいいですからね。

宿題

私が作った SimplePrismViewSample プロジェクトを MainWindow.xaml.cs 部分を含めて IModuleManager を利用して書き換えてみてください。

この宿題については、解答を示すつもりはありません。自分で手を動かして、書き換えてみてください。

WPFにおけるPrism集中講座(5) ViewModelとCommandを使う に続きます。