MVVM モデルで ViewModel から View を操作する場合は Messenger という仕組みを使う…らしいのですが、理解が難しかったので、自分なりにまとめなおしてみます。
結論的には、以下の 2 の内容か、頑張っても 5 の内容で勝負であって、Messenger の実装はやりすぎという感じがします。まあ私に取り回す実力がないだけかもしれませんが(汗
- まず「ViewModel から View を操作したいケース」とは何か
たとえば、WinForm とかで以下のようなコードを書きたいケースです。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click MsgBox("Hello") End Sub
簡単ですね。
- 上記を ViewModel から実行するとどうなるか
なにも考えなければ、以下で OK のはずです。
Class MainWindow Private _viewModel As ViewModel = New ViewModel Private Sub Button_Click(sender As Object, e As RoutedEventArgs) ' 操作類は、viewModel 側のメソッドで実行させたいので、転送する _viewModel.ShowMsgBox() End Sub End Class Class ViewModel Public Sub ShowMsgBox() MsgBox("Hello") End Sub End Class
- 疑問1:ViewModel から 直接 MsgBox を実行していいのか?
MVVM モデルではこれを嫌っているようです。予想される問題は以下の2つ。
- ViewModel に UI(入力操作) が含まれていることで、単体テストが難しくなること。
- MsgBox という命令自体が View そのものであること。
その他には「デザイナーとプログラマーの分業が難しい」とかありそうですが、とりあえず無視とします。
- 疑問2:そもそも View(コードビハインド) に直接 MsgBox を書いてはだめなのか?
嫌われる理由としては、View にビジネスロジック(MVVM でいう ViewModel 部分 / MVC でいう Controller 部分) が混じってしまう、という点です。
# MsgBox 自体は View ですが「MsgBox を表示する」はビジネスロジックです。 - ではどうするか?
たとえば、上記2.で書いたコードを以下のように書き換えます。
Class MainWindow Private WithEvents _viewModel As ViewModel = New ViewModel Private Sub Button_Click(sender As Object, e As RoutedEventArgs) ' 操作類は、viewModel 側のメソッドで実行させたいので、転送する _viewModel.ShowMsgBox() End Sub Private Sub _viewModel_RequestShowMsgBox() Handles _viewModel.RequestShowMsgBox MsgBox("Hello") End Sub End Class Class ViewModel Public Event RequestShowMsgBox() Public Sub ShowMsgBox() ' MsgBoxの表示をお願いする。 RaiseEvent RequestShowMsgBox() End Sub End Class
こうすることで、ViewModel から View のコードは排除することができます。
これで View のことは View側 (コードビハインド側) に集約できます。つまり、MsgBox を デザインがリッチなダイアログに変更したいといった場合も ViewModel は変更せず、View 側の変更だけで対応可能です。また、ViewModel はユーザー入力から切り離されているので、直接単体テストすることが可能です。
今回の例では実装されていませんが、ユーザー入力の入力内容を ViewModel に戻しているケースも、ViewModel からのイベントを受けるテストスタブを作ることで、単体テストが可能です。 - MVVM モデルにおける Messenger クラスとは
要するに、上記のような仕組みを用意するための、一連のクラス群です。
上記 5 では、イベントをコードビハインドに通知していますが、本家(?)のメッセンジャーはもうちょっと複雑です。
View 部分の処理 (今回のでは MsgBox 相当) をコマンドオブジェクトとしてラップし、それをコードビハインドではなく、直接 XAML に記載する手法のようです。 - Messenger クラスを実装した場合はどうなるか
こんな感じ。
しかし、共通部品として隠蔽/再利用できるものがあるとしても、これだけのコードを書く必要があるというのはちょっと…(汗コード部分
Class MainWindow Private _viewModel As ViewModel = New ViewModel Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded Me.DataContext = _viewModel End Sub Private Sub Button_Click(sender As Object, e As RoutedEventArgs) _viewModel.ShowMsgBox() End Sub End Class Class ViewModel Public Property RequestShowMsgBox As Messenger = New Messenger Public Sub ShowMsgBox() ' MsgBoxの表示をお願いする。 Me.RequestShowMsgBox.Raise( New Message("Hello"), Sub(args) MsgBox(args.Response, , "view操作後の処理") End Sub ) End Sub End Class Class ConfirmAction ' 参照設定: C:Program Files (x86)Microsoft SDKsExpressionBlend.NETFrameworkv4.5LibrariesSystem.Windows.Interactivity.dll Inherits System.Windows.Interactivity.TriggerAction(Of DependencyObject) Protected Overrides Sub Invoke(parameter As Object) Dim args = CType(parameter, MessageEventArgs) MsgBox(args.Message.Body) args.Message.Response = "View操作後のReturn" args.Callback()(args.Message) End Sub End Class ' ' ' これより以下は、たぶん使いまわし(隠蔽)可能 Class Message Public Property Body As Object Public Property Response As Object Public Sub New(body As Object) Me.Body = body End Sub End Class Class MessageEventArgs Inherits EventArgs Public Property Message As Message Public Property Callback As Action(Of Message) Public Sub New(message As Message, callback As Action(Of Message)) Me.Message = message Me.Callback = callback End Sub End Class Class Messenger Public Event Raised As EventHandler(Of MessageEventArgs) Public Sub Raise(message As Message, callback As Action(Of Message)) RaiseEvent Raised(Me, New MessageEventArgs(message, callback)) End Sub End Class Class MessageTrigger ' 参照設定: C:Program Files (x86)Microsoft SDKsExpressionBlend.NETFrameworkv4.5LibrariesSystem.Windows.Interactivity.dll Inherits System.Windows.Interactivity.EventTrigger Protected Overrides Function GetEventName() As String Return "Raised" End Function End Class
XAML部分
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local="clr-namespace:WpfApplication1" Title="MainWindow" Height="100" Width="300"> <i:Interaction.Triggers> <local:MessageTrigger SourceObject="{Binding Path=RequestShowMsgBox}"> <local:ConfirmAction /> </local:MessageTrigger> </i:Interaction.Triggers> <Grid> <Button Content="Button" Click="Button_Click"/> </Grid> </Window>
nx802
VB入門者です。
MVVMの意味が理解できず調べていてたどり着きました。
この記事のお陰でやっと理解できました。
といっても理解できたのは5.までで,それ以上はちょっと難しかったです。XAMLを勉強したらまた出直してきます
Link | 2015年1月8日 09:50