ViewModel から View を操作する新しい方法が!
えむナウさんがやってくれました。
MVVM パターンで VM から VIEW を操作するには、VIEW に RoutedUICommnad か ICommand を実装することを推奨する。
MVVM パターンで VM から VIEW を操作したい
文章だけだとよくわからなかったので、コードを書きながら自分なりに解釈してみます。
ViewModel に公開する View の機能を Command として実装
Command は依存関係プロパティとして実装します。
public partial class GreetingView : UserControl { public GreetingView() { InitializeComponent(); } public override void OnApplyTemplate() { // コマンドを設定しておく // これが ViewModel に渡される ShowMessageCommand = new DelegateCommand( ShowMessageCommandExecute, CanShowMessageCommandExecute); base.OnApplyTemplate(); } // MessageBox を表示するコマンド public ICommand ShowMessageCommand { get { return (ICommand)GetValue(ShowMessageCommandProperty); } set { SetValue(ShowMessageCommandProperty, value); } } // ViewModel にバインドして伝達させるために、 // 依存関係プロパティを用意する。 public static readonly DependencyProperty ShowMessageCommandProperty = DependencyProperty.Register( "ShowMessageCommand", typeof(ICommand), typeof(GreetingView)); private bool CanShowMessageCommandExecute(object param) { return param != null; } private void ShowMessageCommandExecute(object param) { MessageBox.Show(param.ToString()); } }
XAML はこんな感じ。
<UserControl x:Class="MvvmSample.GreetingView" 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" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <StackPanel> <TextBox Text="{Binding Path=Name}"/> <Button Content="Greet" Command="{Binding Path=GreetCommand}"/> </StackPanel> </UserControl>
View が公開する Command を呼び出すために ViewModel にも Command を実装
この Command を使って View の機能を呼び出します。
public class GreetingViewModel : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged("Name"); } } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { var h = PropertyChanged; if (h != null) { h(this, new PropertyChangedEventArgs(propertyName)); } } // あいさつをメッセージボックスで表示するコマンド private ICommand _greetCommand; public ICommand GreetCommand { get { return _greetCommand = _greetCommand ?? new DelegateCommand(_ => { // View の機能を呼び出す if (ShowMessageCommand.CanExecute(Name)) { ShowMessageCommand.Execute(string.Format("Hello, {0}.", Name)); } }); } } // View が公開するコマンドがこのプロパティにバインドされる public ICommand ShowMessageCommand { get; set; } }
ViewModel の Command を依存関係プロパティとして実装した View の Command にバインド
Binding の Mode を OneWayToSource に設定して、View から ViewModel に Command の実装を渡します。
<Window x:Class="MvvmSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MvvmSample" Title="MainWindow" Height="350" Width="525"> <Grid> <local:GreetingView ShowMessageCommand="{Binding Path=ShowMessageCommand, Mode=OneWayToSource}"> <local:GreetingView.DataContext> <local:GreetingViewModel/> </local:GreetingView.DataContext> </local:GreetingView> </Grid> </Window>
こんな感じでいいのかな?
View の機能を Command で実装するので、たとえば、「ダイアログの表示」や「コントロールへのフォーカス設定」といった細かい単位で ViewModel に提供できます。
ViewModel を単体テストするときは、ダミーの Command を設定すればいいですね。
この発想はなかったわ。