WRL C++ デスクトップ アプリからのローカル トースト通知の送信

パッケージ デスクトップ アプリと非パッケージ デスクトップ アプリは、ユニバーサル Windows プラットフォーム (UWP) アプリと同様に対話型トースト通知を送信できます。 これには、パッケージ アプリ (「パッケージ化された WinUI 3 デスクトップ アプリの 新しいプロジェクトを作成する」を参照)、外部の場所でのパッケージ アプリ (「外部の場所で パッケージ化してパッケージ ID を付与する」を参照)、パッケージ化されていないアプリ (「パッケージ化されていない WinUI 3 デスクトップ アプリの 新しいプロジェクトを作成する」を参照) が含まれます。

ただし、パッケージ化されていないデスクトップ アプリには、いくつかの特別な手順があります。 これは、さまざまなアクティブ化スキームと、実行時にパッケージ ID が不足しているためです。

重要

UWP アプリを作成する場合は、UWP のドキュメントを参照してください。 その他のデスクトップ言語については、デスクトップ C# を参照してください。

手順 1: Windows SDK を有効化する

アプリに対して Windows SDK を有効にしていない場合は、最初に有効にする必要があります。 主な手順がいくつかあります。

  1. runtimeobject.lib追加の依存ファイルに追加します。
  2. Windows SDK をターゲットにします。

プロジェクトを右クリックし [プロパティ] を選択します。

上部の [構成] メニューで、[すべての構成] を選択して、デバッグとリリースの両方に次の変更が適用されるようにします。

[リンカー] -> [入力] で、[追加の依存ファイル]runtimeobject.lib を追加します。

次に、[全般] で、Windows SDK バージョンがバージョン 10.0 以降に設定されていることを確認します。

手順 2: 互換性ライブラリ コードをコピーする

DesktopNotificationManagerCompat.h ファイルと DesktopNotificationManagerCompat.cpp ファイルを GitHub からプロジェクトにコピーします。 互換性ライブラリは、デスクトップ通知の複雑さの多くを抽象化します。 次の手順では、互換性ライブラリが必要です。

プリコンパイル済みヘッダーを使用している場合は、必ず DesktopNotificationManagerCompat.cpp ファイルの最初の行として #include "stdafx.h"します。

手順 3: ヘッダー ファイルと名前空間を含める

compat ライブラリのヘッダー ファイル、および Windows トースト API の使用に関連するヘッダー ファイルと名前空間を含めます。

#include "DesktopNotificationManagerCompat.h"
#include <NotificationActivationCallback.h>
#include <windows.ui.notifications.h>

using namespace ABI::Windows::Data::Xml::Dom;
using namespace ABI::Windows::UI::Notifications;
using namespace Microsoft::WRL;

手順 4: アクティベーターを実装する

ユーザーがトーストをクリックしたときにアプリが動作するように、トーストをアクティブ化するためのハンドラーを実装する必要があります。 これは、トーストをアクション センターで保持するために必要です (トーストは、アプリが閉じられた数日後にクリックされる可能性があるため)。 このクラスは、プロジェクト内の任意の場所に配置できます。

次に示すように、UUID を含む INotificationActivationCallback インターフェイスを実装し、CoCreatableClass も呼び出してクラスに COM 作成可能としてフラグを設定します。 UUID の場合は、多数のオンライン GUID ジェネレーターのいずれかを使用して一意の GUID を作成します。 この GUID CLSID (クラス識別子) は、アクション センターが COM でアクティブ化するクラスを認識する方法です。

// The UUID CLSID must be unique to your app. Create a new GUID if copying this code.
class DECLSPEC_UUID("replaced-with-your-guid-C173E6ADF0C3") NotificationActivator WrlSealed WrlFinal
    : public RuntimeClass<RuntimeClassFlags<ClassicCom>, INotificationActivationCallback>
{
public:
    virtual HRESULT STDMETHODCALLTYPE Activate(
        _In_ LPCWSTR appUserModelId,
        _In_ LPCWSTR invokedArgs,
        _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data,
        ULONG dataCount) override
    {
        // TODO: Handle activation
    }
};

// Flag class as COM creatable
CoCreatableClass(NotificationActivator);

手順 5: 通知プラットフォームに登録する

その後、通知プラットフォームに登録する必要があります。 アプリがパッケージ化されているかパッケージ化されていないかに応じて、さまざまな手順があります。 両方をサポートしている場合は、両方の手順を実行する必要があります (ただし、ライブラリがコードを処理するため、コードをフォークする必要はありません)。

パッケージに含まれる

アプリがパッケージ化されている場合 (パッケージ化された WinUI 3 デスクトップ アプリの新しいプロジェクトを作成するを参照)、または外部の場所でパッケージ化されている場合 (外部の場所でパッケージ化してパッケージ ID を付与するを参照)、または両方をサポートする場合は、Package.appxmanifest で次を追加します:

  1. xmlns:com の 宣言
  2. xmlns:desktop の 宣言
  3. IgnorableNamespaces 属性では、comデスクトップ
  4. 手順 #4の GUID を使用した COM アクティベータには、com:の拡張機能。 起動がトーストからであったことがわかるように、必ずArguments="-ToastActivated"を含 めてください。
  5. desktop:拡張 windows.toastNotificationActivation を使用して、トースト アクティベーターの CLSID (手順 #4 の GUID) を宣言します。

Package.appxmanifest

<Package
  ...
  xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
  xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
  IgnorableNamespaces="... com desktop">
  ...
  <Applications>
    <Application>
      ...
      <Extensions>

        <!--Register COM CLSID LocalServer32 registry key-->
        <com:Extension Category="windows.comServer">
          <com:ComServer>
            <com:ExeServer Executable="YourProject\YourProject.exe" Arguments="-ToastActivated" DisplayName="Toast activator">
              <com:Class Id="replaced-with-your-guid-C173E6ADF0C3" DisplayName="Toast activator"/>
            </com:ExeServer>
          </com:ComServer>
        </com:Extension>

        <!--Specify which CLSID to activate when toast clicked-->
        <desktop:Extension Category="windows.toastNotificationActivation">
          <desktop:ToastNotificationActivation ToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> 
        </desktop:Extension>

      </Extensions>
    </Application>
  </Applications>
 </Package>

非パッケージ

アプリがパッケージ化されていない場合 (パッケージ化されていない WinUI 3 デスクトップ アプリの新しいプロジェクトの作成を参照)、または両方をサポートしている場合は、スタート画面のアプリのショートカットでアプリケーション ユーザー モデル ID (AUMID) とトースト アクティベーター CLSID (手順 4 の GUID) を宣言する必要があります。

アプリを識別する一意の AUMID を選択します。 これは通常、[CompanyName].[AppName] の形式です。 ただし、すべてのアプリで一意であることを確認する必要があります (末尾に数字を自由に追加してください)。

手順 5.1: WiX インストーラー

インストーラーに WiX を使用している場合は、Product.wxs ファイルを編集して、次に示すように、スタート メニュー ショートカットに 2 つのショートカット プロパティを追加します。 手順 #4 の GUID が次のように囲まれていること {} を確認します。

Product.wxs

<Shortcut Id="ApplicationStartMenuShortcut" Name="Wix Sample" Description="Wix Sample" Target="[INSTALLFOLDER]WixSample.exe" WorkingDirectory="INSTALLFOLDER">
                    
    <!--AUMID-->
    <ShortcutProperty Key="System.AppUserModel.ID" Value="YourCompany.YourApp"/>
    
    <!--COM CLSID-->
    <ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID" Value="{replaced-with-your-guid-C173E6ADF0C3}"/>
    
</Shortcut>

重要

通知を実際に使用するには、通常どおりにデバッグする前にインストーラーを使用してアプリをインストールする必要があります。これにより、AUMID と CLSID を含むスタート ショートカットが表示されます。 スタート ショートカットが表示されたら、Visual Studio から F5 キーを使用してデバッグできます。

手順 5.2: AUMID と COM サーバーを登録する

その後、インストーラーに関係なく、(通知 API を呼び出す前に) アプリのスタートアップ コードで RegisterAumidAndComServer メソッドを呼び出し、手順 #4 の通知アクティベーター クラスと上記で使用した AUMID を指定します。

// Register AUMID and COM server (for a packaged app, this is a no-operation)
hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"YourCompany.YourApp", __uuidof(NotificationActivator));

アプリがパッケージ化デプロイと非パッケージ化デプロイの両方をサポートしている場合は、関係なくこのメソッドを自由に呼び出すことができます。 パッケージ化 (つまり、実行時にパッケージ ID を使用) を実行している場合、このメソッドはすぐに返されます。 コードをフォークする必要はありません。

このメソッドを使用すると、常に AUMID を提供することなく、compat API を呼び出して通知を送信および管理できます。 また、COM サーバーの LocalServer32 レジストリ キーが挿入されます。

手順 6: COM アクティベーターを登録する

パッケージ化アプリと非パッケージ化アプリの両方で、トーストのアクティブ化を処理できるように、通知アクティベーターの種類を登録する必要があります。

ご利用のアプリのスタートアップ コードで、次の RegisterActivatorコ メソッドを呼び出します。 トーストのアクティブ化を受け取るには、これを呼び出す必要があります。

// Register activator type
hr = DesktopNotificationManagerCompat::RegisterActivator();

手順 7: 通知を送信する

通知の送信は UWP アプリと同じですが、DesktopNotificationManagerCompat を使用して ToastNotifier を作成する点が異なります。 互換性ライブラリは、パッケージ化アプリと非パッケージ化アプリの違いを自動的に処理するため、コードをフォークする必要はありません。 非パッケージ化アプリの場合、Compat ライブラリは RegisterAumidAndComServer を呼び出したときに指定した AUMID をキャッシュするため、AUMID を提供するタイミングや提供しない場合について心配する必要はありません。

従来の Windows 8.1 トースト通知テンプレートでは、手順 #4 で作成した COM 通知アクティベーターはアクティブ化されないため、以下に示すように ToastGeneric バインドを使用してください。

重要

HTTP イメージは、マニフェストにインターネット機能があるパッケージ アプリでのみサポートされます。 非パッケージ化アプリでは、HTTP イメージはサポートされていません。画像をローカル アプリ データにダウンロードし、ローカルで参照する必要があります。

// Construct XML
ComPtr<IXmlDocument> doc;
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(
    L"<toast><visual><binding template='ToastGeneric'><text>Hello world</text></binding></visual></toast>",
    &doc);
if (SUCCEEDED(hr))
{
    // See full code sample to learn how to inject dynamic text, buttons, and more

    // Create the notifier
    // Desktop apps must use the compat method to create the notifier.
    ComPtr<IToastNotifier> notifier;
    hr = DesktopNotificationManagerCompat::CreateToastNotifier(&notifier);
    if (SUCCEEDED(hr))
    {
        // Create the notification itself (using helper method from compat library)
        ComPtr<IToastNotification> toast;
        hr = DesktopNotificationManagerCompat::CreateToastNotification(doc.Get(), &toast);
        if (SUCCEEDED(hr))
        {
            // And show it!
            hr = notifier->Show(toast.Get());
        }
    }
}

重要

デスクトップ アプリでは、レガシーのトースト テンプレート (ToastText02 など) を使用できません。 COM CLSID が指定されている場合、レガシー テンプレートのアクティブ化は失敗します。 上記のように、Windows ToastGeneric テンプレートを使用する必要があります。

手順 8: アクティブ化を処理する

ユーザーがトーストまたはトーストのボタンをクリックすると、NotificationActivator クラスの Activate メソッドが呼び出されます。

Activate メソッド内では、トーストで指定した引数を解析し、ユーザーが入力または選択したユーザーによる入力を取得し、それに応じてアプリをアクティブ化できます。

Note

Activate メソッドは、メイン スレッドとは別のスレッドで呼び出されます。

// The GUID must be unique to your app. Create a new GUID if copying this code.
class DECLSPEC_UUID("replaced-with-your-guid-C173E6ADF0C3") NotificationActivator WrlSealed WrlFinal
    : public RuntimeClass<RuntimeClassFlags<ClassicCom>, INotificationActivationCallback>
{
public: 
    virtual HRESULT STDMETHODCALLTYPE Activate(
        _In_ LPCWSTR appUserModelId,
        _In_ LPCWSTR invokedArgs,
        _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA* data,
        ULONG dataCount) override
    {
        std::wstring arguments(invokedArgs);
        HRESULT hr = S_OK;

        // Background: Quick reply to the conversation
        if (arguments.find(L"action=reply") == 0)
        {
            // Get the response user typed.
            // We know this is first and only user input since our toasts only have one input
            LPCWSTR response = data[0].Value;

            hr = DesktopToastsApp::SendResponse(response);
        }

        else
        {
            // The remaining scenarios are foreground activations,
            // so we first make sure we have a window open and in foreground
            hr = DesktopToastsApp::GetInstance()->OpenWindowIfNeeded();
            if (SUCCEEDED(hr))
            {
                // Open the image
                if (arguments.find(L"action=viewImage") == 0)
                {
                    hr = DesktopToastsApp::GetInstance()->OpenImage();
                }

                // Open the app itself
                // User might have clicked on app title in Action Center which launches with empty args
                else
                {
                    // Nothing to do, already launched
                }
            }
        }

        if (FAILED(hr))
        {
            // Log failed HRESULT
        }

        return S_OK;
    }

    ~NotificationActivator()
    {
        // If we don't have window open
        if (!DesktopToastsApp::GetInstance()->HasWindow())
        {
            // Exit (this is for background activation scenarios)
            exit(0);
        }
    }
};

// Flag class as COM creatable
CoCreatableClass(NotificationActivator);

アプリが閉じられている間の起動を適切にサポートするには、WinMain 関数で、トーストから起動されているかどうかを判断する必要があります。 トーストから起動すると、"-ToastActivated" の起動引数が表示されます。 これが表示されたら、通常の起動アクティブ化コードの実行を停止し、必要に応じて NotificationActivator で起動ウィンドウを処理できるようにする必要があります。

// Main function
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLineArgs, _In_ int)
{
    RoInitializeWrapper winRtInitializer(RO_INIT_MULTITHREADED);

    HRESULT hr = winRtInitializer;
    if (SUCCEEDED(hr))
    {
        // Register AUMID and COM server (for a packaged app, this is a no-operation)
        hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"WindowsNotifications.DesktopToastsCpp", __uuidof(NotificationActivator));
        if (SUCCEEDED(hr))
        {
            // Register activator type
            hr = DesktopNotificationManagerCompat::RegisterActivator();
            if (SUCCEEDED(hr))
            {
                DesktopToastsApp app;
                app.SetHInstance(hInstance);

                std::wstring cmdLineArgsStr(cmdLineArgs);

                // If launched from toast
                if (cmdLineArgsStr.find(TOAST_ACTIVATED_LAUNCH_ARG) != std::string::npos)
                {
                    // Let our NotificationActivator handle activation
                }

                else
                {
                    // Otherwise launch like normal
                    app.Initialize(hInstance);
                }

                app.RunMessageLoop();
            }
        }
    }

    return SUCCEEDED(hr);
}

イベントのアクティブ化シーケンス

アクティベーション シーケンスは次のとおりです...

アプリが既に実行されている場合:

  1. NotificationActivatorアクティブ化が呼び出される

アプリが実行されていない場合:

  1. アプリが EXE を起動すると、コマンド ライン引数 "-ToastActivated" が表示されます
  2. NotificationActivatorアクティブ化が呼び出される

フォアグラウンドとバックグラウンドのアクティブ化

デスクトップ アプリの場合、フォアグラウンド アクティベーションとバックグラウンド アクティベーションは同じように処理され、COMアクティベーターが呼び出されます。 ウィンドウを表示するか、単にいくつかの作業を実行して終了するかを決定するのは、アプリのコード次第です。 したがって、トースト コンテンツでバックグラウンドのactivationType を指定しても、動作は変更されません。

手順 9: 通知を削除して管理する

通知の削除と管理は、UWP アプリと同じです。 ただし、デスクトップ アプリに AUMID を提供することを心配する必要がないように、Compat ライブラリを使用して DesktopNotificationHistoryCompat を取得することをお勧めします。

std::unique_ptr<DesktopNotificationHistoryCompat> history;
auto hr = DesktopNotificationManagerCompat::get_History(&history);
if (SUCCEEDED(hr))
{
    // Remove a specific toast
    hr = history->Remove(L"Message2");

    // Clear all toasts
    hr = history->Clear();
}

手順 10: デプロイとデバッグ

パッケージ アプリをデプロイしてデバッグするには、「パッケージ デスクトップ アプリ の実行、デバッグ、テスト」を参照してください。

デスクトップ アプリをデプロイしてデバッグするには、通常どおりにデバッグする前にインストーラーを使用してアプリをインストールする必要があります。これにより、AUMID と CLSID のスタート ショートカットが表示されます。 スタート ショートカットが表示されたら、Visual Studio から F5 キーを使用してデバッグできます。

通知がデスクトップ アプリに表示されない (例外がスローされない) 場合は、スタート ショートカットが存在しない (インストーラーを使用してアプリをインストールする) か、コードで使用した AUMID がスタート ショートカットの AUMID と一致しない可能性があります。

通知が表示されてもアクション センターに保持されない場合 (ポップアップが閉じた後に消える)、COM アクティベーターが正しく実装されていないことを意味します。

パッケージ化デスクトップ アプリと非パッケージ化デスクトップ アプリの両方をインストールした場合、トーストのアクティブ化を処理するときに、パッケージ化アプリが非パッケージ化アプリよりも優先されることに注意してください。 つまり、非パッケージ化アプリからのトーストは、クリックするとパッケージ化アプリを起動します。 パッケージ化アプリをアンインストールすると、ライセンス認証は非パッケージ化アプリに戻ります。

HRESULT 0x800401f0 CoInitialize has not been called.を受け取った 場合は、API を呼び出す 前に必ずアプリで CoInitialize(nullptr) を呼び出してください。

Compat API の呼び出し中に HRESULT 0x8000000e A method was called at an unexpected time. を受信した場合は、必要な登録メソッドを呼び出せなかった (または、パッケージ アプリの場合は、現在パッケージ コンテキストでアプリを実行していない) ことを意味している可能性があります。

多数のunresolved external symbolコンパイル エラーが発生した場合は、手順 1 でruntimeobject.lib追加の依存ファイルに追加するのを忘れた可能性があります (または、リリース構成ではなくデバッグ構成にのみ追加した)。

従来のバージョンの Windows の処理

Windows 8.1 またはそれ以前をサポートしている場合は、DesktopNotificationManagerCompat API を呼び出す前に、または ToastGeneric トーストを送信する前に、 Windows で実行しているか、実行時にチェックする必要があります。

Windows 8 ではトースト通知が導入されましたが、ToastText01 などの従来のトースト テンプレートが使用されました。 トーストは永続化されていない短いポップアップのみのため、ToastNotification クラスのメモリ内アクティブ化イベントによってアクティブ化が処理されました。 Windows 10 では、対話型の ToastGeneric トーストが導入され、通知が数日保持されるアクション センターも導入されました。 アクション センターの導入では、作成した数日後にトーストをアクティブ化できるように、COM アクティベーターの導入が必要でした。

OS ToastGeneric COM アクティベーター 従来のトースト テンプレート
Windows 10 以降 サポートされています サポートされています サポートされています (ただし、COM サーバーはアクティブ化されません)
Windows 8.1 / 8 該当なし 該当なし サポートされています
Windows 7 以降 該当なし 該当なし 該当なし

Windows 10 以降で実行しているかどうかをチェックするには、<VersionHelpers.h>ヘッダーを含め、IsWindows10OrGreater メソッドをチェックします。 それがtrueを返した場合は、このドキュメントで説明されているすべてのメソッドを引き続き呼び出します。

#include <VersionHelpers.h>

if (IsWindows10OrGreater())
{
    // Running on Windows 10 or later, continue with sending toasts!
}

既知の問題

修正済み: トーストをクリックしてもアプリがフォーカスされない: ビルド 15063 以前では、COM サーバーをアクティブ化したときに、フォアグラウンド権限がアプリケーションに転送されませんでした。 そのため、フォアグラウンドに移動しようとすると、アプリが単にフラッシュします。 この問題の回避策はありませんでした。 私たちは ビルド 16299 以降でこれを修正しました。

リソース