title | description | ms.topic | ms.date | keywords | ms.localizationpriority |
---|---|---|---|---|---|
App notifications from UWP to WinUI 3 migration | This topic contains migration guidance in the app notifications feature area. | article | 12/14/2021 | Windows, App, SDK, migrate, migrating, migration, port, porting, push, notifications, toast, toast notifications, app notifications, uwp | medium |
The only difference when migrating app notification code from UWP to WinUI 3 is in handling the activation of notifications. Sending and managing app notifications remains exactly the same.
Note
The term "toast notification" is being replaced with "app notification". These terms both refer to the same feature of Windows, but over time we will phase out the use of "toast notification" in the documentation.
Note
Some information relates to pre-released product, which may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
Category | UWP | WinUI 3 |
---|---|---|
Foreground activation entry point | OnActivated method inside App.xaml.cs is called | OnLaunched method inside App.xaml.cs is called. |
Background activation entry point | Handled separately as a background task | Same as foreground activation. OnLaunched method inside App.xaml.cs is called. Use GetActivatedEventArgs to determine if the app should fully launch or just handle task and quit. |
Window activation | Your window is automatically brought to foreground when foreground activation occurs | You must bring your window to the foreground if desired |
Category | UWP | WinUI 3 |
---|---|---|
Foreground activation entry point | OnActivated method inside App.xaml.cs is called | Subscribe to ToastNotificationManagerCompat.OnActivated event (or COM class for C++) |
Background activation entry point | Handled separately as a background task | Arrives through same ToastNotificationManagerCompat.OnActivated event (or COM class for C++) |
Window activation | Your window is automatically brought to foreground when foreground activation occurs | You must bring your window to the foreground if desired |
For a WinUI 3 app, you handle activation for notifications by using the AppNotificationManager class. This class is provided by the Microsoft.WindowsAppSDK Nuget package, which is included by default in the WinUI 3 Visual Studio project templates.
[!INCLUDE nuget package]
This package adds the ToastNotificationManagerCompat
API.
In your Package.appxmanifest, add:
- Declaration for xmlns:com
- Declaration for xmlns:desktop
- In the IgnorableNamespaces attribute, com and desktop
- desktop:Extension for windows.toastNotificationActivation to declare your toast activator CLSID (using a new GUID of your choice).
- MSIX only: com:Extension for the COM activator using the GUID from step #4. Be sure to include the
Arguments="----AppNotificationActivated:"
so that you know your launch was from a notification
<!--Add these namespaces--> <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> <!--Specify which CLSID to activate when app notification clicked--> <desktop:ExtensionCategory="windows.toastNotificationActivation"> <desktop:ToastNotificationActivationToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> </desktop:Extension> <!--Register COM CLSID LocalServer32 registry key--> <com:ExtensionCategory="windows.comServer"> <com:ComServer> <com:ExeServerExecutable="YourProject.exe"Arguments="----AppNotificationActivated:"DisplayName="App notification activator"> <com:ClassId="replaced-with-your-guid-C173E6ADF0C3"DisplayName="App notification activator"/> </com:ExeServer> </com:ComServer> </com:Extension> </Extensions> </Application> </Applications> </Package>
<!--Add these namespaces--> <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> <!--Specify which CLSID to activate when toast clicked--> <desktop:ExtensionCategory="windows.toastNotificationActivation"> <desktop:ToastNotificationActivationToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> </desktop:Extension> <!--Register COM CLSID LocalServer32 registry key--> <com:ExtensionCategory="windows.comServer"> <com:ComServer> <com:ExeServerExecutable="YourProject.exe"Arguments="-ToastActivated"DisplayName="Toast activator"> <com:ClassId="replaced-with-your-guid-C173E6ADF0C3"DisplayName="Toast activator"/> </com:ExeServer> </com:ComServer> </com:Extension> </Extensions> </Application> </Applications> </Package>
In your app's startup code (typically App.xaml.cs), update your code using the following steps:
- In OnLaunched, get the default instance of the AppNotificationManager class.
- Register for the AppNotificationManager.NotificationInvoked event.
- Call Microsoft.Windows.AppNotifications.AppNotificationManager.Register to register your app to receive notification events. It is important that your call this method after registering the NotificationInvoked handler.
- Refactor your window launch/activation code into a dedicated
LaunchAndBringToForegroundIfNeeded
helper method, so you can call it from multiple places. - Create a
HandleNotification
helper method, so that it can be called from multiple places. - Call AppInstance.GetActivatedEventArgs and check the AppActivationArguments.Kind property of the returned object for the value ExtendedActivationKind.AppNotification.
- If the activation kind is not AppNotification call the LaunchAndBringToForegroundIfNeeded helper method.
- If the activation kind is AppNotification cast the AppActivationArguments.Data property to an AppNotificationActivatedEventArgs and pass it to the
HandleNotification
helper method. - In your ApplicationManager.NotificationInvoked handler, call the
HandleNotification
helper method. - Within your
HandleNotification
helper method, be sure to dispatch to the App or Window dispatcher before executing any UI-related code like showing a window or updating UI - Migrate your old UWP
OnActivated
code that handled app notification activation to your newHandleNotification
helper method.
protectedoverridevoidOnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgsargs){m_window=newMainWindow();// 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+=NotificationManager_NotificationInvoked;notificationManager.Register();varactivatedArgs=Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();varactivationKind=activatedArgs.Kind;if(activationKind!=ExtendedActivationKind.AppNotification){LaunchAndBringToForegroundIfNeeded();}else{HandleNotification((AppNotificationActivatedEventArgs)activatedArgs.Data);}}privatevoidLaunchAndBringToForegroundIfNeeded(){if(m_window==null){m_window=newMainWindow();m_window.Activate();// Additionally we show using our helper, since if activated via a app notification, it doesn't// activate the window correctly.WindowHelper.ShowWindow(m_window);}else{WindowHelper.ShowWindow(m_window);}}privatevoidNotificationManager_NotificationInvoked(AppNotificationManagersender,AppNotificationActivatedEventArgsargs){HandleNotification(args);}privatevoidHandleNotification(AppNotificationActivatedEventArgsargs){// Use the dispatcher from the window if present, otherwise the app dispatcher.vardispatcherQueue=m_window?.DispatcherQueue??DispatcherQueue.GetForCurrentThread();dispatcherQueue.TryEnqueue(asyncdelegate{if(args.Argument.Contains("action")){switch(args.Arguments["action"]){// Send a background message.case"sendMessage":stringmessage=args.UserInput["textBox"].ToString();// TODO: Send it.// If the UI app isn't open.if(m_window==null){// Close since we're done.Process.GetCurrentProcess().Kill();}break;// View a message.case"viewMessage":// Launch/bring window to foreground.LaunchAndBringToForegroundIfNeeded();// TODO: Open the message.break;}}else{Debug.Print("Notification args is null");}});}privatestaticclassWindowHelper{[DllImport("user32.dll")]privatestaticexternboolShowWindow(IntPtrhWnd,intnCmdShow);[DllImport("user32.dll")][return:MarshalAs(UnmanagedType.Bool)]privatestaticexternboolSetForegroundWindow(IntPtrhWnd);publicstaticvoidShowWindow(Windowwindow){// Bring the window to the foreground... first get the window handle...varhwnd=WinRT.Interop.WindowNative.GetWindowHandle(window);// Restore window if minimized... requires DLL import aboveShowWindow(hwnd,0x00000009);// And call SetForegroundWindow... requires DLL import aboveSetForegroundWindow(hwnd);}}
In your app's startup code (typically App.xaml.cs), , update your code using the following steps:
- Define and grab the app-level
DispatcherQueue
- Register for the
ToastNotificationManagerCompat.OnActivated
event - Refactor your window launch/activation code into a dedicated
LaunchAndBringToForegroundIfNeeded
method, so you can call it from multiple places - Avoid launching your window if
ToastNotificationManagerCompat.WasCurrentProcessToastActivated()
returns true (when that method is true, yourOnActivated
event will be called next and you can choose to show a window within the event callback). - Within your
ToastNotificationManagerCompat.OnActivated
, be sure to dispatch to the App or Window dispatcher before executing any UI-related code like showing a window or updating UI - Migrate your old UWP
OnActivated
code that handled toast activation to your newToastNotificationManagerCompat.OnActivated
event handler, and migrate any background task toast activation code to your newToastNotificationManagerCompat.OnActivated
event handler.
publicstaticDispatcherQueueDispatcherQueue{get;privateset;}protectedoverridevoidOnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgsargs){// Get the app-level dispatcher.DispatcherQueue=global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();// Register for toast activation. Requires Microsoft.Toolkit.Uwp.Notifications NuGet package version 7.0 or greater.ToastNotificationManagerCompat.OnActivated+=ToastNotificationManagerCompat_OnActivated;// If we weren't launched by an app, launch our window like normal.// Otherwise if launched by a toast, our OnActivated callback will be triggered.if(!ToastNotificationManagerCompat.WasCurrentProcessToastActivated()){LaunchAndBringToForegroundIfNeeded();}}privatevoidLaunchAndBringToForegroundIfNeeded(){if(m_window==null){m_window=newMainWindow();m_window.Activate();// Additionally we show using our helper, since if activated via a toast, it doesn't// activate the window correctly.WindowHelper.ShowWindow(m_window);}else{WindowHelper.ShowWindow(m_window);}}privatevoidToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompate){// Use the dispatcher from the window if present, otherwise the app dispatcher.vardispatcherQueue=m_window?.DispatcherQueue??App.DispatcherQueue;dispatcherQueue.TryEnqueue(delegate{varargs=ToastArguments.Parse(e.Argument);switch(args["action"]){// Send a background message.case"sendMessage":stringmessage=e.UserInput["textBox"].ToString();// TODO: Send it.// If the UI app isn't open.if(m_window==null){// Close since we're done.Process.GetCurrentProcess().Kill();}break;// View a message.case"viewMessage":// Launch/bring window to foreground.LaunchAndBringToForegroundIfNeeded();// TODO: Open the message.break;}});}privatestaticclassWindowHelper{[DllImport("user32.dll")]privatestaticexternboolShowWindow(IntPtrhWnd,intnCmdShow);[DllImport("user32.dll")][return:MarshalAs(UnmanagedType.Bool)]privatestaticexternboolSetForegroundWindow(IntPtrhWnd);publicstaticvoidShowWindow(Windowwindow){// Bring the window to the foreground... first get the window handle...varhwnd=WinRT.Interop.WindowNative.GetWindowHandle(window);// Restore window if minimized... requires DLL import aboveShowWindow(hwnd,0x00000009);// And call SetForegroundWindow... requires DLL import aboveSetForegroundWindow(hwnd);}}
With Windows App SDK, you can still create app notification content using raw xml, but you can also create app notification content using the new AppNotificationsBuilder API which replaces the ToastContentBuilder class provided by the Windows Community Toolkit. Send the app notification by calling AppNotificationManager.Show. Mixing Windows Community Toolkit and App SDK APIs is not recommended.
usingMicrosoft.Windows.AppNotifications;usingMicrosoft.Windows.AppNotifications.Builder; ...var builder =newAppNotificationBuilder().AddText("Send a message.").AddTextBox("textBox").AddButton(newAppNotificationButton("Send").AddArgument("action","sendMessage"));varnotificationManager=AppNotificationManager.Default;notificationManager.Show(builder.BuildNotification());
You can continue to use the ToastContentBuilder class provided by the Windows Community Toolkit both to create app notification content and to send notifications in a WinUI 3 app.
usingMicrosoft.Toolkit.Uwp.Notifications; ... new ToastContentBuilder().AddText("Send a message.").AddInputTextBox("textBox").AddButton(newToastButton().SetContent("Send").AddArgument("action","sendMessage")).Show();