VB で MVVM にチャレンジしてみた
いまさらですが、今年に入って知った最大の成果は、
WPF なら MVVM パターン使うの当然じゃん
ということです。でもこの話、一部じゃまだまだ異論があるようで、ネットでちょっと調べるみると
- 単体テストしないなら MVVM 不要じゃん。
- 直感的に理解できねえし納期ないから従来どおりやるわ。
- デザイナなんていないから、分離にこだわる必要なし。
- 返ってコード増えなくね?
等、実に様々な意見があるようです。
実際いま私が携わっているプロジェクトも WindowsForms からの移行に加え、ActiveX コントロールも使い倒してるので、MVVM パターンの導入はかなり厳しい感があります。
しかし、だからと言ってMVVM を習得せずにいると、ますます時代に取り残されそうな感があるため、今回少し MVVM パターンを齧ってみました。
MVVM については様々なサイトやブログで MVVM が語られてますが、簡単なサンプルですぐ習得できそうなのが、かるあさんが書かれた以下の記事です。
MVVM Light Toolkitを使ってみよう。その2 MVVMの復習
MVVM Light Toolkit の名前がタイトルにありますが、MVVM Light Toolkitは使ってません。サンプルも簡単なため、たいへん判り易い記事に仕上がってます。
記事見ながらひととおり実装してみて実感したのが、なるほどこれなら View に全くコード書かずに済む!ということです。View と ViewModel・Model が綺麗に分離されてるので、これなら単体テストを実行しやすく、分散開発もし易いというのも頷けます。
ただしこの記事は当然ながら C#。巷を見ると MVVM はどこもかしこも C# が中心で、これでは VB ユーザーがますます取り残されるのはいうまでもありません。ついでに C# から VB に移行する中により深く MVVM が習得できそうだというわけで、かるあさんの記事を参考に、VB で MVVM パターンを実装してみました。
話を判り易くするために、クラスのプロパティを一つだけ用意し、Command も一つだけにして試してみました。何かの参考になれば幸いです。
まず Person クラスを用意します。
' Person.vb Option Explicit On Option Strict On Public Class Person Public Property Name As String End Class
次に汎用 RelayCommand クラスを用意します。
' RelayCommand.vb Option Explicit On Option Strict On Imports System.ComponentModel Imports System.Windows.Input Public Class RelayCommand Implements ICommand Private _canExecuteAction As Func(Of Object, Boolean) Private _executeAction As Action(Of Object) Public Sub New(ByVal executeAction As Action(Of Object), ByVal canExecuteAction As Func(Of Object, Boolean)) Me._executeAction = executeAction Me._canExecuteAction = canExecuteAction End Sub Public Function CanExecute(ByVal parameter As Object) As Boolean _ Implements ICommand.CanExecute Return _canExecuteAction(parameter) End Function Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) _ Implements ICommand.CanExecuteChanged Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute _executeAction(parameter) End Sub End Class
ViewModel です。
' PersonViewModel.vb Option Explicit On Option Strict On Imports System.ComponentModel Imports System.Windows.Input Public Class PersonViewModel Implements INotifyPropertyChanged Private _person As Person Public Sub New() _person = New Person() _person.Name = "ひらぽん" Me.OnOK = New RelayCommand(AddressOf OK, Function(parameter) True) End Sub Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) _ Implements INotifyPropertyChanged.PropertyChanged Public Property OnOK As RelayCommand Public Property Person() As Person Get Return _person End Get Set(ByVal value As Person) If (_person.Equals(value)) Then Return End If _person = value RaisePropertyChanged("Name") End Set End Property Public Sub RaisePropertyChanged(ByVal propertyName As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) End Sub Private Sub OK(ByVal parameter As Object) MessageBox.Show(_person.Name) End Sub End Class
んで XAML。
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:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="169" Width="300" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"> <Window.Resources> <local:PersonViewModel x:Key="viewModel" /> </Window.Resources> <Grid DataContext="{StaticResource viewModel}"> <TextBox Height="22" HorizontalAlignment="Left" Margin="12,39,0,0" Name="TextBox1" VerticalAlignment="Top" Width="254" Text="{Binding Person.Name}"/> <Button Content="OK" Height="24" HorizontalAlignment="Left" Margin="186,94,0,0" Name="buttonOk" VerticalAlignment="Top" Width="80" Command="{Binding Path=OnOK}"/> </Grid> </Window>
起動した時点では、TextBox の初期値は「ひらぽん」になってます。TextBox を任意の値に変更して OK ボタンをクリックすると、バインディングで変更された値になっているのが確認できると思います。
ここまで実装した感想ですが、確かに綺麗にUIとコードを分離できるのですがプロパティが多くになるにつれ、保守は楽になるものの実装が面倒になるのも事実です。だからこそ先日のわんくま勉強会でえムナウさんが言ってたとおり、T4 やフレームワークを使い倒して、如何に楽に実装するかが肝になりそうです。