はじめに
Prismと言えば、MVVMなどのデザインパターンの実装を提供する超メジャーなフレームワークだが、これをVisual Basicで使用する方法をまとめてみた。
なお、ここで書いたPrism自体の基本的な知識は、以下で得ました。
前者はC#による講座、後者はPrismご本家で、当然ながらC#による実装です。念のため。
WPFプロジェクトの作成
先ずは、Visual Studio 2022(.NET6)のVisual Basic、WPFアプリケーションでプロジェクトを作成する。ここではプロジェクト名を、「HogeApp」とする。
C#の場合、VS 2019では拡張機能でC#用のPrismテンプレートをプロジェクトテンプレートに追加できたが、2022ではまだ未対応のようだ。(2022年1月21日時点。使いたければ、GitHubのPrismLibrary/Prism.Templatesにある)
いずれにしてもVisual Basic用のPrismテンプレートは以前から無かった
重要な、フォルダ構成と名前付け
全体としては以下のようなフォルダ構成になる。
これが重要で、ViewとViewModelは必ずこの通りのフォルダ名・フォルダ構成でファイルを配置する。
各ViewModelの名前は、
View名 + "ViewModel"
とする。
フォルダ名は、Views
およびViewModels
と複数形であることに注意する。
後述するViewModelLocator
がViewとViewModelの関連付けを自動的に行うためである。
WPFプロジェクトに既にあるMainWindow.xaml
は、ドラッグしてViews
フォルダに移動させる。(C#と違いデフォルトで名前空間が無いので、単にドラッグして移動させても問題ない)
Prism.Unityのインストール
Prism本体は、NuGetで「Prism.Unity」を検索してインストールする。(執筆時バージョン8.1.97)
以下画像のとおり、必要なアセンブリは全てここに含まれているようで、インストールはこれだけでOK
Application.xaml.vbの書き換え
PrismApplication
を継承するようにApplication
クラスを書き換える。PrismApplication
は抽象クラスなのでMustOverrideメンバのオバーライドが必要だ。
Imports Prism.Unity
Public Class Application : Inherits PrismApplication
End Class
ここまで書くとクラス名に赤く波線がつくので、そこのところにカーソルを合わせて Ctrl + [ . ] を押し「抽象クラスの実装」を選択する。
GitHubを見ると、PrismApplicationクラスはPrismApplicationBaseクラスを継承している。PrismApplicationBaseクラスは更にApplication
クラスを継承している。
え?ApplicationがApplicationを継承?無限ループか⁈
これは、
起源となるApplicationの名前空間は「System.Windows
」
継承するApplicationの名前空間は「HogeApp
」
となり、Applicationという名前は一緒だが、名前空間が違うので違いを判別できるのだ。
ここではとりあえずCreateShell
を下記のようにオーバーライドする。
Imports Prism.Ioc
Imports Prism.Unity
Public Class Application : Inherits PrismApplication
Protected Overrides Sub RegisterTypes(containerRegistry As IContainerRegistry)
' Throwはとりあえず消しておく
End Sub
Protected Overrides Function CreateShell() As Window
' 以下を書く
Return Container.Resolve(Of MainWindow)
End Function
End Class
なお、Inherits PrismApplication
を書かずに、
Imports Prism.Ioc
' パーシャルクラス
Partial Public Class Application
End Class
としてもコンパイル可能だが、その場合はMustOverrideメンバを一から自分で書かなければならない。
Application.xamlの書き換え
Application.xaml
を以下のように書き換える。
-
xmlns:prism="http://prismlibrary.com/"
を追記。 -
Application
タグをprism:PrismApplication
に書き換え。 -
StartupUri="MainWindow.xaml"
を消す。
<prism:PrismApplication x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:HogeApp">
<Application.Resources>
</Application.Resources>
</prism:PrismApplication>
Viewの作成
画面遷移を確認するため、3つのUserControlをそれぞれRegionA
、RegionB
、DialogC
と名付けてプロジェクトに追加する。
<UserControl x:Class="RegionA"
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"
xmlns:local="clr-namespace:HogeApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Brown">
<TextBlock Text="{Binding Text}" FontWeight="Bold" FontSize="50" Foreground="YellowGreen"/>
</Grid>
</UserControl>
<UserControl x:Class="RegionB"
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"
xmlns:local="clr-namespace:HogeApp"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Aqua">
<TextBlock Text="{Binding TextB}" FontWeight="Bold" FontSize="50" Foreground="Red"/>
</Grid>
</UserControl>
<UserControl x:Class="DialogC"
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"
xmlns:local="clr-namespace:HogeApp"
mc:Ignorable="d"
Height="100" Width="350"
d:DesignHeight="100" d:DesignWidth="350">
<Grid Background="Pink">
<TextBlock Text="{Binding Text}" FontWeight="Bold" FontSize="50" Foreground="Purple"/>
</Grid>
</UserControl>
3つのUserControlはTextBlockを一つ持ち、クラス名と表示コントロールの色設定が違うだけの単純なもの。
RegionA
とRegionB
は画面遷移、DialogC
はダイアログボックスのためのViewとなる。
ViewModelの作成
冒頭で書いた通り、ViewModelは、関連付けたいViewの名前の末尾に「ViewModel」を付けて名づける。これはView名からViewModel名を推論するための決まりである。ViewModelLocationProviderクラスに関連付けのロジックがある。
RegionA
とRegionB
はMainWindow内の描画領域に表示するView。以下は各々のViewModelである。
Imports Prism.Mvvm
Public Class RegionAViewModel
Public Property Text As String = "領域A"
End Class
Imports Prism.Mvvm
Public Class RegionBViewModel
Public Property Text As String = "領域B"
End Class
DialogC
はダイアログボックスのためのView。ダイアログ用のViewModelはIDialogAware
インターフェイスの実装が必要で、少し複雑になる。
Imports Prism.Mvvm
Imports Prism.Services.Dialogs
Public Class DialogCViewModel : Inherits BindableBase : Implements IDialogAware
End Class
ここまで書いたら、IDialogAware
に赤い波線が現れるので、カーソルを合わせて Ctrl + [ . ] を押し「インターフェイスを実装します」を選択する。
そして、以下のように書き換える。
Imports Prism.Mvvm
Imports Prism.Services.Dialogs
Public Class DialogCViewModel : Inherits BindableBase : Implements IDialogAware
Private Const _title As String = "DialogC"
Public ReadOnly Property Title As String Implements IDialogAware.Title
Get
Return _title
End Get
End Property
Public Property Text As String = "領域C"
Public Event RequestClose As Action(Of IDialogResult) Implements IDialogAware.RequestClose
Public Sub OnDialogClosed() Implements IDialogAware.OnDialogClosed
' Throwはとりあえず消しておく
End Sub
Public Sub OnDialogOpened(parameters As IDialogParameters) Implements IDialogAware.OnDialogOpened
' Throwはとりあえず消しておく
End Sub
Public Function CanCloseDialog() As Boolean Implements IDialogAware.CanCloseDialog
' 以下を追記。Falseにするとダイアログを閉じられなくなる。
Return True
End Function
End Class
MainWindow.xamlの書き換え
MainWindow.xaml
を書き換える。書き換えのポイントは以下のとおり。
-
xmlns:prism="http://prismlibrary.com/"
を追記する。 -
prism:ViewModelLocator.AutoWireViewModel="True"
を追記する。 -
Grid
内にContentControl
タグを追記する。 - 上記に
prism:RegionManager.RegionName="描画領域"
を追記する。
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:local="clr-namespace:HogeApp"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="True"
WindowStartupLocation="CenterScreen"
Title="{Binding Title}" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="画面A" Command="{Binding RegionACommand}" Width="100"/>
<Button Content="画面B" Command="{Binding RegionBCommand}" Width="100"/>
<Button Content="画面C" Command="{Binding DialogCCommand}" Width="100"/>
</StackPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="描画領域" />
</Grid>
</Window>
prism:ViewModelLocator.AutoWireViewModel="True"
これによりViewModelLocatorクラスが、各々のViewに関連付けられたViewModelを各ViewのDataContextに代入することとなる。
そして、各々のViewを呼び出すための3つのボタンを配置。
MainWindowViewModelの作成
MainWindowViewModel
の記述において重要なポイントは、コンストラクタでIRegionManager
とIDialogService
を受け取っているところである。
これらのインスタンスをフィールドに保持してRegionの描画や、Dialogの生成を行わせる。
Imports Prism.Regions
Imports Prism.Mvvm
Imports Prism.Commands
Imports Prism.Services.Dialogs
Public Class MainWindowViewModel : Inherits BindableBase
Private _regionManager As IRegionManager
Private _dialogService As IDialogService
Public Property Title As String
Public Property RegionACommand As DelegateCommand
Public Property RegionBCommand As DelegateCommand
Public Property DialogCCommand As DelegateCommand
' コンストラクタの引数でRegionManagerとDialogServiceを受け取る。
Public Sub New(regionManager As IRegionManager, dialogService As IDialogService)
_regionManager = regionManager
_dialogService = dialogService
RegionACommand = New DelegateCommand(Sub() _regionManager.RequestNavigate("描画領域", NameOf(RegionA)))
RegionBCommand = New DelegateCommand(Sub() _regionManager.RequestNavigate("描画領域", NameOf(RegionB)))
DialogCCommand = New DelegateCommand(Sub() _dialogService.ShowDialog(NameOf(DialogC)))
Title = "HogeApp"
End Sub
End Class
そしてMainWindowに配置した3つのボタンにバインドするDelegateCommandプロパティを作り、コンストラクタで設定する。引数で渡す各メソッドは、RegionManagerもしくはDialogSeviceでViewを描画・生成する。
_regionManager.RequestNavigate("描画領域", NameOf(RegionA))
RequestNavigate
メソッドの一つ目の引数("描画領域")はMainWindow.xaml
のContentControl
タグ内のRegionName
で設定した文字列と一致させる。
再度、Application.xaml.vbに追記
最後にApplication.xaml.vb
に戻り、以下のようにRegisterTypes
に追記する。
Imports Prism.Ioc
Imports Prism.Unity
Public Class Application : Inherits PrismApplication
Protected Overrides Sub RegisterTypes(containerRegistry As IContainerRegistry)
' 以下3行を追記。UnityContainerに3つのViewを登録。
' RegionとDialogでそれぞれ登録用メソッドが異なる。
containerRegistry.RegisterForNavigation(Of RegionA)
containerRegistry.RegisterForNavigation(Of RegionB)
containerRegistry.RegisterDialog(Of DialogC)
End Sub
Protected Overrides Function CreateShell() As Window
Return Container.Resolve(Of MainWindow)
End Function
End Class
アプリ動作の様子
おわりに
今回のプロジェクトの汎用的な部分だけを残して「テンプレートのエクスポート」をクリックすれば、プロジェクトテンプレートとして登録して再利用することができる。Visual Basic用のテンプレートが無いなら自作すればいいのだ。
C#に比べてVisual Basic(VB.NET)の情報は年々少なくなっている。言語としての進化も止まった。
しかし、世に多く出回るC#のコードの殆どはVisual Basicで書き換えることができる。この書き換え作業は、自分にとっては楽しみであり、より深いロジック理解の助けとなっている。
今回の記事は、正にそんな楽しみから生まれたもので、見捨てられたVisual Basicでも何とかPrismを利用したいという一心から取り組んだ結果です。少しでも皆様のお役に立てれば、、、