從 WRL C++ 傳統型應用程式傳送本機快顯通知

封裝和解壓縮的桌面應用程式可以傳送互動式快顯通知,就像通用 Windows 平臺 (UWP) 應用程式可以一樣。 這包括已封裝的應用程式(請參閱為已封裝的 WinUI 3 傳統型應用程式建立新專案):具有外部位置的已封裝應用程式(請參閱使用外部位置封裝來授與套件身分識別]和未封裝的應用程式(請參閱為未封裝的 WinUI 3 傳統型應用程式建立新專案)。

不過,針對未封裝的桌面應用程式,有一些特殊步驟。 這是因為不同的啟用配置,以及運行時間缺少套件身分識別。

重要

如果您要撰寫 UWP 應用程式,請參閱 UWP 檔。 如需其他傳統型語言,請參閱 桌面 C#

步驟 1:啟用 Windows SDK

如果您尚未為應用程式啟用 Windows SDK,您必須先執行此動作。 有幾個關鍵步驟。

  1. 新增 runtimeobject.lib 至其他相 依性
  2. 以 Windows SDK 為目標。

以滑鼠右鍵按下您的項目,然後選取 [ 屬性]。

在頂端的 [組態] 功能表中,選取 [所有組態],讓下列變更同時套用至 [偵錯] 和 [發行]。

在 [鏈接器 - 輸入] 下,新增runtimeobject.lib[其他相依性]。>

然後在 [一般] 下,確定 Windows SDK 版本設定為 10.0 版或更新版本。

步驟 2:複製相容性連結庫程式碼

DesktopNotificationManagerCompat.hDesktopNotificationManagerCompat.cpp 檔案從 GitHub 複製到您的專案。 相容性連結庫會抽象化桌面通知的複雜度。 下列指示需要相容性連結庫。

如果您使用先行編譯標頭,請務必 #include "stdafx.h" 作為DesktopNotificationManagerCompat.cpp檔案的第一行。

步驟 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:實作啟動器

您必須實作快顯通知啟用的處理程式,如此一來,當使用者按兩下快顯通知時,您的應用程式就可以執行某些動作。 這需要您的快顯通知才能保存在控制中心(因為可以在幾天后關閉您的應用程式時按一下快顯通知)。 這個類別可以放在專案中的任何位置。

實作如下所示的 INotificationActivationCallback 介面,包括 UUID,並呼叫 CoCreatableClass 將您的類別標示為 COM creatable。 針對您的 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 傳統型應用程式建立新專案)或以外部位置封裝(請參閱 使用外部位置封裝來授與套件身分識別),或如果您同時支援這兩者,請在 Package.appxmanifest 中新增:

  1. xmlns:com 的 宣告
  2. xmlns:desktop 的 宣告
  3. 在 IgnorableNamespaces 屬性中comdesktop
  4. com:Extension for the COM activator using the GUID from step #4. 請務必包含 , 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 傳統型應用程式建立新專案),或如果您同時支援這兩者,則必須在 [開始] 中宣告應用程式使用者模型識別碼 (AUMID) 和快顯通知啟動程式 CLSID (步驟 4 中的 GUID)。

挑選可識別您應用程式的唯一 AUMID。 這通常是以 [CompanyName] 的形式。[AppName]。 但您想要確保所有應用程式都是唯一的(因此,您可以隨意在結尾新增一些數位)。

步驟 5.1:WiX 安裝程式

如果您使用WiX進行安裝程式,請編輯 Product.wxs 檔案,將兩個快捷方式屬性新增至 [開始] 功能表 快捷方式,如下所示。 請確定步驟 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));

如果您的應用程式同時支援已封裝和未封裝的部署,則不論如何,都能隨意呼叫此方法。 如果您正在執行封裝(也就是在運行時間使用套件身分識別),則此方法只會立即傳回。 不需要分支您的程序代碼。

此方法可讓您呼叫 compat API 來傳送和管理通知,而不需要持續提供您的 AUMID。 它會插入 COM 伺服器的 LocalServer32 登錄機碼。

步驟 6:註冊 COM 啟動器

針對已封裝和未封裝的應用程式,您必須註冊通知啟動器類型,才能處理快顯通知啟用。

在應用程式的啟動程序代碼中,呼叫下列 RegisterActivator 方法。 您必須呼叫此專案,才能接收任何快顯通知啟用。

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

步驟 7:傳送通知

傳送通知與 UWP 應用程式相同,不同之處在於您將使用 DesktopNotificationManagerCompat 來建立 ToastNotifier。 Compat 連結庫會自動處理已封裝和未封裝應用程式之間的差異,因此您不需要分支您的程式代碼。 針對未封裝的應用程式,compat 連結庫會快取您在呼叫 RegisterAumidAndComServer 時所提供的 AUMID,因此您不需要擔心何時提供或未提供 AUMID。

請確定您使用 ToastGeneric 系結,因為舊版 Windows 8.1 快顯通知範本不會啟動您在步驟 4 中建立的 COM 通知啟動器。

重要

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 方法內,您可以剖析您在快顯通知中指定的自變數,並取得使用者輸入或選取的使用者輸入,然後據以啟用您的應用程式。

注意

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 中的 Activate

如果您的應用程式未執行:

  1. 您的應用程式已啟動 EXE,您會收到 “-ToastActivated” 的命令行自變數
  2. 呼叫 NotificationActivator 中的 Activate

前景與背景啟用

針對傳統型應用程式,前景和背景啟用會以相同的方式處理—呼叫您的 COM 啟動器。 應用程式的程式代碼是決定要顯示視窗,還是只執行一些工作,然後結束。 因此,在快顯通知內容中指定背景activationType 並不會變更行為。

步驟 9:移除和管理通知

拿掉和管理通知與 UWP 應用程式相同。 不過,建議您使用我們的相容性連結庫來取得 DesktopNotificationHistoryCompat ,因此您不必擔心為傳統型應用程式提供 AUMID。

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.,請務必先在應用程式中呼叫 CoInitialize(nullptr) ,再呼叫 API。

如果您在呼叫 Compat API 時收到 HRESULT 0x8000000e A method was called at an unexpected time. ,這表示您無法呼叫必要的 Register 方法(或已封裝的應用程式,您目前並未在封裝的內容下執行您的應用程式)。

如果您收到許多unresolved external symbol編譯錯誤,您可能會忘記在步驟 1 中新增runtimeobject.lib至 [其他相依性] #1(或您只將其新增至偵錯組態,而不是發行組態)。

處理舊版 Windows

如果您支援 Windows 8.1 或更低版本,您會想要在運行時間檢查您是否在 Windows 上執行,再呼叫任何 DesktopNotificationManagerCompat API 或傳送任何 ToastGeneric 快顯通知。

Windows 8 引進快顯通知,但使用了 舊版快顯通知範本,例如 ToastText01。 啟用是由 ToastNotification 類別的記憶體內部 Activated 事件所處理,因為快顯通知只是未保存的簡短快顯。 Windows 10 引進 了互動式 ToastGeneric 快顯通知,也引進了控制中心,其中通知會保存數天。 控制中心的引進需要引進 COM 啟動器,以便您可以在建立快顯通知后幾天啟用快顯通知。

OS ToastGeneric COM 啟動器 舊版快顯通知範本
Windows 10 和更新版本 支援 支援 支援 (但不會啟用 COM 伺服器)
Windows 8.1 / 8 N/A N/A 支援
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!
}

已知問題

已修正:按兩下快顯通知之後,應用程式不會成為焦點:在組建15063和更早版本中,當我們啟動 COM 伺服器時,前景許可權不會傳送至您的應用程式。 因此,當您嘗試將應用程式移至前景時,只會閃爍。 此問題沒有因應措施。 我們已在組建16299或更新版本中修正此問題。

資源