2016/04/26

デスクトップアプリのトースト通知とアクションセンター

Windows 10ではユーザーへの通知インターフェイスとしてアクションセンターが中心に位置づけられています。これにWindows 8式のデスクトップアプリからのトースト通知では十分対応できませんが、対応方法のサンプルがMicrosoftから公開されていたので、整理してみます。

なお、言語はC#で、トースト内のインタラクティブ機能については別途

1. 概要


まず基本情報とサンプル(C++とC#)は以下のとおり。
トーストがアクションセンターに対応すると以下のメリットがあります。
  1. トーストがタイムアウトなどで消えた後もアクションセンターに残るので、後から見てアクティベートできる。
  2. トーストを出したアプリが終了した後でも、トーストからアプリを直接起動して処理を続けることができる。
一応Windows 8式のトーストでもトーストが出ている間にアクションセンターを開くとアクションセンターに移動しますが、ただ移動するだけのようです。

コード的には、実はトーストの出し方は基本的にWindows 8と変わりません。これらの機能はトーストの設定によってではなく、アプリでCOMのINotificationActivationCallbackインターフェイスを実装したクラスを使って行います。というより、UWP用に用意された機能を(ラップした関数は提供されないので)剥き出しで使っちゃいなよという感じ。

INotificationActivationCallbackのメンバーはActivateメソッドだけで、トーストがアクティベートされるとこのメソッドが実行されます。これを利用するレベルとしては、
  1. 下準備として、INotificationActivationCallbackを実装したCOMクラスのCLSIDをアプリのショートカットに含める。
  2. そのクラスの型をCOMに登録すると、トーストがアクションセンターに残るようになる。
  3. そのクラスのCLSIDとアプリの実行ファイルのパスをレジストリに登録してCOMサーバーで起動できるようにすると、トーストからアプリを起動できるようになる。
トーストからアプリを起動できるようにする場合、アプリ本体のUIを開く前にバックグラウンドで処理を分岐させたり、アプリ終了時の状態を復元させたり、UXの観点から色々なやり方があると思います。

なお、元々のToastNotificationクラスのイベントも発生するので、両方をうまく組み合わせる必要があります。もしくはActivatedの場合だけ捉えるのであれば、INotificationActivationCallbackのActivateは常に実行されるので、こちらだけ処理する手もあります。

2. コード


先に、INotificationActivationCallbackを実装したCOMクラスのCLSIDについては、ショートカットのプロパティにSystem.AppUserModel.ToastActivatorCLSIDが追加されています(Windows 10 SDKのpropkey.hにある)。読み書きは型がGuidになる以外はAppUserModelIDの場合と同様にできるので省略。

具体的なCOMクラスとしては以下のようなものです(INotificationActivationCallbackの宣言などは省略)。
基底クラスとしてNotificationActivatorBaseをライブラリに置き、その派生クラスのNotificationActivatorをアプリ本体に置く想定です。これはこのクラスのCLSIDはアプリごとに一意である必要があるため。

アプリの起動時にNotificationActivatorの型を引数としてRegisterComTypeメソッドを実行し、COMに登録します。終了時にはUnregisterComTypeメソッドで登録を解除します。トーストがアクティベートされるとActivateメソッドが実行され、RegisterComTypeの引数で与えられていたActionが実行されます。Activateの引数のinvokedArgsとdataはインタラクティブなトーストから返ってくる情報ですが、必要がなければ無視していいです。

次に、アプリをCOMサーバーで起動できるようにレジストリに登録するヘルパークラス。これは普通にレジストリを読み書きするだけです。
アプリの初回起動時にRegisterComServerを実行します。引数はCOMクラスの型と実行ファイルのパス、起動時のコマンドライン引数(もしあれば)。このレジストリのキーはアプリを使わなくなれば当然不要になるので、削除を。

INotificationActivationCallbackの動作は、アプリが実行中に(アクションセンター内に限らず)トーストがアクティベートされると、そのままActivateが実行されます。アプリが終了後にアクティベートされると、レジストリの情報に従ってアプリが起動された後にActivateが実行されます。その際、アプリのコマンドライン引数に自動的に"-Embedding"が追加されるので、その有無でトーストから起動されたか否か判別できます。

細かい実装とサンプル(WPF)はレポジトリを見てください。ToastNotificationのイベントとINotificationActivationCallbackのActivateメソッドのタイミングが分かるようになっています。

3. まとめ


以上のように、単に通知を出すのとは違って、アクションセンターへの対応はアプリのライフサイクルに関わってくるので少し大事になります。またアクティベート時の状態をどうするかによって動作の練り直しが必要になるかもしれません。

ちなみに、Windows 10で実行する場合は、WinRTのアセンブリはWindows.winmdではなくWindows.Foundation.UniversalApiContract.winmdの方が適当かなと思って参照しようとしたら、このパスが以前と変わってました。この、どのアセンブリを使えばいいのか分からん問題が、個人的にWinRTの大きな難点なんですけどね。