ViewModelからViewの操作を呼び出したいときあります。メッセージボックスとかメッセージボックスとか。
Messengerは良いものだとは思いますが、個人的にはあんまり好きくない。
以下のように書けるようにしてみました。
こんなアプリで
ViewModelはこんな感じ
using Reactive.Bindings; using System; namespace WpfApp { public class MainWindowVM { //これがViewに対する要求。メッセージボックスではなく問い合わせという形に抽象化している。 public Func<string, bool?> Ask { get; set; } public ReactiveProperty<string> Reply { get; } = new ReactiveProperty<string>(string.Empty); public void Communication() { var ret = Ask("楽しんでますか?"); Reply.Value = (ret == true) ? "よかったね" : "そうですか"; } } }
Xamlです。ここでViewModelの抽象化された要求を具体的なViewのメソッドと接続します。
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:WpfApp" xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <l:MainWindowVM/> </Window.DataContext> <c:Connection.Methods> <c:MethodCollection> <c:Method Name ="ShowMessageBox" Invoker="Ask"/> </c:MethodCollection> </c:Connection.Methods> <StackPanel> <Button Content="会話" Command="{c:Command Communication }"/> <TextBlock Text="{Binding Reply.Value}"/> </StackPanel> </Window>
一旦コードビハインド
using System.Windows; namespace WpfApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } public bool? ShowMessageBox(string text) { return MessageBox.Show(this, text, "", MessageBoxButton.YesNo) == MessageBoxResult.Yes; } } }
MethodCollectionに登録しておけば、つないでくれるという仕様にしてみました。上の書き方ならViewのShowMessageBoxにつないでくれるわけですね。ViewModelからの呼び出しはDelegateになって、直でメッセージボックス呼び出すより抽象化されてまろやかになっております。戻り値もMessageBoxResultでもいいんですけど、bool?に抽象化した方がいい感じですよね。
View以外のメソッドにもつないでみます。
はい、コードビハインド嫌なんでしょ?それにメッセージボックスとかよく使うから部品化したいですよね。こんな感じの書き方でどうでしょうか?メソッドを持っているインスタンスをTargetで指定できるようにしてみました。指定なければViewを見に行きます。これなら、部品化できますよね。これで書き直してみます。ViewModelはそのままでいきましょう。View層だけ付け替え可能なところがViewModelのメリットですね。
Xaml
<Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:WpfApp" xmlns:c="clr-namespace:VVMConnection;assembly=VVMConnection" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <l:MainWindowVM/> </Window.DataContext> <c:Connection.Methods> <c:MethodCollection> <c:Method Name ="Show" Invoker="Ask"> <c:Method.Target> <c:MessageBox Button="YesNo" Caption="質問"/> </c:Method.Target> </c:Method> </c:MethodCollection> </c:Connection.Methods> <StackPanel> <Button Content="会話" Command="{c:Command Communication }"/> <TextBlock Text="{Binding Reply.Value}"/> </StackPanel> </Window>
Viewからコードビハインドはなくなりました。
using System.Windows; namespace WpfApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
MessageBoxはこんな感じで部品化します。特にインターフェイスを継承したりしなくてもいいようにしてみました。
using System.Windows; namespace VVMConnection { public class MessageBox { public string MessageBoxText { get; set; } = string.Empty; public string Caption { get; set; } = string.Empty; public MessageBoxButton Button { get; set; } = MessageBoxButton.OK; public MessageBoxImage Icon { get; set; } = MessageBoxImage.None; public MessageBoxResult DefaultResult { get; set; } = MessageBoxResult.None; public MessageBoxOptions Options { get; set; } = MessageBoxOptions.None; public bool? Show() { return Show(MessageBoxText); } public bool? Show(string messageBoxText) { switch (System.Windows.MessageBox.Show(messageBoxText, Caption, Button, Icon, DefaultResult, Options)) { case MessageBoxResult.OK: case MessageBoxResult.Yes: return true; case MessageBoxResult.No: case MessageBoxResult.Cancel: return false; default: return null; } } } }
おー、いい感じじゃないか!
長くなったので、c:Connection.Methodsの解説は次回に持ち越します。