物置き

Win32ネイティブアプリからトースト通知を表示する

自アプリ(Win32ネイティブアプリ)でトースト表示するにあたり、手順を調べたので以下にメモしておく。

なお、本記事に張り付けたコード辺は自アプリの関連処理を切り出したものだけど、切り出したものに対する検証(ビルド、実行)はしていないため、何か不備はあるかも・・

アプリに対してAppUserModelIdを割り当てる

AppUserModelIdはGUID。guidgen.exeを使って自アプリ用のAppUserModelIdを作っておく。 トーストを表示する際、このAppUserModelIdを指定する必要がある。

AppUserModelIDを登録する(スタートメニューにアプリのショートカットを作成する)

スタートメニューにショートカットを作り、そのショートカットメニューのプロパティとして PKEY_AppUserModel_IDキーに紐づける形で前ステップで生成したAppUserModelIDを割り当てる。

以下のような形でショートカットを作成しておけばよい。

#include <propkey.h>
#include <propvarutil.h>
// 上記のほかに、CComPtrを使うための準備をしておく(関連ヘッダのinclude,CoInitializeなど)

// 事前に確保しておく↓
constexpr LPCWSTR MYAPP_USERMODEL_ID = L"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}";

// pathToSave : ショートカットキー保存先パス
// exePath : ショートカットのリンク先(実行ファイル)のパス
bool registerShortcut(LPCWSTR pathToSave, LPCWSTR exePath)
{
    CComPtr<IShellLink> shellLinkPtr;
    HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,IID_IShellLink,(void**)&shellLinkPtr);
    if (FAILED(hr)){
        return false;
    }


    // 実行ファイルへのパス
    shellLinkPtr->SetPath(exePath);

    // 引数、カレントディレクトリなどは必要に応じて設定する
    shellLinkPtr->SetArguments(L"");
    shellLinkPtr->SetWorkingDirectory(L"");

    // AppUserModelIdをショートカットに設定する
    CComPtr<IPropertyStore> propStore;
    shellLinkPtr->QueryInterface(IID_IPropertyStore, (void**)&propStore);

    // AppUserModelIdをappIdPropVarに設定し、キーに書き込む
    PROPVARIANT appIdPropVar;
    InitPropVariantFromString(MYAPP_USERMODEL_ID, &appIdPropVar);
    propStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);

    propStore->Commit();

    PropVariantClear(&appIdPropVar);

    CComPtr<IPersistFile> persistFilePtr;
    hr = shellLinkPtr->QueryInterface(IID_IPersistFile, (void**)&persistFilePtr);
    if(FAILED(hr)){
        return false;
    }
    // ショートカットキーをファイルとして保存する
    hr = persistFilePtr->Save(pathToSave, TRUE);
    if(FAILED(hr)){
        return false;
    }

    return true;
}

トーストを表示する

ToastNotificationインスタンスを作成し、これをToastNotificationManagerに対して渡すとトーストを表示することができる。

トーストに表示する内容はXMLベースのデータをこしらえて定義する。
テキストのほかに画像をおいたりハイパーリンクを設置したり、ボタンを置いたりなどできる。 また、クリック時の処理などいろいろ定義することができる。 ただし、クリック時の処理を定義しようとすると、そのための準備がいろいろ必要になるがここでは触れていない。

#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Data::Xml::Dom;

void ShowToast()
{
    XmlDocument doc;
    doc.LoadXml(L"
          <toast>\
          <visual>\
          <binding template=\"ToastGeneric\">\
          <text></text>\
          <text></text>\
          </binding>\
          </visual>\
          </toast>");

    doc.DocumentElement().SetAttribute(L"launch", L"action=xxx&message=yyy");  // コールバック時にここで指定した文字列がえられる


    doc.SelectSingleNode(L"//text[1]").InnerText(L"こんにちは");
    doc.SelectSingleNode(L"//text[2]").InnerText(L"テストです");

    // トーストを表示する
    winrt::Windows::UI::Notifications::ToastNotification notif(doc);

    winrt::Windows::UI::Notifications::ToastNotificationManager toastManager;

    // CreateToastNotifierの引数に自アプリのAppUserModelIdを指定することにより、
    // 自アプリの通知としてトースト表示される。
    ToastNotifier toastNotifier(toastManager.CreateToastNotifier(MYAPP_USERMODEL_ID));
    toastNotifier.Show(notif);

}

その他メモ

  • Windows10/11環境では、Shell_NotifyIconを使って、トースト表示をすることもできる

    • ただし、以下のような制約があるようだ
      • クリックしたときの処理を定義できない
      • 見た目の細かなカスタマイズはできない(せいぜいアイコンを指定するくらい)
      • 通知センターに通知が残らない、
  • 自前でトースト表示する場合、トーストをクリックしたときの処理を自アプリで処理することができるが、その場合はコールバックを登録したり、コールバックを用意する必要がある

  • 他アプリが登録したUserAppModeIdを流用することで、自アプリでAppUserModeIdを登録しなくてもトースト表示はできる

    • ただし、流用元アプリとしてトースト表示が行われる

既存のAppUserModelIdを調べる

  • Excplorerのアドレスバーにshell:AppsFolderと打つ
  • 何もない領域を右クリック(Win11環境であれば、シフトキー押しながら右クリック)し、グループで表示>その他を選択する
  • 「詳細表示の設定」ダイアログが表示されるので、AppUserModelIdをチェックしてOKボタンを押下する
  • 再度、 何もない領域を右クリックして、表示>詳細を選択する

リスト表示にAppUserModelIdという列が表示されるので、ここからアプリごとのAppUserModelIdを確認することができる