MVVM パターンで ViewModel から Viewを操作する方法

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 を設定すればいいですね。

この発想はなかったわ。