trapemiyaの日記

hatenablogが新しくなったんで新規一転また2019年1月からちょこちょこ書いてます。C#中心のプログラミングに関するお話です。

ViewModelからViewのメソッドを実行する。

音声ファイルを再生する。
http://d.hatena.ne.jp/trapemiya/20150330/1427700162

の続きです。遅くなりすみません。

さて、音声ファイルの再生方法は、androidiOS等、各プラットフォームによって異なります。よって、この音声を再生する記述は、各プラットフォーム毎のクラスに書いた方が、PCLで各プラットフォーム毎に場合分けして書くよりもスマートです。そうすると、PCL、つまりViewModelから各プラットフォームのクラスに対して、音声再生処理のお願いをしなければなりません。MVVMにおいてこの問題は良く出てくる話です。私も、WPFの開発をMVVMで始めた際にいろいろと悩みました。
ViewModelはViewのインスタンスとは疎結合ですから、ViewModelからViewに対して何らかの手動で連絡を取る必要があります。その手段として、ここではMessenger Pluginを使用します。プラグインはMvvmCrossに用意されている機能であり、IoCコンテナを利用して実現されています。

では、早速、Messenger Pluginを導入しましょう。

ソリューションエクスプローラーでPCLプロジェクトの「参照設定」を右クリックし、「NuGetパッケージの管理」をクリックします。そして、以下の図ように「Messenger Plugin」をインストールして下さい。

同様のことをandroid等、各プラットフォームのプロジェクトでも行って下さい。ここではandroid用プロジェクトのみを扱うものとします。

Messenger Pluginは、以下のようにMvx.ResolveメソッドでMvxMessengerHubオブジェクトを注入し、そのオブジェクトのPublishメソッドでメッセージを送ります。

var messenger = Mvx.Resolve<IMvxMessenger>();
messenger.Publish(new 汎用Message(this, "Play!"));

上記のメソッドが記述されているViewModel、および、メッセージオブジェクトである汎用Messageクラスの全容を以下に示します。

public class FirstViewModel
    : MvxViewModel
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public FirstViewModel()
    {
        PlayCommand = new MvxCommand(Play);
    }
    
    private string _hello = "Hello MvvmCross";
    public string Hello
    {
        get { return _hello; }
        set { _hello = value; RaisePropertyChanged(() => Hello); }
    }

    public MvxCommand PlayCommand { get; private set; }

    private void Play()
    {
        var messenger = Mvx.Resolve<IMvxMessenger>();
        messenger.Publish(new 汎用Message(this, "Play!"));
    }
}

public class 汎用Message : MvxMessage
{
    public 汎用Message(object sender, object param) : base(sender) { this.Param = param; }

    public object Param { get; set; }
}

メッセージを受け取る側、つまりViewでは以下のようにして、メッセージが来るのを待ちます。

var messenger = Mvx.Resolve<IMvxMessenger>();
MvxSubscriptionToken _tokenEnd = messenger.SubscribeOnMainThread<汎用Message>(OnReceiveMessage);

メッセージが来たらOnReceiveMessageメソッドが実行され、そこで音楽が再生されます。
上記のメソッドが記述されているViewのコードの全容を以下に示します。

public class FirstView : MvxActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.FirstView);

        //起動時に音楽を鳴らすのを止める。
        //MediaPlayer.Create(this, Resource.Raw.c9).Start();

        var messenger = Mvx.Resolve<IMvxMessenger>();
        MvxSubscriptionToken _tokenEnd = messenger.SubscribeOnMainThread<汎用Message>(OnReceiveMessage);
    }

    public void OnReceiveMessage(汎用Message message)
    {
        if (message.Sender != ViewModel)
            return;

        MediaPlayer.Create(this, Resource.Raw.c9).Start();
    }
}

Messenger Pluginは、IoCコンテナの特徴の一つであるsingletonオブジェクトを介して、メッセージを伝達しています。Messenger Pluginがなぜsingletonになるかは、以下のソースコードの通り、Mvx.RegisterSingletonメソッドで登録されているからです。

MvvmCross/Plugins/Cirrious/Messenger/Cirrious.MvvmCross.Plugins.Messenger/PluginLoader.cs
https://github.com/MvvmCross/MvvmCross/blob/c306ba37afd9024f68b7a4f1fedcaf4cf7d01b8d/Plugins/Cirrious/Messenger/Cirrious.MvvmCross.Plugins.Messenger/PluginLoader.cs#L27

以上、おつかれさまでした。

(参考としたページ)
Executing UI Code from ViewModel on MVVMCross
http://stackoverflow.com/questions/15590776/executing-ui-code-from-viewmodel-on-mvvmcross