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 やフレームワークを使い倒して、如何に楽に実装するかが肝になりそうです。