Skip to content

Commit 66be202

Browse files
author
msftbot[bot]
authored
New Win32 toast notification helpers (no more shortcut needed!) (#3457)
## Fixes # **Dramatic Win32 improvements!** It's finally effortless for Win32 apps now! * **No longer need a start menu shortcut!** - This was the biggest complaint * **COM activator is automatically registered** - Simply subscribe to the OnActivated callback **Before for Win32 apps** 1. Generate a new GUID and AUMID 1. Update your shortcut to use your GUID and AUMID 1. Create a NotificationActivator class using your GUID 1. Register your AUMID and COM server 1. Register your activator 1. You can finally now send notifications ```csharp // (Omitted - setting up your shortcut using your installer) // Register AUMID and COM server DesktopNotificationManagerCompat.RegisterAumidAndComServer<MyNotificationActivator>("YourCompany.YourApp"); // Register COM server and activator type DesktopNotificationManagerCompat.RegisterActivator<MyNotificationActivator>(); // The GUID CLSID must be unique to your app. Create a new GUID if copying this code. [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(INotificationActivationCallback))] [Guid("replaced-with-your-guid-C173E6ADF0C3"), ComVisible(true)] public class MyNotificationActivator : NotificationActivator { public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId) { // TODO: Handle activation } } // Finally can send... ``` **After for Win32 apps** 1. Send a notification! Literally just install the package and send away. To receive activation, subscribe to OnActivated. No GUIDs, no AUMIDs, all super straightforward. ```csharp new ToastContentBuilder() .AddText("Hello world") .Show(); // Listen to toast notification activations ToastNotificationManagerCompat.OnActivated += e => { // TODO: Handle activation } ``` **Also simplifies the APIs for showing toasts!** New Show() and Schedule() methods on ToastContentBuilder to simplify showing toasts. **Before** ```csharp var content = new ToastContentBuilder() .AddText("Hello world") .GetToastContent(); var notif = new ToastNotification(content.GetXml()); ToastNotificationManager.CreateToastNotifier().Show(notif); ``` **After** (Plus now both Win32 and UWP can use the exact same API) ```csharp new ToastContentBuilder() .AddText("Hello world") .Show(); ``` Customization of the toast notification can still be done using an overload ```csharp new ToastContentBuilder() .AddText("Hello world") .Show(notif => { notif.Tag = "myTag"; }); ``` Also on ToastContentBuilder *added*... `.AddToastActivationInfo("myArgs")` defaults to Foreground so don't have to specify that 95% of the time, also added option for adding a ToastAudio object `AddAudio(new ToastAudio())` (useful for if you want to specify a silent toast, otherwise that's impossible today). ## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> <!-- - Bugfix --> Feature <!-- - Code style update (formatting) --> <!-- - Refactoring (no functional changes, no api changes) --> <!-- - Build or CI related changes --> <!-- - Documentation content changes --> <!-- - Sample app changes --> <!-- - Other... Please describe: --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> See above ## What is the new behavior? <!-- Describe how was this issue resolved or changed? --> See above. Can now send toasts without having to set up a start menu shortcut... in fact, can send notifications without setting up anything! ### High level changes * New `Show()` method added to `ToastContentBuilder` for easily showing a toast (works with Win32 apps too) * New `Schedule()` method added to `ToastContentBuilder` for easily scheduling a toast (works with Win32 apps too) * New `ToastNotificationManagerCompat` class added for converging Win32 and UWP API surface, and drastically simplifying sending toasts from Win32 (no longer need start menu shortcut, don't need to create the COM activator) * New `GetXml()` method added to `ToastContentBuilder` for obtaining XML without having to call `GetToastContent().GetXml()` * Made the activationType optional in `AddToastActivationInfo` so can set foreground activation more concisely (95% of time it's foreground activation) * Added a `ToastAudio` overload for `AddAudio` so can set silent audio * C++ UWP support for ToastContentBuilder class * `ToastArguments` is now part of the toolkit (don't need to use an external query string library) The `DesktopNotificationManagerCompat` classes have been left identical for back-compat purposes. When developers update their package, they do NOT have to make any changes if they don't want to. There are [migration steps described here](https://review.docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/migrating-to-toastcompat?branch=aleader%2Fconverged-uwp-win32&tabs=uwp). ## PR Checklist Please check if your PR fulfills the following requirements: - [x] Tested code with current [supported SDKs](../readme.md#supported) - [x] Tested with sparse-signed package - [x] Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). [Preview docs](https://docstaging.z5.web.core.windows.net/aleader/toolkit-7/design/shell/tiles-and-notifications/send-local-toast.html?tabs=uwp) - [x] Sample in sample app has been added / updated (for bug fixes / features) - [x] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets) - [x] Tests for the changes have been added (for bug fixes / features) (if applicable) - [x] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [x] Contains **NO** breaking changes <!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. Please note that breaking changes are likely to be rejected. --> ## How verified * Tested NuGet package from CI server on all test apps within here: https://github.com/andrewleader/NotifTestApps * Includes UWP, .NET Core, and .NET Framework apps, and legacy versions of .NET Core and .NET Framework apps for testing back-compat and zero breaking changes * Tested on commit 04a7e71 * Also tested with sparse signed package sample
2 parents 8315ffb + aa6e892 commit 66be202

File tree

56 files changed

+4585
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+4585
-188
lines changed

Microsoft.Toolkit.Uwp.Notifications/Adaptive/AdaptiveProgressBarValue.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ namespace Microsoft.Toolkit.Uwp.Notifications
1111
/// </summary>
1212
public sealed class AdaptiveProgressBarValue
1313
{
14+
/// <summary>
15+
/// Gets or sets the property name to bind to.
16+
/// </summary>
17+
public string BindingName { get; set; }
18+
1419
/// <summary>
1520
/// Gets or sets the value (0-1) representing the percent complete.
1621
/// </summary>
@@ -35,6 +40,11 @@ internal string ToXmlString()
3540
return "indeterminate";
3641
}
3742

43+
if (BindingName != null)
44+
{
45+
return "{" + BindingName + "}";
46+
}
47+
3848
return Value.ToString();
3949
}
4050

@@ -69,5 +79,18 @@ public static AdaptiveProgressBarValue FromValue(double d)
6979
Value = d
7080
};
7181
}
82+
83+
/// <summary>
84+
/// Returns a progress bar value using the specified binding name.
85+
/// </summary>
86+
/// <param name="bindingName">The property to bind to.</param>
87+
/// <returns>A progress bar value.</returns>
88+
public static AdaptiveProgressBarValue FromBinding(string bindingName)
89+
{
90+
return new AdaptiveProgressBarValue()
91+
{
92+
BindingName = bindingName
93+
};
94+
}
7295
}
7396
}

Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,17 @@
2525
</ItemGroup>
2626
<PropertyGroup>
2727
<!--Define the WINDOWS_UWP conditional symbol, since the Windows.Data.Xml and the Windows.UI.Notification namespaces are available-->
28-
<DefineConstants>$(DefineConstants);WINDOWS_UWP</DefineConstants>
28+
<DefineConstants>$(DefineConstants);WINDOWS_UWP;WIN32</DefineConstants>
2929
</PropertyGroup>
3030
</When>
3131

32-
<!--Non-desktop apps (UWP, libraries, ASP.NET servers)-->
33-
<Otherwise>
34-
<ItemGroup>
35-
<!--Remove the DesktopNotificationManager code-->
36-
<Compile Remove="DesktopNotificationManager\**\*" />
37-
</ItemGroup>
38-
</Otherwise>
39-
4032
</Choose>
4133

42-
<!--NET Core desktop apps also need the Registry NuGet package-->
34+
<!--NET Core desktop apps also need the Registry NuGet package and System.Reflection.Emit for generating COM class dynamically-->
4335
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
4436
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
37+
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
38+
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
4539
</ItemGroup>
4640

4741
<ItemGroup Condition=" '$(TargetFramework)' == 'native' ">

Microsoft.Toolkit.Uwp.Notifications/Tiles/TileCommon.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ public enum TileSize
1515
/// <summary>
1616
/// Small Square Tile
1717
/// </summary>
18-
Small = 0,
18+
Small = 1,
1919

2020
/// <summary>
2121
/// Medium Square Tile
2222
/// </summary>
23-
Medium = 1,
23+
Medium = 2,
2424

2525
/// <summary>
2626
/// Wide Rectangle Tile
2727
/// </summary>
28-
Wide = 2,
28+
Wide = 4,
2929

3030
/// <summary>
3131
/// Large Square Tile
3232
/// </summary>
33-
Large = 4
33+
Large = 8
3434
}
3535
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#if WINDOWS_UWP
6+
7+
using Windows.Foundation;
8+
using Windows.UI.Notifications;
9+
10+
namespace Microsoft.Toolkit.Uwp.Notifications
11+
{
12+
/// <summary>
13+
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
14+
/// </summary>
15+
/// <param name="toast">The toast to modify that will be displayed.</param>
16+
public delegate void CustomizeToast(ToastNotification toast);
17+
18+
/// <summary>
19+
/// Allows you to set additional properties on the <see cref="ToastNotification"/> object before the toast is displayed.
20+
/// </summary>
21+
/// <param name="toast">The toast to modify that will be displayed.</param>
22+
/// <returns>An operation.</returns>
23+
public delegate IAsyncAction CustomizeToastAsync(ToastNotification toast);
24+
25+
/// <summary>
26+
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
27+
/// </summary>
28+
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
29+
public delegate void CustomizeScheduledToast(ScheduledToastNotification toast);
30+
31+
/// <summary>
32+
/// Allows you to set additional properties on the <see cref="ScheduledToastNotification"/> object before the toast is scheduled.
33+
/// </summary>
34+
/// <param name="toast">The scheduled toast to modify that will be scheduled.</param>
35+
/// <returns>An operation.</returns>
36+
public delegate IAsyncAction CustomizeScheduledToastAsync(ScheduledToastNotification toast);
37+
}
38+
39+
#endif

Microsoft.Toolkit.Uwp.Notifications/Toasts/Builder/ToastContentBuilder.Actions.cs

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88

99
namespace Microsoft.Toolkit.Uwp.Notifications
1010
{
11-
#if !WINRT
1211
#pragma warning disable SA1008
1312
#pragma warning disable SA1009
1413
/// <summary>
1514
/// Builder class used to create <see cref="ToastContent"/>
1615
/// </summary>
17-
public partial class ToastContentBuilder
16+
public
17+
#if WINRT
18+
sealed
19+
#endif
20+
partial class ToastContentBuilder
1821
{
1922
private IToastActions Actions
2023
{
@@ -45,6 +48,36 @@ private IList<IToastInput> InputList
4548
}
4649
}
4750

51+
private string SerializeArgumentsIncludingGeneric(ToastArguments arguments)
52+
{
53+
if (_genericArguments.Count == 0)
54+
{
55+
return arguments.ToString();
56+
}
57+
58+
foreach (var genericArg in _genericArguments)
59+
{
60+
if (!arguments.Contains(genericArg.Key))
61+
{
62+
arguments.Add(genericArg.Key, genericArg.Value);
63+
}
64+
}
65+
66+
return arguments.ToString();
67+
}
68+
69+
/// <summary>
70+
/// Add a button to the current toast.
71+
/// </summary>
72+
/// <param name="content">Text to display on the button.</param>
73+
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
74+
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
75+
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
76+
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments)
77+
{
78+
return AddButton(content, activationType, arguments, default);
79+
}
80+
4881
/// <summary>
4982
/// Add a button to the current toast.
5083
/// </summary>
@@ -53,15 +86,18 @@ private IList<IToastInput> InputList
5386
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
5487
/// <param name="imageUri">Optional image icon for the button to display (required for buttons adjacent to inputs like quick reply).</param>
5588
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
56-
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
89+
#if WINRT
90+
[Windows.Foundation.Metadata.DefaultOverload]
91+
#endif
92+
public ToastContentBuilder AddButton(string content, ToastActivationType activationType, string arguments, Uri imageUri)
5793
{
5894
// Add new button
5995
ToastButton button = new ToastButton(content, arguments)
6096
{
6197
ActivationType = activationType
6298
};
6399

64-
if (imageUri != default(Uri))
100+
if (imageUri != default)
65101
{
66102
button.ImageUri = imageUri.OriginalString;
67103
}
@@ -76,17 +112,46 @@ private IList<IToastInput> InputList
76112
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
77113
public ToastContentBuilder AddButton(IToastButton button)
78114
{
115+
if (button is ToastButton toastButton && toastButton.Content == null)
116+
{
117+
throw new InvalidOperationException("Content is required on button.");
118+
}
119+
79120
// List has max 5 buttons
80121
if (ButtonList.Count == 5)
81122
{
82123
throw new InvalidOperationException("A toast can't have more than 5 buttons");
83124
}
84125

126+
if (button is ToastButton b && b.CanAddArguments())
127+
{
128+
foreach (var arg in _genericArguments)
129+
{
130+
if (!b.ContainsArgument(arg.Key))
131+
{
132+
b.AddArgument(arg.Key, arg.Value);
133+
}
134+
}
135+
}
136+
85137
ButtonList.Add(button);
86138

87139
return this;
88140
}
89141

142+
/// <summary>
143+
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
144+
/// </summary>
145+
/// <param name="textBoxId">ID of an existing <see cref="ToastTextBox"/> in order to have this button display to the right of the input, achieving a quick reply scenario.</param>
146+
/// <param name="content">Text to display on the button.</param>
147+
/// <param name="activationType">Type of activation this button will use when clicked. Defaults to Foreground.</param>
148+
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
149+
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
150+
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments)
151+
{
152+
return AddButton(textBoxId, content, activationType, arguments, default);
153+
}
154+
90155
/// <summary>
91156
/// Add an button to the toast that will be display to the right of the input text box, achieving a quick reply scenario.
92157
/// </summary>
@@ -96,7 +161,7 @@ public ToastContentBuilder AddButton(IToastButton button)
96161
/// <param name="arguments">App-defined string of arguments that the app can later retrieve once it is activated when the user clicks the button.</param>
97162
/// <param name="imageUri">An optional image icon for the button to display (required for buttons adjacent to inputs like quick reply)</param>
98163
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
99-
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri = default(Uri))
164+
public ToastContentBuilder AddButton(string textBoxId, string content, ToastActivationType activationType, string arguments, Uri imageUri)
100165
{
101166
// Add new button
102167
ToastButton button = new ToastButton(content, arguments)
@@ -105,38 +170,70 @@ public ToastContentBuilder AddButton(IToastButton button)
105170
TextBoxId = textBoxId
106171
};
107172

108-
if (imageUri != default(Uri))
173+
if (imageUri != default)
109174
{
110175
button.ImageUri = imageUri.OriginalString;
111176
}
112177

113178
return AddButton(button);
114179
}
115180

181+
#if WINRT
182+
/// <summary>
183+
/// Add an input text box that the user can type into.
184+
/// </summary>
185+
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
186+
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
187+
public ToastContentBuilder AddInputTextBox(string id)
188+
{
189+
return AddInputTextBox(id, default, default);
190+
}
191+
192+
/// <summary>
193+
/// Add an input text box that the user can type into.
194+
/// </summary>
195+
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
196+
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
197+
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
198+
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent)
199+
{
200+
return AddInputTextBox(id, placeHolderContent, default);
201+
}
202+
#endif
203+
116204
/// <summary>
117205
/// Add an input text box that the user can type into.
118206
/// </summary>
119207
/// <param name="id">Required ID property so that developers can retrieve user input once the app is activated.</param>
120208
/// <param name="placeHolderContent">Placeholder text to be displayed on the text box when the user hasn't typed any text yet.</param>
121209
/// <param name="title">Title text to display above the text box.</param>
122210
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
123-
public ToastContentBuilder AddInputTextBox(string id, string placeHolderContent = default(string), string title = default(string))
211+
public ToastContentBuilder AddInputTextBox(
212+
string id,
213+
#if WINRT
214+
string placeHolderContent,
215+
string title)
216+
#else
217+
string placeHolderContent = default,
218+
string title = default)
219+
#endif
124220
{
125221
var inputTextBox = new ToastTextBox(id);
126222

127-
if (placeHolderContent != default(string))
223+
if (placeHolderContent != default)
128224
{
129225
inputTextBox.PlaceholderContent = placeHolderContent;
130226
}
131227

132-
if (title != default(string))
228+
if (title != default)
133229
{
134230
inputTextBox.Title = title;
135231
}
136232

137233
return AddToastInput(inputTextBox);
138234
}
139235

236+
#if !WINRT
140237
/// <summary>
141238
/// Add a combo box / drop-down menu that contain options for user to select.
142239
/// </summary>
@@ -145,7 +242,7 @@ public ToastContentBuilder AddButton(IToastButton button)
145242
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
146243
public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
147244
{
148-
return AddComboBox(id, default(string), choices);
245+
return AddComboBox(id, default, choices);
149246
}
150247

151248
/// <summary>
@@ -157,7 +254,7 @@ public ToastContentBuilder AddComboBox(string id, params (string comboBoxItemId,
157254
/// <returns>The current instance of <see cref="ToastContentBuilder"/></returns>
158255
public ToastContentBuilder AddComboBox(string id, string defaultSelectionBoxItemId, params (string comboBoxItemId, string comboBoxItemContent)[] choices)
159256
{
160-
return AddComboBox(id, default(string), defaultSelectionBoxItemId, choices);
257+
return AddComboBox(id, default, defaultSelectionBoxItemId, choices);
161258
}
162259

163260
/// <summary>
@@ -185,12 +282,12 @@ public ToastContentBuilder AddComboBox(string id, string title, string defaultSe
185282
{
186283
var box = new ToastSelectionBox(id);
187284

188-
if (defaultSelectionBoxItemId != default(string))
285+
if (defaultSelectionBoxItemId != default)
189286
{
190287
box.DefaultSelectionBoxItemId = defaultSelectionBoxItemId;
191288
}
192289

193-
if (title != default(string))
290+
if (title != default)
194291
{
195292
box.Title = title;
196293
}
@@ -203,6 +300,7 @@ public ToastContentBuilder AddComboBox(string id, string title, string defaultSe
203300

204301
return AddToastInput(box);
205302
}
303+
#endif
206304

207305
/// <summary>
208306
/// Add an input option to the Toast.
@@ -218,5 +316,4 @@ public ToastContentBuilder AddToastInput(IToastInput input)
218316
}
219317
#pragma warning restore SA1008
220318
#pragma warning restore SA1009
221-
#endif
222319
}

0 commit comments

Comments
 (0)