title | description | ms.topic | ms.date | keywords | ms.localizationpriority | ms.custom |
---|---|---|---|---|---|---|
Quickstart App notifications in the Windows App SDK | Send app notifications using the Windows App SDK | article | 12/27/2021 | toast, local, notification, windows app sdk, winappsdk | medium | template-quickstart |
In this quickstart, you will create a desktop Windows application that sends and receives local app notifications, also known as toast notifications, using the Windows App SDK.
Important
Notifications for an elevated (admin) app is currently not supported.
- Get started with WinUI
- Either Create a new project that uses the Windows App SDK OR Use the Windows App SDK in an existing project
This quickstart covers code from the notifications sample apps found on GitHub.
[!div class="button"] Sample App Code
For API reference documentation for app notifications, see Microsoft.Windows.AppNotifications Namespace.
Add the namespace for Windows App SDK app notifications Microsoft.Windows.AppNotifications
.
usingMicrosoft.Windows.AppNotifications;
If your app is unpackaged (that is, it lacks package identity at runtime), then skip to Step 3: Register to handle an app notification.
If your app is packaged (including packaged with external location):
- Open your Package.appxmanifest.
- Add
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
andxmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
namespaces to<Package>
- Add
<desktop:Extension>
forwindows.toastNotificationActivation
to declare your COM activator CLSID. You can obtain a CLSID by navigating to Create GUID under Tools in Visual Studio. - Add
<com:Extension>
for the COM activator using the same CLSID.- Specify your .exe file in the
Executable
attribute. The .exe file must be the same process callingRegister()
when registering your app for notifications, which is described more in Step 3. In the example below, we useExecutable="SampleApp\SampleApp.exe"
. - Specify
Arguments="----AppNotificationActivated:"
to ensure that Windows App SDK can process your notification's payload as an AppNotification kind. - Specify a
DisplayName
.
- Specify your .exe file in the
Important
Warning: If you define a Windows.Protocol app extensibility type in your appx manifest with <uap:Protocol>
, then clicking on notifications will launch new processes of the same app, even if your app is already running.
<!--Packaged apps only--><!--package.appxmanifest--> <Packagexmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" ... <Applications> <Application> ... <Extensions> <!--Specify which CLSID to activate when notification is clicked--> <desktop:ExtensionCategory="windows.toastNotificationActivation"> <desktop:ToastNotificationActivationToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> </desktop:Extension> <!--Register COM CLSID--> <com:ExtensionCategory="windows.comServer"> <com:ComServer> <com:ExeServerExecutable="SampleApp\SampleApp.exe"DisplayName="SampleApp"Arguments="----AppNotificationActivated:"> <com:ClassId="replaced-with-your-guid-C173E6ADF0C3" /> </com:ExeServer> </com:ComServer> </com:Extension> </Extensions> </Application> </Applications> </Package>
Register your app to handle notifications, then unregister when your app terminates.
In your App.xaml
file, register for AppNotificationManager::Default().NotificationInvoked, then call AppNotificationManager::Default().Register. The order of these calls matters.
Important
You must call AppNotificationManager::Default().Register before calling AppInstance.GetCurrent.GetActivatedEventArgs.
When your app is terminating, call AppNotificationManager::Default().Unregister() to free up the COM server and allow for subsequent invokes to launch a new process.
// App.xaml.csnamespaceCsUnpackagedAppNotifications{publicpartialclassApp:Application{privateWindowmainWindow;privateNotificationManagernotificationManager;publicApp(){this.InitializeComponent();notificationManager=newNotificationManager();AppDomain.CurrentDomain.ProcessExit+=newEventHandler(OnProcessExit);}protectedoverridevoidOnLaunched(LaunchActivatedEventArgsargs){mainWindow=newMainWindow();notificationManager.Init();// Complete in Step 5mainWindow.Activate();}voidOnProcessExit(objectsender,EventArgse){notificationManager.Unregister();}}}// NotificationManager.csnamespaceCsUnpackagedAppNotifications{internalclassNotificationManager{privateboolm_isRegistered;privateDictionary<int,Action<AppNotificationActivatedEventArgs>>c_map;publicNotificationManager(){m_isRegistered=false;// When adding new a scenario, be sure to add its notification handler here.c_map=newDictionary<int,Action<AppNotificationActivatedEventArgs>>();c_map.Add(ToastWithAvatar.ScenarioId,ToastWithAvatar.NotificationReceived);c_map.Add(ToastWithTextBox.ScenarioId,ToastWithTextBox.NotificationReceived);}~NotificationManager(){Unregister();}publicvoidInit(){// To ensure all Notification handling happens in this process instance, register for// NotificationInvoked before calling Register(). Without this a new process will// be launched to handle the notification.AppNotificationManagernotificationManager=AppNotificationManager.Default;notificationManager.NotificationInvoked+=OnNotificationInvoked;notificationManager.Register();m_isRegistered=true;}publicvoidUnregister(){if(m_isRegistered){AppNotificationManager.Default.Unregister();m_isRegistered=false;}}publicvoidProcessLaunchActivationArgs(AppNotificationActivatedEventArgsnotificationActivatedEventArgs){// Complete in Step 5}}}
// App.xaml.cpp// NotificationManager is responsible for registering and unregistering the Sample for App Notifications as well as// dispatching actioned notifications to the appropriate scenario.// Registration will happen when Init() is called and Unregistration will happen when this// instance variable goes out of scope, i.e.: when the App is terminated.static NotificationManager g_notificationManager; namespacewinrt::CppUnpackagedAppNotifications::implementation { App::App() { InitializeComponent(); } std::wstring App::GetFullPathToExe() { TCHAR buffer[MAX_PATH] = { 0 }; GetModuleFileName(NULL, buffer, MAX_PATH); std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/"); returnstd::wstring(buffer).substr(0, pos); } std::wstring App::GetFullPathToAsset(std::wstring const& assetName) { returnGetFullPathToExe() + L"\\Assets\\" + assetName; } voidApp::OnLaunched(winrt::Microsoft::UI::Xaml::LaunchActivatedEventArgs const& /*args*/) { window = make<MainWindow>(); g_notificationManager.Init(); // Complete in Step 5 window.Activate(); } } // NotificationManager.cppstaticconst std::map<unsigned, std::function<void (winrt::AppNotificationActivatedEventArgs const&)>> c_map { // When adding new a scenario, be sure to add its notification handler here. { ToastWithAvatar::ScenarioId, ToastWithAvatar::NotificationReceived }, { ToastWithTextBox::ScenarioId, ToastWithTextBox::NotificationReceived } }; NotificationManager::NotificationManager():m_isRegistered(false){} NotificationManager::~NotificationManager() { if (m_isRegistered) { winrt::AppNotificationManager::Default().Unregister(); } } voidNotificationManager::Init() { auto notificationManager{ winrt::AppNotificationManager::Default() }; // Always setup the notification hanlder before registering your App, otherwise notifications may get lost.constauto token{ notificationManager.NotificationInvoked([&](constauto&, winrt::AppNotificationActivatedEventArgs const& notificationActivatedEventArgs) { NotifyUser::NotificationReceived(); if (!DispatchNotification(notificationActivatedEventArgs)) { NotifyUser::UnrecognizedToastOriginator(); } }) }; winrt::AppNotificationManager::Default().Register(); m_isRegistered = true; } voidNotificationManager::ProcessLaunchActivationArgs(winrt::AppNotificationActivatedEventArgs const& notificationActivatedEventArgs) { // Complete in Step 5 }
You MUST complete Step 3: Register to handle an app notification before proceeding.
Now you will display a simple app notification with an appLogoOverride
image and a button.
Construct your app notification using the AppNotificationBuilder class and then call Show
. For more information on how to construct your app notification using XML, please refer to the examples at Toast content and the Notifications XML schema.
Note
If your app is packaged (including packaged with external location), then your app's icon in the notification's upper left corner is sourced from the package.manifest
. If your app is unpackaged, then the icon is sourced by first looking into the shortcut, then looking at the resource file in the app process. If all attempts fail, then the Windows default app icon is used. The supported icon file types are .jpg
, .png
, .bmp
, and .ico
.
// ToastWithAvatar.csclassToastWithAvatar{publicconstintScenarioId=1;publicconststringScenarioName="Local Toast with Avatar Image";publicstaticboolSendToast(){varappNotification=newAppNotificationBuilder().AddArgument("action","ToastClick").AddArgument(Common.scenarioTag,ScenarioId.ToString()).SetAppLogoOverride(newSystem.Uri("file://"+App.GetFullPathToAsset("Square150x150Logo.png")),AppNotificationImageCrop.Circle).AddText(ScenarioName).AddText("This is an example message using XML").AddButton(newAppNotificationButton("Open App").AddArgument("action","OpenApp").AddArgument(Common.scenarioTag,ScenarioId.ToString())).BuildNotification();AppNotificationManager.Default.Show(appNotification);returnappNotification.Id!=0;// return true (indicating success) if the toast was sent (if it has an Id)}publicstaticvoidNotificationReceived(AppNotificationActivatedEventArgsnotificationActivatedEventArgs){// Complete in Step 5 }}// Call SendToast() to send a notification.
// ToastWithAvatar.cppboolToastWithAvatar::SendToast() { auto appNotification{ winrt::AppNotificationBuilder() .AddArgument(L"action", L"ToastClick") .AddArgument(Common::scenarioTag, std::to_wstring(ToastWithAvatar::ScenarioId)) .SetAppLogoOverride(winrt::Windows::Foundation::Uri(L"file://" + winrt::App::GetFullPathToAsset(L"Square150x150Logo.png")), winrt::AppNotificationImageCrop::Circle) .AddText(ScenarioName) .AddText(L"This is an example message using XML") .AddButton(winrt::AppNotificationButton(L"Open App") .AddArgument(L"action", L"OpenApp") .AddArgument(Common::scenarioTag, std::to_wstring(ToastWithAvatar::ScenarioId))) .BuildNotification() }; winrt::AppNotificationManager::Default().Show(appNotification); return appNotification.Id() != 0; // return true (indicating success) if the toast was sent (if it has an Id) } voidToastWithAvatar::NotificationReceived(winrt::Microsoft::Windows::AppNotifications::AppNotificationActivatedEventArgs const& notificationActivatedEventArgs) { // Complete in Step 5 } // Call SendToast() to send a notification.
Users can select your notification's body or button. Your app needs to process the invocation in response to a user interacting with your notification.
There are 2 common ways to process this:
- You choose to have your app launch in a specific UI context OR
- You choose to have your app evaluate an action-specific behavior (like a button press in the notification body) without rendering any UI. Also known as a background action.
The code example below, which is not from the sample app, illustrates both ways of processing a user-generated action. Add a launch
value (corresponds to user clicking the notification body), an input
element (quick reply text box), and a button with an arguments
value (corresponds to user clicking the button) to your notification's XML payload. In your ProcessLaunchActivationArgs
, case on each argument.
Important
Setting activationType="background"
in the notification XML payload is ignored for desktop apps. You must instead process the activation arguments and decide whether to display a window or not, as stated in this step.
// Example of how to process a user either selecting the notification body or inputting a quick reply in the text box. // Notification XML payload//<toast launch="action=openThread&threadId=92187">// <visual>// <binding template="ToastGeneric">// <image placement="appLogoOverride" hint-crop="circle" src="C:\<fullpath>\Logo.png"/>// <text>Local Toast with Avatar and Text box</text>// <text>This is an example message using</text>// </binding>// </visual>// <actions>// <input id="replyBox" type="text" placeHolderContent="Reply" />// <action// content="Send"// hint-inputId="replyBox"// arguments="action=reply&threadId=92187" />// </actions>//</toast>voidProcessLaunchActivationArgs(const winrt::AppNotificationActivatedEventArgs& notificationActivatedEventArgs) { // If the user clicks on the notification body, your app needs to launch the chat thread windowif (std::wstring(notificationActivatedEventArgs.Argument().c_str()).find(L"openThread") != std::wstring::npos) { GenerateChatThreadWindow(); } else// If the user responds to a message by clicking a button in the notification, your app needs to reply back to the other user with no window launchedif (std::wstring(notificationActivatedEventArgs.Argument().c_str()).find(L"reply") != std::wstring::npos) { auto input = notificationActivatedEventArgs.UserInput(); auto replyBoxText = input.Lookup(L"replyBox"); // Process the reply textSendReplyToUser(replyBoxText); } }
Follow the below guidelines:
- If a notification is selected by the user and your app is not running, it is expected that your app is launched and the user can see the foreground window in the notification's context.
- If a notification is selected by the user and your app is minimized, it is expected that your app is brought to the foreground and a new window is rendered in the notification's context.
- If a notification background action is invoked by the user (e.g. the user responds to a notification by typing in the notification text box and hitting reply), your app processes the payload without rendering a foreground window.
See the sample app code found on GitHub for a more detailed example.
Remove notifications when they are no longer relevant to the user.
In this example, the user has seen all messages from a group chat in your app, so you clear all notifications from the group chat. Then, the user mutes a friend, so you clear all notifications from the friend. You first added the Group and Tag properties to the notifications before displaying in order to identify them now.
voidSendNotification(winrt::hstring const& payload, winrt::hstring const& friendId, winrt::hstring const& groupChatId) { winrt::AppNotification notification(payload); // Setting Group Id here allows clearing notifications from a specific chat group later notification.Group(groupChatId); // Setting Tag Id here allows clearing notifications from a specific friend later notification.Tag(friendId); winrt::AppNotificationManager::Default().Show(notification); } winrt::Windows::Foundation::IAsyncAction RemoveAllNotificationsFromGroupChat(const std::wstring groupChatId) { winrt::AppNotificationManager manager = winrt::AppNotificationManager::Default(); co_await manager.RemoveByGroupAsync(groupChatId); } winrt::Windows::Foundation::IAsyncAction RemoveAllNotificationsFromFriend(const std::wstring friendId) { winrt::AppNotificationManager manager = winrt::AppNotificationManager::Default(); co_await manager.RemoveByTagAsync(friendId); }
To send an app notification from the cloud, follow Send a cloud-sourced app notification at Quickstart: Push notifications in the Windows App SDK.
Set an expiration time on your app notification using the Expiration
property if the message in your notification is only relevant for a certain period of time. For example, if you send a calendar event reminder, set the expiration time to the end of the calendar event.
Note
The default and maximum expiration time is 3 days.
classToastWithAvatar{publicstaticboolSendToast(){varappNotification=newAppNotificationBuilder().SetAppLogoOverride(newSystem.Uri("ms-appx:///images/logo.png"),AppNotificationImageCrop.Circle).AddText("Example expiring notification").AddText("This is an example message").BuildNotification();appNotification.Expiration=DateTime.Now.AddDays(1);AppNotificationManager.Default.Show(appNotification);returnappNotification.Id!=0;// return true (indicating success) if the toast was sent (if it has an Id)}}
Set the ExpiresOnReboot
property to True if you'd like notifications to delete on reboot.
classToastWithAvatar{publicstaticboolSendToast(){varappNotification=newAppNotificationBuilder().SetAppLogoOverride(newSystem.Uri("ms-appx:///images/logo.png"),AppNotificationImageCrop.Circle).AddText("Example ExpiresOnReboot notification").AddText("This is an example message").BuildNotification();appNotification.ExpiresOnReboot=true;AppNotificationManager.Default.Show(appNotification);returnappNotification.Id!=0;// return true (indicating success) if the toast was sent (if it has an Id)}}
boolSendToast() { auto appNotification{ winrt::AppNotificationBuilder() .SetAppLogoOverride(winrt::Windows::Foundation::Uri(L"ms-appx:///images/logo.png"), winrt::AppNotificationImageCrop::Circle) .AddText(L"Example ExpiresOnReboot notification") .AddText(L"This is an example message") .BuildNotification() }; appNotification.ExpiresOnReboot(); winrt::AppNotificationManager::Default().Show(appNotification); return appNotification.Id() != 0; // return true (indicating success) if the toast was sent (if it has an Id) }
You can display progress bar related updates in a notification:
Use the AppNotificationProgressData
construct to update the progress bar notification.
const winrt::hstring c_tag = L"weekly-playlist"; const winrt::hstring c_group = L"downloads"; // Send first Notification Progress UpdatevoidSendUpdatableNotificationWithProgress() { auto notification{ winrt::AppNotificationBuilder() .AddText(L"Downloading this week's new music...") .AddProgressBar(winrt::AppNotificationProgressBar() .BindTitle() .BindValue() .BindValueStringOverride() .BindStatus()) .BuildNotification() } notification.Tag(c_tag); notification.Group(c_group); // Assign initial values for first notification progress UI winrt::AppNotificationProgressData data(1); // Sequence number data.Title(L"Weekly playlist"); // Binds to {progressTitle} in xml payload data.Value(0.6); // Binds to {progressValue} in xml payload data.ValueStringOverride(L"15/26 songs"); // Binds to {progressValueString} in xml payload data.Status(L"Downloading..."); // Binds to {progressStatus} in xml payload notification.Progress(data); winrt::AppNotificationManager::Default().Show(notification); } // Send subsequent progress updates winrt::Windows::Foundation::IAsyncAction UpdateProgressAsync() { // Assign new values winrt::AppNotificationProgressData data(2/* Sequence number */ ); data.Title(L"Weekly playlist"); // Binds to {progressTitle} in xml payload data.Value(0.7); // Binds to {progressValue} in xml payload data.ValueStringOverride(L"18/26 songs"); // Binds to {progressValueString} in xml payload data.Status(L"Downloading..."); // Binds to {progressStatus} in xml payloadauto result = co_awaitwinrt::AppNotificationManager::Default().UpdateAsync(data, c_tag, c_group); if (result == winrt::AppNotificationProgressResult::AppNotificationNotFound) { // Progress Update failed since the previous notification update was dismissed by the user! So account for this in your logic by stopping updates or starting a new Progress Update flow. } }