共用方式為


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

已封裝和未封裝的桌面應用程式可以傳送互動式快顯通知,就像通用 Windows 平台 (UWP) 應用程式一樣。 這包括封裝應用程式 (請參閱為已封裝的 WinUI 3 桌面應用程式建立新專案)、使用外部位置的封裝應用程式 (請參閱使用外部位置進行封裝以授與封裝身分識別),以及未封裝應用程式 (請參閱為未封裝 WinUI 3 桌面應用程式建立新專案)。

不過,若是未封裝桌面應用程式,則要進行一些特殊步驟。 這是因為啟用方案不同,以及執行時期缺少封裝身分識別。

重要

如果您要編寫 UWP 應用程式,請參閱 UWP 文件。 如需其他桌面語言,請參閱桌面 C#

步驟 1:啟用 Windows SDK

如果您尚未啟用適用應用程式的 Windows SDK,則必須先執行此操作。 有幾個重要步驟要執行。

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

用滑鼠右鍵按一下您的專案,然後選取 [屬性]

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

[連結器] -> [輸入] 底下,將 runtimeobject.lib 新增至其他相依性

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

步驟 2:複製 compat 程式庫程式碼

從 GitHub 將 DesktopNotificationManagerCompat.hDesktopNotificationManagerCompat.cpp 檔案複製到您的專案中。 GitHub 程式庫會簡化桌面通知的許多複雜性。 下列指示需要 compat 程式庫。

如果您使用預先編譯的標頭,請務必 #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。 使用多種線上 GUID 產生器之一,為您的 UUID 建立唯一 UUID。 重要訊息中心會透過此 GUID CLSID (類別識別項) 得知要執行 COM activate 的類別。

// 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 啟動器的 com:Extension,使用步驟 #4 中的 GUID。 請務必包含 Arguments="-ToastActivated",以便得知您是從快顯啟動
  5. windows.toastNotificationActivationdesktop:Extension,以宣告您的快顯啟動器 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 指定為 background 並不會變更行為。

步驟 9:移除和管理通知

移除和管理通知的方式與 UWP 應用程式相同。 不過,建議您使用我們的 compat 程式庫來取得 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 新增至其他相依性 (或您只將它新增至 Debug 組態,而未新增至 Release 組態)。

處理舊版 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 或更新版本中修正此問題。

資源