次の方法で共有


WRL C++ デスクトップからローカル app 通知を送信する app

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

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

Note

"toast 通知" という用語は、"app 通知" に置き換えられます。 これらの用語はどちらも Windows の同じ機能を指しますが、時間の経過と共に、ドキュメント内の "toast 通知" の使用を段階的に廃止します。

Important

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

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

appに対して 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: ヘッダー ファイルと名前空間を含める

互換性ライブラリのヘッダー ファイルと、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: アクティベーターを実装する

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

次に示すように、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: 通知プラットフォームに登録する

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

Packaged

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

  1. xmlns:com の宣言
  2. xmlns:desktop の宣言
  3. IgnorableNamespaces 属性で、comデスクトップ が無視されます。
  4. 手順 4 の GUID を使用して、COM アクティベーターの の com:Extension を行います。 起動がArguments="-ToastActivated"通知から行われたことがわかっているように、必ずappを含めます
  5. desktop:Extension 用の 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>

Unpackaged

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

appを識別する一意の 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>

Important

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

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

その後、インストーラーに関係なく、 appのスタートアップ コードで (通知 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));

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

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

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

パッケージ化されたアプリとパッケージ化されていないアプリの両方で、通知アクティベーターの種類を登録して、 toast アクティブ化を処理できるようにする必要があります。

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

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

手順 7: 通知を送信する

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

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

Important

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

// 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());
        }
    }
}

Important

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

ステップ 8: アクティベーションを処理する

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

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

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);

appが閉じられている間の起動を適切にサポートするには、WinMain 関数で、app通知から起動されているかどうかを判断する必要があります。 通知から起動すると、"-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);
}

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

アクティブ化シーケンスは次のとおりです。...

appが既に実行されている場合:

  1. NotificationActivator でアクティブにし、 を呼び出す

appが実行されていない場合:

  1. app EXE が起動すると、"-ToastActivated" というコマンドライン引数を受け取ります。
  2. NotificationActivator でアクティブにし、 を呼び出す

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

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

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

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

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: デプロイとデバッグ

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

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

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

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

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

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

Compat API の呼び出し中に HRESULT 0x8000000e A method was called at an unexpected time. を受け取った場合は、必要な Register メソッドの呼び出しに失敗した可能性があります (または、パッケージ化された appの場合は、パッケージ化されたコンテキストで現在 app を実行していません)。

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

以前のバージョンの Windows を処理する

Windows 8.1 以前をサポートしている場合は、DesktopNotificationManagerCompat API を呼び出すか、ToastGeneric トーストを送信する前に、実行時に Windows で実行しているかどうかを確認する必要があります。

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

OS ToastGeneric COM アクティベーター レガシ toast テンプレート
Windows 10 以降 Supported Supported サポートされています (ただし、COM サーバーはアクティブ化されません)
Windows 8.1 / 8 N/A N/A Supported
Windows 7 以前 N/A N/A N/A

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

#include <VersionHelpers.h>

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

既知の問題

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

Resources