WPF + Caliburn.Micro + Castle.Windsorモゞュラヌアプリケヌション

たず、この蚘事でモゞュラヌアプリケヌションが䜕を意味するのかを刀断したす。 したがっお、いわゆるアプリケヌションで構成されるアプリケヌションなどのモゞュヌル匏アプリケヌションを怜蚎したす。 シェルずプラグむンのセット。 それらの間には盎接的な関係はなく、契玄を通じおのみです。 これにより、各コンポヌネントを個別に倉曎したり、構成を倉曎したりできたす。 私がいない人は皆、モゞュヌル匏アヌキテクチャの利点を知っおいるず思いたす。



画像



おそらく、このアヌキテクチャでWPFアプリケヌションを䜜成するための最も有名なフレヌムワヌクはPrismです。 この蚘事では、比范分析を行いたせん。 Prismの䜿甚経隓はありたせん。 チュヌトリアルを読んだ埌、すべおのリヌゞョン、mef、およびその他のアヌティファクトを含むPrismは、非垞に耇雑に思えたした。 Prismを知っおいる読者が、私が間違っおいるこずず、このフレヌムワヌクの利点を合理的に指摘しおくれたら、感謝したす。



この蚘事では、これらのツヌルを䜿甚した単玔なモゞュヌル匏アプリケヌションの開発に぀いお説明したす。



Caliburn.Micro



Caliburn.Microは、ViewおよびViewModelの蚘述を倧幅に簡玠化するフレヌムワヌクです。 実際、圌自身が呜名芏則に基づいおバむンダヌを䜜成しおいるため、開発者は手䜜業でバむンダヌを䜜成する必芁がなく、コヌドをより小さく、よりきれいにするこずができたす。 ここに圌らのサむトからのいく぀かの䟋がありたす



<ListBox x:Name="Products" />
      
      





 public BindableCollection<ProductViewModel> Products { get; private set; } public ProductViewModel SelectedProduct { get { return _selectedProduct; } set { _selectedProduct = value; NotifyOfPropertyChange(() => SelectedProduct); } }
      
      





このXAMLでは、ItemSourceたたはSelectedItemのいずれも指定しおいたせん。



 <StackPanel> <TextBox x:Name="Username" /> <PasswordBox x:Name="Password" /> <Button x:Name="Login" Content="Log in" /> </StackPanel>
      
      





 public bool CanLogin(string username, string password) { return !String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password); } public string Login(string username, string password) { ... }
      
      





コマンドずCommandParameterはありたせん。



必芁に応じお、契玄を再定矩できたす。

もちろん、Caliburn.Microにはもっずやるこずがありたす。 さらに怜蚎したす。残りはドキュメントに蚘茉されおいたす。



Castle.windsor



Castle.Windsorは、.netで最も有名で最も機胜的なDIコンテナヌの1぀です読者はDIおよびIoCを認識しおいるず想定されたす。 はい、Caliburn.Microは、他の倚くのフレヌムワヌクず同様に、独自のDIコンテナSimpleContainerを備えおおり、さらなる䟋ずしおは、その機胜で十分です。 しかし、より耇雑なタスクの堎合は適切ではない可胜性があるため、䟋ずしおCastle.Windsorを䜿甚しお任意のコンテナを䜿甚する方法を瀺したす。



挑戊する



䟋ずしお、単玔なモゞュヌル匏アプリケヌションを䜜成するプロセスを怜蚎するこずを提案したす。 その䞻芁郚分-シェル-はりィンドりであり、その巊偎にListBoxメニュヌがありたす。 メニュヌ項目を遞択するず、察応するフォヌムが右偎に衚瀺されたす。 メニュヌは、ロヌド時たたは操䜜䞭にモゞュヌルで満たされたす。 モゞュヌルは、シェルの開始時ず操䜜䞭の䞡方でロヌドできたすたずえば、モゞュヌルは必芁に応じお他のモゞュヌルをロヌドできたす。



契玄



すべおのコントラクトは、コントラクトアセンブリに含たれ、シェルずモゞュヌルが参照する必芁がありたす。 タスクに基づいお、シェルのコントラクトを䜜成したす。



 public interface IShell { IList<ShellMenuItem> MenuItems { get; } IModule LoadModule(Assembly assembly); }
      
      





  public class ShellMenuItem { public string Caption { get; set; } public object ScreenViewModel { get; set; } }
      
      





ここではすべおが明確だず思いたす。 シェルを䜿甚するず、モゞュヌルでメニュヌを管理したり、プロセスでモゞュヌルをロヌドしたりできたす。 メニュヌ項目には衚瀺名ずViewModelが含たれ、そのタむプは絶察に任意です。 りィンドりの右偎でメニュヌ項目を遞択するず、このViewModelに察応するビュヌが衚瀺されたす。 適切なビュヌを刀断する方法は Caliburn.Microがこれを凊理したす。 このアプロヌチはViewModel-firstず呌ばれたす。コヌドではビュヌモデルを操䜜し、ビュヌの䜜成は背景にフェヌドむンし、フレヌムワヌクの手に委ねられるためです。 詳现は以䞋の通りです。



モゞュヌルコントラクトは非垞に単玔に芋えたす。



 public interface IModule { void Init(); }
      
      





Initメ゜ッドは、モゞュヌルのロヌドを開始したパヌティを呌び出したす。



アセンブリがプロゞェクトで眲名され、通垞は倧芏暡プロゞェクトである堎合、シェルずモゞュヌルが同じバヌゞョンのコントラクトを持぀アセンブリを䜿甚するこずを確認する必芁があるこずに泚意するこずが重芁です。



シェルを䜿い始める



タむプWPFアプリケヌションのプロゞェクトを䜜成したす。 次に、Caliburn.MicroずCastle.WIndsorをプロゞェクトに接続する必芁がありたす。 これを行う最も簡単な方法は、NuGetを䜿甚するこずです。



PM> Install-Package Caliburn.Micro -Version 2.0.2

PM> Install-Package Castle.Windsor









ただし、アセンブリをダりンロヌドするか、自分で組み立おるこずができたす。 次に、プロゞェクトに2぀のフォルダヌ、ViewsずViewModelsを䜜成したす。 ViewModelsフォルダヌで、ShellViewModelクラスを䜜成したす。 INotifyPropertyChangedを実装しないように、Caliburn.MicroのPropertyChangedBaseから継承したす。 これは、シェルのメむンりィンドりのビュヌモデルになりたす。



 class ShellViewModel: PropertyChangedBase { public ShellViewModel() { MenuItems = new ObservableCollection<ShellMenuItem>(); } public ObservableCollection<ShellMenuItem> MenuItems { get; private set; } private ShellMenuItem _selectedMenuItem; public ShellMenuItem SelectedMenuItem { get { return _selectedMenuItem; } set { if(_selectedMenuItem==value) return; _selectedMenuItem = value; NotifyOfPropertyChange(() => SelectedMenuItem); NotifyOfPropertyChange(() => CurrentView); } } public object CurrentView { get { return _selectedMenuItem == null ? null : _selectedMenuItem.ScreenViewModel; } } }
      
      





メむンりィンドりMainWindow自䜓がビュヌにコピヌされ、ShellViewに名前が倉曎されたす。 ファむルだけでなく、名前空間を持぀クラスの名前も忘れないでください。 ぀たり Shell.MainWindowsクラスの代わりに、Shell.Views.ShellViewが必芁です。 これは重芁です。 そうしないず、Caliburn.Microは、以前に䜜成されたモデルず䞀臎するのはこのビュヌであるず刀断できたせん。 前述のように、Caliburn.Microは呜名芏則に䟝存しおいたす。 この堎合、ビュヌモデルのクラス名から「モデル」ずいう単語が削陀され、察応するビュヌのクラス名が取埗されたすShell.ViewModels.ShellViewModel-Shell.Views.ShellView。 Viewの圹割は、Windows、UserControl、Pageです。 モゞュヌルでは、UserControlを䜿甚したす。

メむンりィンドりのXAMlレむアりトは次のようになりたす。



 <Window x:Class="Shell.Views.ShellView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <ListBox x:Name="MenuItems" DisplayMemberPath="Caption" Grid.Column="0"/> <ContentControl x:Name="CurrentView" Grid.Column="1"/> </Grid> </Window>
      
      





Caliburn.Microを起動したす



これを行うには、最初に最小限のコンテンツでBootstraperクラスを䜜成したす。



 public class ShellBootstrapper : BootstrapperBase { public ShellBootstrapper() { Initialize(); } protected override void OnStartup(object sender, StartupEventArgs e) { DisplayRootViewFor<ShellViewModel>(); } }
      
      





BootstrapperBaseから継承する必芁がありたす。 OnStartupメ゜ッドは、プログラムの起動時に呌び出されたす。 DisplayRootViewForは、デフォルトではビュヌモデルクラスのむンスタンスをデフォルトのコンストラクタヌで䜜成し、䞊蚘のアルゎリズムを䜿甚しお察応するビュヌを怜玢し、衚瀺したす。



これを機胜させるには、アプリケヌションぞの゚ントリポむント-App.xamlを線集する必芁がありたす。



 <Application x:Class="Shell.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:shell="clr-namespace:Shell"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary> <shell:ShellBootstrapper x:Key="bootstrapper" /> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
      
      





StartupUriブヌトストラップに割り圓おられおいるを削陀し、ブヌトストラップをリ゜ヌスに远加したした。 このようなネストは、そのようなものではありたせん。そうでなければ、プロゞェクトは組み立おられたせん。



これで、アプリケヌションが起動するず、ブヌトストラップが䜜成され、OnStartupが呌び出され、ビュヌモデルに関連付けられたメむンアプリケヌションりィンドりが衚瀺されたす。



ビュヌモデルの䜜成に泚意しおください。 デフォルトのコンストラクタヌによっお䜜成されたす。 そしお、もし圌女が持っおいなければ 圌女が他の゚ンティティに䟝存しおいる堎合、たたは他の゚ンティティが圌女に䟝存しおいたすか Castle.WindsorのDIコンテナを実行する時が来たず思いたす。



Castle.Windsorを起動する



ShellInstallerクラスを䜜成したす。



 class ShellInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container .Register(Component.For<IWindsorContainer>().Instance(container)) .Register(Component.For<ShellViewModel>() /*.LifeStyle.Singleton*/); } }
      
      





その䞭で、流れるような構文を䜿甚しお、すべおのコンポヌネントをコヌドに登録したす。 xmlを䜿甚しおこれを行うこずができたす。サむトのドキュメントを参照しおください。 これたでのずころ、メむンりィンドりのビュヌモデルずいう1぀のコンポヌネントがありたす。 これをシングルトンずしお登録したすこれはデフォルトでLifeStyleであるため、明瀺的に指定するこずはできたせん。 たた、コンテナにアクセスできるように、コンテナ自䜓も登録したす。 今埌の予定-モゞュヌルをロヌドするずきにこれが必芁になりたす。



次に、ブヌトストラップに倉曎を加えたす。



 public class ShellBootstrapper : BootstrapperBase { private readonly IWindsorContainer _container = new WindsorContain-er(); public ShellBootstrapper() { Initialize(); } protected override void OnStartup(object sender, StartupEventArgs e) { DisplayRootViewFor<ShellViewModel>(); } protected override void Configure() { _container.Install(new ShellInstaller()); } protected override object GetInstance(Type service, string key) { return string.IsNullOrWhiteSpace(key) ? _container.Kernel.HasComponent(service) ? _container.Resolve(service) : base.GetInstance(service, key) : _container.Kernel.HasComponent(key) ? _container.Resolve(key, service) : base.GetInstance(service, key); } }
      
      





コンテナを䜜成したす。 再定矩されたConfigureメ゜ッドでは、むンストヌラヌを䜿甚したす。 GetInstanceメ゜ッドをオヌバヌラむドしたす。 基本的な実装では、デフォルトのコンストラクタヌを䜿甚しおオブゞェクトを䜜成したす。 コンテナからオブゞェクトを取埗しようずしたす。



モゞュヌルずの盞互䜜甚



たず、モゞュヌルをロヌドする方法を孊ぶ必芁がありたす。 そしお、このために、モゞュヌルずは䜕かを決めたしょうか



モゞュヌルこの堎合は、必芁な機胜を実装するクラスのセットを含むアセンブリです。 これらのクラスの1぀は、IModuleコントラクトを実装する必芁がありたす。 さらに、シェルず同様に、モゞュヌルには、DIコンテナヌにモゞュヌルのコンポヌネントクラスを登録するむンストヌラヌが必芁です。



次に、ブヌトロヌダヌの実装を開始したしょう。 ダりンロヌドは、シェルの起動時に呌び出され、プロセス内で呌び出すこずもできるため、別のクラスを䜜成したす。



 class ModuleLoader { private readonly IWindsorContainer _mainContainer; public ModuleLoader(IWindsorContainer mainContainer) { _mainContainer = mainContainer; } public IModule LoadModule(Assembly assembly) { try { var moduleInstaller = FromAssembly.Instance(assembly); var modulecontainer = new WindsorContainer(); _mainContainer.AddChildContainer(modulecontainer); modulecontainer.Install(moduleInstaller); var module = modulecontainer.Resolve<IModule>(); if (!AssemblySource.Instance.Contains(assembly)) AssemblySource.Instance.Add(assembly); return module; } catch (Exception ex) { //TODO: good exception handling return null; } } }
      
      





シェルコンテナヌがデザむナヌを通じお衚瀺されたすこれのために特別に登録したしたか。 LoadModuleメ゜ッドでは、モゞュヌルアセンブリからむンストヌラヌを取埗したす。 ロヌド可胜なモゞュヌルのコンポヌネント甚に個別のコンテナを䜜成したす。 シェルコンテナに関連しお子ずしお登録したす。 むンストヌラヌモゞュヌルを䜿甚したす。 IModuleのむンスタンスを返そうずしおいたす。 Caliburn.Microにアセンブリを通知し、アセンブリ内のコンポヌネントの呜名芏則を適甚したす。



たた、ShellInstallerにモゞュヌルロヌダヌを登録するこずを忘れないでください。



 .Register(Component.For<ModuleLoader>()
      
      





「子コンテナ」に぀いお少し。 䞀番䞋の行は、そのすべおのコンポヌネントが、独自のコンテナに加えお、芪コンテナのコンポヌネントを「芋る」こずですが、その逆はそうではありたせん。 異なる子コンテナのコンポヌネントもお互いに぀いお䜕も知りたせん。 私たちはシェルをモゞュヌルから分離し、モゞュヌルを互いに分離したすが、シェルからは分離したせん-圌らはそれを芋たす。



次に、モゞュヌルがシェルにアクセスするためのIShellコントラクトを実装したす。



  class ShellImpl: IShell { private readonly ModuleLoader _loader; private readonly ShellViewModel _shellViewModel; public ShellImpl(ModuleLoader loader, ShellViewModel shellViewModel) { _loader = loader; _shellViewModel = shellViewModel; } public IList<ShellMenuItem> MenuItems { get { return _shellViewModel.MenuItems; } } public IModule LoadModule(Assembly assembly) { return _loader.LoadModule(assembly); } }
      
      





登録したす。



 .Register(Component.For<IShell>().ImplementedBy<ShellImpl>())
      
      







次に、シェルの起動時にモゞュヌルをロヌドする必芁がありたす。 そしお、圌らはどこから来るのでしょうか この䟋では、シェルはShell.exeの隣にモゞュヌルを持぀アセンブリを探したす。



この機胜は、OnStartupメ゜ッドで実装する必芁がありたす。



  protected override void OnStartup(object sender, StartupEventArgs e) { var loader = _container.Resolve<ModuleLoader>(); var exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var pattern = "*.dll"; Directory .GetFiles(exeDir, pattern) .Select(Assembly.LoadFrom) .Select(loader.LoadModule) .Where(module => module != null) .ForEach(module => module.Init()); DisplayRootViewFor<ShellViewModel>(); }
      
      





すべお、シェルの準備ができたした



モゞュヌルを曞く



ロヌド時のテストモゞュヌルは、シェルメニュヌに2぀のアむテムを远加したす。 最初のアむテムは、碑文のある非垞にシンプルなフォヌムを右偎に衚瀺したす。 2番目のボタンはボタンのあるフォヌムで、開いたファむル遞択ダむアログでモゞュヌルを遞択しおモゞュヌルをロヌドできたす。 呜名芏則に埓っお、2぀のビュヌずViewModelsフォルダヌを䜜成したす。 その埌、それらを埋めたす。



最初のビュヌずビュヌモデルは簡単です。



 <UserControl x:Class="Module.Views.FirstView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="60">Hello, I'm first !</TextBlock> </Grid> </UserControl>
      
      





  class FirstViewModel { }
      
      





2番目のビュヌも難しくありたせん。



 <UserControl x:Class="Module.Views.SecondView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Button x:Name="Load" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="50">Load Module</Button> </Grid> </UserControl>
      
      





2番目のビュヌモデルでは、遞択したモゞュヌルをロヌドしたす。



 class SecondViewModel { private readonly IShell _shell; public SecondViewModel(IShell shell) { _shell = shell; } public void Load() { var dlg = new OpenFileDialog (); if (dlg.ShowDialog().GetValueOrDefault()) { var asm = Assembly.LoadFrom(dlg.FileName); var module = _shell.LoadModule(asm); if(module!=null) module.Init(); } } }
      
      





IModuleコントラクトを実装したす。 Initメ゜ッドで、アむテムをシェルメニュヌに远加したす。



 class ModuleImpl : IModule { private readonly IShell _shell; private readonly FirstViewModel _firstViewModel; private readonly SecondViewModel _secondViewModel; public ModuleImpl(IShell shell, FirstViewModel firstViewModel, SecondViewModel secondViewModel) { _shell = shell; _firstViewModel = firstViewModel; _secondViewModel = secondViewModel; } public void Init() { _shell.MenuItems.Add(new ShellMenuItem() { Caption = "First", ScreenViewModel = _firstViewModel }); _shell.MenuItems.Add(new ShellMenuItem() { Caption = "Second", ScreenViewModel = _secondViewModel }); } }
      
      





そしお最埌の仕䞊げはむンストヌラヌです。



 public class ModuleInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container .Register(Component.For<FirstViewModel>()) .Register(Component.For<SecondViewModel>()) .Register(Component.For<IModule>().ImplementedBy<ModuleImpl>()); } }
      
      





できた



゜ヌス-gitハブ 。



おわりに



この蚘事では、Castle.WindwsorおよびCaliburn.Microフレヌムワヌクを䜿甚しお、最も単玔なモゞュラヌWPFアプリケヌションを䜜成するこずを怜蚎したした。 もちろん、倚くの偎面がカバヌされおいなかった、いく぀かの詳现が省略されおいた、など、そうでなければ本は刀明し、そうではなかったでしょう。 さらに詳现な情報は、公匏リ゜ヌスだけでなく、芋぀けるこずができたす。



すべおの質問に喜んでお答えしたす。



ご枅聎ありがずうございたした



All Articles