-
Notifications
You must be signed in to change notification settings - Fork 413
Description
| Summary | Status |
|---|---|
Customer / job-to-be-done: App developers using WPF, WinForms, or Win32 C++ need to keep their users informed via toasts regardless of whether their app is currently open Problem: WPF apps must use the Community Toolkit to send notifications, C++ and Electron apps have told us they hate needing to have a shortcut and setting up notifications is difficult and they also have to use raw XML How does our solution make things easier? All types of apps will benefit from the easier experience WPF apps currently have through the Toolkit (no more shortcut, zero configuration work to send a notification, easy activation). WPF app developers will benefit from only having to reference and update one single NuGet package (Reunion) instead of two (Reunion+Toolkit) |
✅ Problem validation 🔄 Docs ❌ Dev spec ❌ Solution validation ❌ Implementation ❌ Samples |
Target customer
| Packaging | App types |
|---|---|
| ✅ Packaged apps ✅ Unpackaged apps |
✅ WPF ✅ Win32 (C++) ✅ WinForms ✅ Console ✅ UWP ⚠ Cross-plat (Electron, MAUI, React Native, etc) ⚠ PowerShell |
While this work should be consumable by cross-platform platforms to improve experiences there, we should have a separate feature specifically for ensuring notifications are built in "natively" and developers don't have to do additional custom work to access the Windows native layer.
Customer's job-to-be-done's
| Job-to-be-done | Validated? |
|---|---|
| Need to inform users about important events | ✅ Validated |
| Sometimes need to show images in notifications | ✅ Validated |
| Sometimes need buttons/inputs | ✅ Validated |
| Occasionally need really rich visual content and interactivity | ✅ Validated |
Problems that exist today
| Problem | Validated? |
|---|---|
| Start menu shortcut: Unpackaged apps need a Start menu shortcut and this is very painful for developers | ✅ People hate this. Hands down top feedback |
| Don't want to use MSIX: Switching to MSIX just to simply use notifications is too difficult/painful, I want to use it from my unpackaged app | ✅ Heard this from multiple customers |
| Manual config: Unpackaged apps need to manually config their COM server with their shortcut, this is very painful for developers | ✅ People struggle and get caught up here |
Sending differences: UWP and Win32 MSIX/sparse apps simply call CreateToastNotifier()... Win32 apps have to call CreateToastNotifier("MyAumid") |
✅ We've had several customers ask/be confused about which API they should use |
| XML is difficult: Programmatically building up an XML payload via code is difficult | ✅ C# apps love using the Toolkit for this reason (has nearly half a million downloads now) |
| Sending verbosity: The APIs to send a simple toast are quite verbose and require talking to a number of different classes and objects. Couldn't it be easier? | ✅ Several customers have asked us this specific question: why can't it be just one line of code |
| No HTTP images for unpackaged apps: Unpackaged apps have to download the image to disk | ✅ Several complaints about this |
| Activation of elevated process: When using toasts from an unpackaged elevated app, COM server doesn't activate as elevated | ✅ Heard several reports of this problem |
Summary
Along with simplifying the APIs for UWP apps, these new simplified APIs will also work for all Win32 apps, AND they will work down-level too, so that developers (UWP or Win32) can use them instantly! There will be no requirement to change existing code to the new APIs, and existing SDKs that worked before will still work with your apps.
To make the registration experience seamless for Win32 apps, we'll take on the heavy lifting of registering a Win32 app down-level, using their existing app assets (app name, icon). Developers simply have to call our new simplified APIs, and regardless of their app type, toasts will automatically work!
Quick links
Rationale
- See the problems today section above.
- Win32 app developers find the shortcut requirement the most frustrating/painful experience about using toasts today.
- Win32 toasts should be actionable from the action centre. A large set of win32 developers today display toasts that cannot be actioned on causing user dissatisfaction. We should make it easier for developers to properly support activation.
- We should not have to document and tell developers which technique to use to activate toasts based on OS versioning. The process should be abstracted and simplified behind a well know set of common APIs.
- Developers need not have to go and jump through hoops to display toasts using methodologies like Nitro. For example, Nitro puts a shortcut dependency on app developers which causes painpoints in packaging and deploying these shortcuts.
- Only 50% of Win32 developers are happy with today's documentation for Toasts and Activations.
- There are corner cases that don't work with COM servers that run under an elevated context: They need to setup COM access privileges and do special stuff for their scenarios to work when the APIs should be handling this for them.
Scope
| Capability | Priority |
|---|---|
| Common set of APIs across all app types to register and display toast notifications | Must |
| Easy, simple, and straightforward to use | Must |
| Full activation support for toasts when the app is closed for all App Types (Win32 and UWP) | Must |
| Can build toasts with rich UI functionality (Icons, themes, App names) | Must |
| Can build toasts using objects/builder syntax rather than XML documents | Must |
| Activation of apps running as admin "just works" | Must |
| PowerShell and Python scripts can easily use toasts | Must |
| Electron apps can easily use toasts | Must |
| "Portable" apps (ones that just run from EXE, not installed) are supported | Must |
| Same toast content builders must work on ASP.NET web servers for push notifications | Must |
| Scheduled toasts work | Must |
| Renaming your app doesn't cause you to lose your current notifications | Should |
| HTTP images supported for all app types | Should |
| Support toast collections | Could |
| Support multi-user apps | Could |
API experience
Here's a look at what (UWP) developers do today, compared to what we're proposing (any app type) developers will do using Reunion...
| Today | Tomorrow (Reunion) |
|---|---|
| Install Toolkit Notifications library | Install Reunion library |
| Create a ToastContentBuilder, add their content | Create a NotificationBuilder, add their content, call Show() |
| Create a ToastNotification using the XML from ToastContentBuilder | |
| Create a ToastNotifier using ToastNotificationManager | |
| Show the notification using ToastNotifier |
Today
var content = new ToastContentBuilder() // Toolkit Notifications library
.AddText("Hello from UWP!")
.GetToastContent();
var notif = new ToastNotification(content.GetXml()); // Platform APIs
ToastNotificationManager.CreateToastNotifier().Show(notif); // Platform APIsTomorrow (Reunion)*
new NotificationBuilder() // Reunion library
.AddText("Hello from WPF!")
.Show();To receive activation, we haven't been able to get a 100% converged experience...
- UWP apps: They would still use App.xaml.cs OnActivated as they do today
- Win32 MSIX: They would first have to add a COM server registration in their app manifest (maybe tooling can eliminate this somehow), and then they subscribe to
ToastNotificationManagerInterop.OnActivated - Win32 normal/sparse apps: They subscribe to
ToastNotificationManagerInterop.OnActivated
Sending toast API experience
First, developers would install the Reunion NuGet package.
Then, we're bringing in the toast XML object model that the Toolkit Notifications library has, so that you can have everything you need to easily construct toasts within one library! No manipulating XML necessary :) There will be a new ToastNotificationBuilder class, which allows you to create a toast using zero XML, set all the properties on it, and show it without calling the lengthy call-chain soup that today is ToastNotificationManager.CreateToastNotifier().Show()!
// Construct the notification and show it!
new NotificationBuilder()
.AddLaunchArgs("picOfHappyCanyon")
.AddText("Andrew sent you a picture")
.AddText("Check this out, Happy Canyon in Utah!")
.Show();Receiving activation API experience
UWP apps
UWP apps would receive activation as they do today, within their App.xaml.cs OnActivated method.
protected override void OnActivated(IActivatedEventArgs e)
{
// Handle toast activation
if (e is ToastNotificationActivatedEventArgs toastActivationArgs)
{
// Obtain the arguments from the toast
string args = toastActivationArgs.Argument;
// Obtain any user input (text boxes, menu selections) from the toast
ValueSet userInput = toastActivationArgs.UserInput;
// TODO: Show the corresponding content
}
}Win32 MSIX apps
First, in your Package.appxmanifest, add:
- Declaration for xmlns:com
- Declaration for xmlns:desktop
- In the IgnorableNamespaces attribute, com and desktop
- com:Extension for the COM activator using a new GUID of your choice. Be sure to include the Arguments="-ToastActivated" so that you know your launch was from a toast
- desktop:Extension for windows.toastNotificationActivation to declare your toast activator CLSID (the GUID from Update issue templates #4 above).
Then, in your app's startup code (App.xaml.cs OnStartup for WPF), subscribe to the OnActivated event.
// Listen to activation
AppLifecycle.OnActivated += AppLifecycle_OnActivated;
private void AppLifecycle_OnActivated(IActivatedEventArgs e)
{
// Handle notification activation
if (e is NotificationActivatedEventArgs toastActivationArgs)
{
// Obtain the arguments from the toast
string args = toastActivationArgs.Argument;
// Obtain any user input (text boxes, menu selections) from the toast
ValueSet userInput = toastActivationArgs.UserInput;
// TODO: Show the corresponding content
}
}Win32 or sparse apps
In your app's startup code (App.xaml.cs OnStartup for WPF), subscribe to the OnActivated event.
// Listen to activation
AppLifecycle.OnActivated += AppLifecycle_OnActivated;
private void AppLifecycle_OnActivated(IActivatedEventArgs e)
{
// Handle notification activation
if (e is NotificationActivatedEventArgs toastActivationArgs)
{
// Obtain the arguments from the toast
string args = toastActivationArgs.Argument;
// Obtain any user input (text boxes, menu selections) from the toast
ValueSet userInput = toastActivationArgs.UserInput;
// TODO: Show the corresponding content
}
}What about existing SDKs? Please don't break those!
I fully agree with you. If you're using a SDK that returns an XmlDocument or a ToastNotification, you'll still be able to use those, we'll provide APIs to allow you to pass those through to the new APIs.
API definitions
NOTE: These are OUTDATED, haven't been updated to the new Builder style
Introduce a new class...
Microsoft.UI.Notifications.ToastNotificationManagerInterop
Methods
| Method | Description | Return type | Min supported build | Supported app types |
|---|---|---|---|---|
| CreateToastNotifier() | Creates and initializes a new instance of the ToastNotifier that lets you raise a toast notification. | Windows.UI.Notifications.ToastNotifier |
10240 | All three |
| GetToastCollectionManager() | Creates a ToastCollectionManager that you can use to save, update, and clear notification groups. | Windows.UI.Notifications.ToastCollectionManager |
15063 | All three |
| CreateToastNotifierForToastCollectionAsync(string collectionId) | Creates and initializes a new instance of the ToastNotifier that lets you raise a toast notification within the specified toast collection. Note that the platform API is called GetToastNotifierForToastCollectionIdAsync, I changed "Get" to "Create" and dropped "Id" as it seems excessively verbose. | IAsyncOperation< Windows.UI.Notifications.ToastNotifier> |
15063 | All three |
| GetHistoryForToastCollectionAsync(string collectionId) | Gets the notification history for the specified toast collection. Note that I dropped the "Id" from the platform API since it seemed excessively verbose. | IAsyncOperation< Microsoft.UI.Notifications.ToastNotificationHistoryInterop> |
15063 | All three |
Omitted methods
We're explicitly omitting a few methods from the platform ToastNotificationManager and ToastNotificationManagerForUser...
| Method | Reason |
|---|---|
| CreateToastNotifier(string appId) | Only used by multi-app packages, which are rare or non-existent anymore... if we have requests for this we can always easily add it at any point in time |
| GetDefault() | 99% of apps are single-user apps, making 99% of developers always call GetDefault() is annoying. |
| GetForUser(Windows.System.User user) | Do we need to support MUA apps? |
| GetTemplateContent(ToastTemplateType) | These toast templates are from Windows 8, Windows 10 now uses ToastGeneric which we have builder classes for and this method is meaningless. |
| GetToastCollectionManager(string appId) | Only used by multi-app packages |
Properties
| Property | Description | Return type | Min supported build | Supported app types |
|---|---|---|---|---|
| History | Gets the ToastNotificationHistoryInterop object. | Microsoft.UI.Notifications.ToastNotificationHistoryInterop |
10240 | All three |
Events
| Event | Description | Args type | Min supported build | Supported app types |
|---|---|---|---|---|
| OnActivated | Event that is fired when a toast notification or action on a toast is clicked. This is not supported on UWP apps and will throw an exception if called from UWP. Win32 MSIX/sparse apps must first add values in their app manifest before calling this. | (Microsoft.UI.Notifications. ToastNotificationActivatedEventArgsInterop e) |
10240 | Win32 MSIX/sparse and Win32 (not UWP) |
And add another new class...
Microsoft.UI.Notifications.ToastNotificationHistoryInterop
| Method | Description | Return type | Min supported build | Supported app types |
|---|---|---|---|---|
| Clear() | Removes all notifications sent by this app from action center. | void |
10240 | All three |
| GetHistory() | Gets the collection of toasts currently in Action Center. Note: Should we change the name History? It's wonky, implies it'd include dismissed toasts. | IReadOnlyList< Windows.UI.Notifications.ToastNotification> |
10240 | All three |
| Remove(string tag) | Removes an individual toast, with the specified tag label, from action center. | void |
10240 | All three |
| Remove(string tag, string group) | Removes a toast notification from the action using the notification's tag and group labels. | void |
10240 | All three |
| RemoveGroup(string group) | Removes a group of toast notifications, identified by the specified group label, from action center. | void |
10240 | All three |
Omitted methods
We're explicitly omitting a few methods from the platform ToastNotificationHistory...
| Method | Reason |
|---|---|
| Clear(string appId) | Only used by multi-app packages |
| GetHistory(string appId) | Only used by multi-app packages |
| Remove(string tag, string group, string appId) | Only used by multi-app packages |
| Remove(string group, string appId) | Only used by multi-app packages |
And one more new class... (unfortunately we can't just instantiate ToastNotificationActivatedEventArgs).
Microsoft.UI.Notifications.ToastNotificationActivatedEventArgsInterop
This class will only be used by Win32 MSIX/sparse and Win32 apps... there's nothing stopping UWP apps from using it, but it just won't ever do anything or be sent to them.
Or ideally we should have a converged activation experience with the rest of Reunion...
Properties
| Property | Description | Return type | Min supported build | Supported app types |
|---|---|---|---|---|
| Argument | Gets the arguments that were originally specified on the toast corresponding to which action was taken on the toast. | string |
10240 | All three |
| UserInput | Gets the user inputs the user provided on the toast notification | ValueSet |
10240 | All three |
Implementation details
When the developer calls ToastNotificationManagerInterop.CreateToastNotifier(), we'll handle the differences between UWP/Win32 MSIX/spase and Win32.
If running with identity: We simply call CreateToastNotifier()
If not running with identity (Win32): We'll first register the app by obtaining its display name and icon and using the EXE path for the AUMID, and then we'll call CreateToastNotifier(aumid).
We'll do the same forking logic for when they access .History.
Uniquely and consistently obtaining an app's identity
We need to be able to uniquely identify (and consistently re-identify) a Win32 app so that we can register it with a stable identity.
Scenarios we should support are...
- EXE closed and re-opened
- App has two EXEs (like Notepad), regardless of which one runs they both should resolve to the same identity
- EXE's path changes upon app upgrade (Electron cases, see comment about 4 comments down)
- "Portable" apps where they aren't installed and EXE path might change
In all those cases, we should hopefully be able to keep the same identity.
How NotificationIcon (taskbar notification icons) identifies apps
Taskbar either uses hWnd + uId (where uId is an integer) or guidItem to uniquely identify the notification icon in the system tray (docs). There's a code sample here. You can only set hWnd and leave uId as default 0, that's the most minimal. It doesn't automatically infer your hWnd though.
Open questions
Would appreciate community feedback on any of the following!!
- Are Win32 apps okay with the same display assets they use on taskbar being used for toasts? Or do they want a way to specify custom display name/icons?
- Should the new OnActivated event be added to a
DesktopNotificationManagerInteropclass? It's not usable by UWP at all, and won't do anything on UWP (since UWP activation goes through the OnActivated App.xaml.cs method). Or maybe we just throw an exception when running on UWP to let the developer know to not use it (in addition to having an IntelliSense comment). - Do we need to support multi-user apps?
- Should methods be called
CreateToastNotifier()orGetToastNotifier()? For the new toast notifier for toast collections, we went with Get... should we converge but possibly make it slightly tougher to switch? Existing apps shouldn't need to switch to these new APIs anyways though. - Converged activation experience across all of Reunion?
- Should notifications from the dev-deployed version of the app be separate from notifications from the Installer-installed version of their app?
- One reason for NO is that app data itself is usually shared across the dev debugging version and the installed version of the app. For example, apps simply save their data to the AppData folder, picking a unique name, and they probably don't pick a different name for debug vs production installed. Electron's "userData" folder is just AppData[AppDisplayName], so it's the same across debug deployed and installed version.
Important Notes
PM gathered feedback from 12 developers on GitHub who used our Desktop notifications library today in Win32 non-packaged apps to learn what their pain points are, what approaches they would prefer, etc
• Overwhelmingly, people’s biggest pain point was needing to create the shortcut (only 2 found that easy)
• About 50/50 were happy with current COM activation.
• About 50/50 were happy with the current documentation experience
• When asked whether they’d prefer COM or EXE activation, of those who responded, most (4 developers) said COM, only one said EXE, two said in-memory callback
• Handling activation of COM server from an elevated process is a challenge/problem today and something we need to fix
• One developer was a scripting developer, we can’t forget the PowerShell community (there’s a BurntToast library for sending toasts via PowerShell, we can update that to use the new registration).
A sample of the docs for sending toasts are available here: Internal link / Public link