從 WRL C++ WRL 桌面應用程式傳送本機快顯通知
已封裝和未封裝的桌面應用程式可以傳送互動式快顯通知,就像通用 Windows 平台 (UWP) 應用程式一樣。 這包括封裝應用程式 (請參閱為已封裝的 WinUI 3 桌面應用程式建立新專案)、使用外部位置的封裝應用程式 (請參閱使用外部位置進行封裝以授與封裝身分識別),以及未封裝應用程式 (請參閱為未封裝 WinUI 3 桌面應用程式建立新專案)。
不過,若是未封裝桌面應用程式,則要進行一些特殊步驟。 這是因為啟用方案不同,以及執行時期缺少封裝身分識別。
步驟 1:啟用 Windows SDK
如果您尚未啟用適用應用程式的 Windows SDK,則必須先執行此操作。 有幾個重要步驟要執行。
- 將
runtimeobject.lib
新增至其他相依性。 - 以 Windows SDK 為目標。
用滑鼠右鍵按一下您的專案,然後選取 [屬性]。
在頂端 [組態] 功能表中,選取 [所有組態],讓下列變更同時套用至 Debug 和 Release。
在 [連結器] -> [輸入] 底下,將 runtimeobject.lib
新增至其他相依性。
然後在 [一般] 底下,確認 [Windows SDK 版本] 設定為 10.0 版或更新版本。
步驟 2:複製 compat 程式庫程式碼
從 GitHub 將 DesktopNotificationManagerCompat.h 和 DesktopNotificationManagerCompat.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 中新增:
- xmlns:com 宣告
- xmlns:desktop 宣告
- 在 IgnorableNamespaces 屬性中,com 和 desktop
- COM 啟動器的 com:Extension,使用步驟 #4 中的 GUID。 請務必包含
Arguments="-ToastActivated"
,以便得知您是從快顯啟動 - windows.toastNotificationActivation 的 desktop: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(¬ifier);
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);
}
事件的啟用順序
啟用順序如下...
如果您的應用程式已在執行:
- 會呼叫 NotificationActivator 中的 Activate
如果您的應用程式未執行:
- 您的應用程式透過 EXE 啟動,您會收到命令冽引數「-ToastActivated」
- 會呼叫 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 或更新版本中修正此問題。