tmytのらくがき

個人の日記レベルです

Adaptive and Interactive Toast でアプリの起動を正しくハンドルしよう

NuAnsの話もいいけどちゃんとでべろっぷめんとの話もたまにはしたいよね。ということで、Windows 8.xのころのトーストでタップされた時の引数を設定するには、

<toast launch="ここが引数">
  <visual>
    <!-- いろいろ -->
  </visual>
</toast>

こんなXMLを書いて、App.xaml.csのOnLaunchedでこうやってあげて、MainPage.xaml.csとかのOnNavigatedToとかでハンドリングすればいいんですが

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    // e.Arguments にlaunch で指定した文字列が入ってます。
    var args = e.Arguments;
    // Navigateとかで適当に引き渡す
    ...
    rootFrame.Navigate(typeof(MainPage), args);
}

Windows 10で追加された新しいトーストだとうまく動きません。とりあえずXMLはこんな感じに書いたとします。

<toast launch="好きな文字列">
  <visual>
    <binding template="ToastGeneric">
    </binding>
  </visual>
  <actions>
    <!-- 1) foreground -->
    <action content="yes" arguments="yes" />
    <!-- 2) background -->
    <action content="no" arguments="no" activationType="background" />
    <!-- 3) protocol -->
    <action content="web" arguments="https://example.com/" activationType="protocol" />
  </actions>
</toast>

このとき、actions の中の3つは、それぞれOnActivated、BackgroundTask、Protocolなのでなにもしない。でハンドリングしますが、 ボタンじゃないところを触った時はどうしたらいいの。というと、activationType="foreground" (何も指定しない場合はforeground)と同じように OnActivatedでハンドリングしないといけなくなりました。

つまるところ古いトーストのOnLaunchedのままにしておくとトーストタップしたときスプラッシュすら出ずにアプリが起動しなくなります。

なので、他にOnActivated使わないようなアプリなのであればApp.xaml.csをこんな感じに書くといいかもしれません。

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    OnActivated(e);
}

protected override void OnActivated(IActivatedEventArgs e)
{
    // ** OnLaunched のなかみをコピペしてちょっと修正したもの
    Frame rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        rootFrame.NavigationFailed += OnNavigationFailed;

        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { }

        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // ** 適当にキャストして値を取り出しますよ
        rootFrame.Navigate(typeof(MainPage), 
            (e as LaunchActivatedEventArgs)?.Arguments ?? (e as ToastNotificationActivatedEventArgs).Argument);
    }
    Window.Current.Activate();
}

LaunchActivatedEventArgsはIActivatedEventArgsを実装しているので、こんな感じに書いてあげるとちょっと楽かも。 という感じですが、もしかするとOnLaunchedと、OnActivatedから呼び出すIActivatedEventArgsとArgumentを受け取る関数としたほうがいいかも? たとえばこんなかんじで。

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    OnLaunchedCore(e, e.Arguments);
}

protected override void OnActivated(IActivatedEventArgs e)
{
    if(e is ToastNotificationActivatedEventArgs){
        OnLaunchedCore(e, ((ToastNotificationActivatedEventArgs)e).Argument);
        return;
    }
    base.OnActivated();
}

private void OnLaunchedCore(IActivatedEventArgs e, object parameter)
{
    // ** OnLaunched のなかみをコピペしてちょっと修正したもの
    Frame rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        rootFrame.NavigationFailed += OnNavigationFailed;

        if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { }

        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // ** 適当にキャストして値を取り出しますよ
        rootFrame.Navigate(typeof(MainPage), parameter);
    }
    Window.Current.Activate();
}

ちなみに、古いトーストを使っている場合は従来どおりの挙動になります。つまりOnLaunchedで引数を取得します。