3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ViewModel と相互作用処理

Last updated at Posted at 2020-04-27

はじめに

オレオレ解釈の覚え書き その6

今回から ViewModel のお話です。ここでは相互作用処理についてまとめます。

本文

MVVM における相互作用処理とはユーザとの対話が発生する処理を指します。ダイアログを表示して Yes/No の選択を促す場合などがこれにあたります。こういった処理ではユーザの判断を待たなければなりませんが、直接 View にアクセスできない ViewModel はどのように実装すればよいでしょうか?

MVVM を学び始めたころの私は、このパターンで構築するにあたり、なんとしても ViewModel だけで処理を実現させようと躍起になっていました。当時は参考になる文献が少なく、思い通りに実装できない状態が続き、MVVM への苦手意識を持ったポイントにもなりました。

現在は強力なフレームワークがいくつも登場し、相互作用処理を容易に実現できる環境が整っています。Microsoft 製の Prism では InteractionRequest と InteractionRequestTrigger を組み合わせてこれを実現しています。InteractionRequest クラスには Raise メソッドが実装されており、これを実行するとインターフェースで定義された Raised イベントが発生します。このイベントを購読するためのトリガーが InteractionRequestTrigger になります。イベント引数に内包されるオブジェクトにメッセージを格納することで View に情報を伝播させます。

実装例を見てみましょう。ViewModel に InteractionRequest クラスのインスタンスを宣言し、View に InteractionRequestTrigger を配置してイベントを購読します。授受されるオブジェクトのクラスは Prism に用意されている Notification です。アクションには Notification の情報をダイアログにマッピングして表示する処理を実装し、View でトリガーに紐づければ完成です。

ViewModel
namespace TestApp.ViewModels
{
    public class MainWindowViewModel 
    {
        public InteractionRequest<Notification> MessageRequest { get; }
            = new InteractionRequest<Notification>();

        // このメソッドを呼び出せばダイアログを表示できる
        public void ShowMessage()
            => this.MessageRequest.Raise(new Notification { Title = "タイトル", Content = "メッセージ" });
    }
}
View(Action)
namespace TestApp.Views.Behaviors
{
    public class MessageAction : TriggerAction<DependencyObject>
    {
        protected override void Invoke(object parameter)
        {
            if (!(parameter is InteractionRequestedEventArgs args))
                return;
            if (!(args.Context is Notification context))
                return;

            var owner = this.AssociatedObject as Window ?? Window.GetWindow(this.AssociatedObject);
            var message = context.Content?.ToString() ?? string.Empty:
            MessageBox.Show(owner, message, context.Title, MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }
}
View(Trigger)
<Window x:Class="TestApp.Views.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/xaml/behaviors"
        xmlns:p="http://prismlibrary.com/"
        xmlns:behaviors="clr-namespace:TestApp.Views.Behaviors"
        p:ViewModelLocator.AutoWireViewModel="True">
    <i:Interaction.Triggers>
        <p:InteractionRequestTrigger SourceObject="{Binding MessageRequest, Mode=OneTime}">
            <behaviors:MessageAction/>
        </p:InteractionRequestTrigger>
    </i:Interaction.Triggers>
</Window>

上記で ViewModel は間接的に画面を操作していますが、View を直接参照していないため、独立性を保っています。Raised イベントをとらえて結果を返すようなテストロジックを組めば、View を介さずに ViewModel のロジックテストを実施できます。このように仲介者を挟んで双方が通信する方法は Messenger パターンと呼ばれたりもします。

おわりに

InteractionRequest を使った相互作用処理についてまとめました。
今回は Prism での実装でしたが、Livet などほかのフレームワークでは、これらのやり取りをさらにシンプルに実現したものもあります。それぞれ良さがあるので、自分の作り方にあった基盤を取り入れましょう。

実は今回ご紹介した方法は、執筆時点での最新版である Prism 7.2 で非推奨とされました。(使用できないわけではありません。)ただし、この考え方自体は有益であり、汎用性も高く、理解しておいて損はない内容だと思います。次回は新たに採用された相互作用処理の実現方法である DialogService についてまとめます。

3
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?