rksoftware

Visual Studio とか C# とかが好きです

MessagingCenter で DisplayAlert を表示する。

MVVM モデルでは、VM は V を意識しないこと(VM は V を知らないこと)が理想とされています。
すなわち、View が行うべき処理を、ViewModel に書いたり、ViewModel から View のメソッドを呼び出すことはしない、ということです。
しかし、現実問題、ViewModel での処理中または処理結果として View が行う処理をしたい場合はあります。

・画面遷移
・ポップアップメッセージ

あたりは、どうしても View の処理を呼ばなくてはならない典型でしょう。

■ MessagingCenter

こういった場合、MessagingCenter によるメッセージによる通知が使えます。
参考

■ 実行イメージ

ボタン押すと

ポップアップメッセージ表示

「移動する」を押すと画面遷移

■ コード

ボタンを押すと、ボタンにバインドされた ViewModel の Command が実行され、その中で View へメッセージを送ります。
送るメッセージは2回で、1回目はポップアップメッセージの表示、ポップアップで「移動する」が選択された場合、2回目の画面遷移のメッセージを送ります。
App.cs (変更)

using Xamarin.Forms;

namespace MessagingCenterSample
{
    public partial class App : Application
    {
        public App ()
        {
            InitializeComponent();

            MainPage = new NavigationPage(new MainPage());
        }

        protected override void OnStart ()
        {
            // Handle when your app starts
        }

        protected override void OnSleep ()
        {
            // Handle when your app sleeps
        }

        protected override void OnResume ()
        {
            // Handle when your app resumes
        }
    }
}

MainPage.xaml (変更)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MessagingCenterSample"
             xmlns:vm="clr-namespace:MessagingCenterSample.ViewModels"
             x:Class="MessagingCenterSample.MainPage"
             Appearing="MainPage_Appearing"
             Disappearing="MainPage_Disappearing"
             Title="MainPage"
             >
    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Button Text="次画面へ移動する" Command="{Binding GoNextCommand}"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

SecondPage.xaml (追加)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MessagingCenterSample"
             xmlns:vm="clr-namespace:MessagingCenterSample.ViewModels"
             x:Class="MessagingCenterSample.MainPage"
             Appearing="MainPage_Appearing"
             Disappearing="MainPage_Disappearing"
             Title="MainPage"
             >
    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout>
            <Button Text="次画面へ移動する" Command="{Binding GoNextCommand}"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

MainPage.xaml.cs (変更)

using MessagingCenterSample.MessagingParameter;
using MessagingCenterSample.ViewModels;
using System;
using Xamarin.Forms;

namespace MessagingCenterSample
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private void MainPage_Appearing(object sender, EventArgs e)
        {
            MessagingCenter.Subscribe<MainPageViewModel, AlertParameter>(this, "DisplayAlert", DisplayAlert);
            MessagingCenter.Subscribe<MainPageViewModel>(this, "GoNext", GoNextPage);
        }

        private void MainPage_Disappearing(object sender, EventArgs e)
        {
            MessagingCenter.Unsubscribe<MainPageViewModel, AlertParameter>(this, "DisplayAlert");
            MessagingCenter.Unsubscribe<MainPageViewModel>(this, "GoNext");
        }

        private async void DisplayAlert<T>(T sender, AlertParameter arg)
        {
            var isAccept = await DisplayAlert(arg.Title, arg.Message, arg.Accept, arg.Cancel);
            arg.Action?.Invoke(isAccept);
        }

        private void GoNextPage<T>(T sender)
        {
            this.Navigation.PushAsync(new SecondPage());
        }
    }
}

MainPageViewModel.cs (追加)

using MessagingCenterSample.MessagingParameter;
using Xamarin.Forms;

namespace MessagingCenterSample.ViewModels
{
    public class MainPageViewModel
    {
        public Command GoNextCommand { get; }

        public MainPageViewModel()
        {
            GoNextCommand = new Command(() =>
            {
                MessagingCenter.Send(this, "DisplayAlert", new AlertParameter()
                {
                    Title = "確認",
                    Message = "次画面に移動します。よろしいですか?",
                    Accept = "移動する",
                    Cancel = "移動しない",
                    Action = result =>
                    {
                        if (result)
                            MessagingCenter.Send(this, "GoNext");
                    }
                });
            });
        }
    }
}

AlertParameter (追加)

using System;

namespace MessagingCenterSample.MessagingParameter
{
    class AlertParameter
    {
        public string Title { get; set; }
        public string Message { get; set; }
        public string Accept { get; set; }
        public string Cancel { get; set; }
        public Action<bool> Action { get; set; }
    }
}

コードのポイント

■ メッセージの送信・購読

まず、メッセージの送信です。
今回は ViewModel からメッセージを送信するので、MainPageViewModel.cs に送信のコードを書いています。

MessagingCenter.Send(this, "DisplayAlert", new AlertParameter()
{
  Title = "確認",
  ・・・・

メッセージの購読は、View が行うので、MainPage.xaml.cs です。

MessagingCenter.Subscribe<MainPageViewModel, AlertParameter>(this, "DisplayAlert", DisplayAlert);

これで、MainPageViewModel から "DisplayAlert" というメッセージが送信された際に、DisplayAlert メソッドが実行されます。
また、メッセージを送信する際にパラメータを送りたい場合には、 Send メソッドの 3 つ目の引数に設定し、Subscribe の 2 つ目のジェネリックパラメータにもパラメータの型を指定します。
今回は、ポップアップメッセージに表示する文言等をパラメータで渡しています。

■ メッセージ購読の登録・解除

View 側ではメッセージを Subscribe しますので、対として不要になったら Unsubscribe しなければなりません。
そのタイミングですが、AppearingDisappearing のイベントで行っています。
Appearing は画面がアクティブになった際、Disappearing は画面が閉じられたり別の画面が上に表示された際のイベントです。

■ ポップアップメッセージでの選択

参考
今回は、ポップアップメッセージでの選択結果に応じて画面遷移を行ったり行わなかったりしたい点が厄介です。
DisplayAlert が非同期メソッドですが、MessagingCenter.Send メソッドが同期メソッドであるために結果を待つことができません。
そのため、AlertParameter パラメーターに Action というプロパティでコールバックを設定できるようにしています。
DisplayAlert の終了後に、View 側から Action に設定された処理を呼び出すことで、ViewModel へ処理を返しています。

少々複雑ではありますが、これで望み通りの「ポップアップメッセージを表示し、選択に応じて画面遷移したりしなかったりする」ことが実現できました。