0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Visual Basic でPrismする

Last updated at Posted at 2022-01-22

はじめに

Prismと言えば、MVVMなどのデザインパターンの実装を提供する超メジャーなフレームワークだが、これをVisual Basicで使用する方法をまとめてみた。

なお、ここで書いたPrism自体の基本的な知識は、以下で得ました。

前者はC#による講座、後者はPrismご本家で、当然ながらC#による実装です。念のため。

WPFプロジェクトの作成

先ずは、Visual Studio 2022(.NET6)のVisual Basic、WPFアプリケーションでプロジェクトを作成する。ここではプロジェクト名を、「HogeApp」とする。
image.png
C#の場合、VS 2019では拡張機能でC#用のPrismテンプレートをプロジェクトテンプレートに追加できたが、2022ではまだ未対応のようだ。(2022年1月21日時点。使いたければ、GitHubのPrismLibrary/Prism.Templatesにある)
いずれにしてもVisual Basic用のPrismテンプレートは以前から無かった:sob:

重要な、フォルダ構成と名前付け

全体としては以下のようなフォルダ構成になる。
image.png
これが重要で、ViewとViewModelは必ずこの通りのフォルダ名・フォルダ構成でファイルを配置する。
各ViewModelの名前は、

View名 + "ViewModel"

とする。
フォルダ名は、ViewsおよびViewModelsと複数形であることに注意する。
後述するViewModelLocatorがViewとViewModelの関連付けを自動的に行うためである。
WPFプロジェクトに既にあるMainWindow.xamlは、ドラッグしてViewsフォルダに移動させる。(C#と違いデフォルトで名前空間が無いので、単にドラッグして移動させても問題ない)

Prism.Unityのインストール

Prism本体は、NuGetで「Prism.Unity」を検索してインストールする。(執筆時バージョン8.1.97)
image.png
以下画像のとおり、必要なアセンブリは全てここに含まれているようで、インストールはこれだけでOK:ok_hand:
image.png

Application.xaml.vbの書き換え

PrismApplicationを継承するようにApplicationクラスを書き換える。PrismApplicationは抽象クラスなのでMustOverrideメンバのオバーライドが必要だ。

Imports Prism.Unity

Public Class Application : Inherits PrismApplication

End Class

ここまで書くとクラス名に赤く波線がつくので、そこのところにカーソルを合わせて Ctrl + [ . ] を押し「抽象クラスの実装」を選択する。
image.png
GitHubを見ると、PrismApplicationクラスPrismApplicationBaseクラスを継承している。PrismApplicationBaseクラスは更にApplicationクラスを継承している。
え?ApplicationApplicationを継承?無限ループか⁈
これは、

起源となるApplicationの名前空間は「System.Windows

継承するApplicationの名前空間は「HogeApp

となり、Applicationという名前は一緒だが、名前空間が違うので違いを判別できるのだ。

ここではとりあえずCreateShellを下記のようにオーバーライドする。

Application.xaml.vb
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"を消す。
Application.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をそれぞれRegionARegionBDialogCと名付けてプロジェクトに追加する。

RegionA.xaml
<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>
RegionB.xaml
<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>
DialogC.xaml
<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を一つ持ち、クラス名と表示コントロールの色設定が違うだけの単純なもの。
RegionARegionBは画面遷移、DialogCはダイアログボックスのためのViewとなる。

ViewModelの作成

冒頭で書いた通り、ViewModelは、関連付けたいViewの名前の末尾に「ViewModel」を付けて名づける。これはView名からViewModel名を推論するための決まりである。ViewModelLocationProviderクラスに関連付けのロジックがある。

RegionARegionBはMainWindow内の描画領域に表示するView。以下は各々のViewModelである。

RegionAViewModel.vb
Imports Prism.Mvvm

Public Class RegionAViewModel
    Public Property Text As String = "領域A"
End Class
RegionBViewModel.vb
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 + [ . ] を押し「インターフェイスを実装します」を選択する。
image.png
そして、以下のように書き換える。

DialogCViewModel.vb
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="描画領域"を追記する。
MainWindow.xaml
<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の記述において重要なポイントは、コンストラクタでIRegionManagerIDialogServiceを受け取っているところである。
これらのインスタンスをフィールドに保持してRegionの描画や、Dialogの生成を行わせる。

MainWindowViewModel.vb
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.xamlContentControlタグ内のRegionNameで設定した文字列と一致させる。

再度、Application.xaml.vbに追記

最後にApplication.xaml.vbに戻り、以下のようにRegisterTypesに追記する。

Application.xaml.vb
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

アプリ動作の様子

でき上がったアプリの動きはこんな感じ。
HogeApp2.gif

おわりに

今回のプロジェクトの汎用的な部分だけを残して「テンプレートのエクスポート」をクリックすれば、プロジェクトテンプレートとして登録して再利用することができる。Visual Basic用のテンプレートが無いなら自作すればいいのだ。:muscle:
image.png
C#に比べてVisual Basic(VB.NET)の情報は年々少なくなっている。言語としての進化も止まった。
しかし、世に多く出回るC#のコードの殆どはVisual Basicで書き換えることができる。この書き換え作業は、自分にとっては楽しみであり、より深いロジック理解の助けとなっている。
今回の記事は、正にそんな楽しみから生まれたもので、見捨てられたVisual Basicでも何とかPrismを利用したいという一心から取り組んだ結果です。少しでも皆様のお役に立てれば、、、:laughing:

0
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?