快速入门:Windows 应用 SDK中的应用通知
在本快速入门中,你将创建一个桌面 Windows 应用程序,该应用程序使用 Windows 应用 SDK发送和接收本地应用通知(也称为 Toast 通知)。
重要
目前不支持提升的(管理员)应用的通知。
先决条件
示例应用
本快速入门介绍 GitHub 上的通知示例应用中的代码。
API 参考
有关应用通知的 API 参考文档,请参阅 Microsoft.Windows.AppNotifications 命名空间。
步骤 1:添加命名空间声明
为Windows 应用 SDK应用通知 Microsoft.Windows.AppNotifications
添加命名空间。
using Microsoft.Windows.AppNotifications;
步骤 2:更新应用的清单
如果应用未打包 (即,它在运行时) 缺少包标识,请跳到 步骤 3:注册以处理应用通知。
如果你的应用打包 (包括打包的外部位置) :
- 打开 Package.appxmanifest。
- 将 和
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
命名空间添加到xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
<Package>
- 添加
<desktop:Extension>
以windows.toastNotificationActivation
声明 COM 激活器 CLSID。 可以通过导航到 Visual Studio 中的“工具”下的“创建 GUID”来获取 CLSID。 - 使用相同的 CLSID 为 COM 激活器添加
<com:Extension>
。- 在 属性中
Executable
指定.exe文件。 .exe 文件必须是注册应用以获取通知时调用Register()
的同一进程,步骤 3 中详细介绍了此过程。 在下面的示例中,我们使用Executable="SampleApp\SampleApp.exe"
。 - 指定
Arguments="----AppNotificationActivated:"
以确保Windows 应用 SDK可以将通知的有效负载作为 AppNotification 类型进行处理。 - 指定
DisplayName
。
- 在 属性中
重要
警告:如果使用 在 appx 清单<uap:Protocol>
中定义 Windows.Protocol 应用扩展性类型,则单击通知将启动同一应用的新进程,即使你的应用已在运行也是如此。
<!--Packaged apps only-->
<!--package.appxmanifest-->
<Package
xmlns: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:Extension Category="windows.toastNotificationActivation">
<desktop:ToastNotificationActivation ToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" />
</desktop:Extension>
<!--Register COM CLSID-->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="SampleApp\SampleApp.exe" DisplayName="SampleApp" Arguments="----AppNotificationActivated:">
<com:Class Id="replaced-with-your-guid-C173E6ADF0C3" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
</Package>
步骤 3:注册以处理应用通知
注册应用以处理通知,然后在应用终止时取消注册。
App.xaml
在 文件中,注册 AppNotificationManager::D efault () 。NotificationInvoked,然后调用 AppNotificationManager::D efault () 。注册。 这些调用的顺序很重要。
重要
必须调用 AppNotificationManager::D efault () 。在 调用 AppInstance.GetCurrent.GetActivatedEventArgs 之前注册。
当应用终止时,调用 AppNotificationManager::D efault () 。注销 () 以释放 COM 服务器并允许后续调用启动新进程。
// App.xaml.cs
namespace CsUnpackagedAppNotifications
{
public partial class App : Application
{
private Window mainWindow;
private NotificationManager notificationManager;
public App()
{
this.InitializeComponent();
notificationManager = new NotificationManager();
AppDomain.CurrentDomain.ProcessExit += new EventHandler(OnProcessExit);
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
mainWindow = new MainWindow();
notificationManager.Init();
// Complete in Step 5
mainWindow.Activate();
}
void OnProcessExit(object sender, EventArgs e)
{
notificationManager.Unregister();
}
}
}
// NotificationManager.cs
namespace CsUnpackagedAppNotifications
{
internal class NotificationManager
{
private bool m_isRegistered;
private Dictionary<string, Action<AppNotificationActivatedEventArgs>> c_map;
public NotificationManager()
{
m_isRegistered = false;
// When adding new a scenario, be sure to add its notification handler here.
c_map = new Dictionary<int, Action<AppNotificationActivatedEventArgs>>();
c_map.Add(ToastWithAvatar.ScenarioId, ToastWithAvatar.NotificationReceived);
c_map.Add(ToastWithTextBox.ScenarioId, ToastWithTextBox.NotificationReceived);
}
~NotificationManager()
{
Unregister();
}
public void Init()
{
// 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.
AppNotificationManager notificationManager = AppNotificationManager.Default;
notificationManager.NotificationInvoked += OnNotificationInvoked;
notificationManager.Register();
m_isRegistered = true;
}
public void Unregister()
{
if (m_isRegistered)
{
AppNotificationManager.Default.Unregister();
m_isRegistered = false;
}
}
public void ProcessLaunchActivationArgs(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
{
// Complete in Step 5
}
}
}
步骤 4:显示应用通知
在继续操作之前, 必须 完成 步骤 3:注册以处理应用通知 。
现在,你将显示带有 appLogoOverride
图像和按钮的简单应用通知。
使用 AppNotificationBuilder 类构造应用通知,然后调用 Show
。 有关如何使用 XML 构造应用通知的详细信息,请参阅 Toast 内容 和 通知 XML 架构中的示例。
注意
如果你的应用打包 (包括打包的外部位置) ,则通知左上角中的应用图标源自 package.manifest
。 如果应用未打包,则图标的源是先查看快捷方式,然后查看应用进程中的资源文件。 如果所有尝试都失败,则使用 Windows 默认应用图标。 支持的图标文件类型为 .jpg
、 .png
、 .bmp
和 .ico
。
// ToastWithAvatar.cs
class ToastWithAvatar
{
public const int ScenarioId = 1;
public const string ScenarioName = "Local Toast with Avatar Image";
public static bool SendToast()
{
var appNotification = new AppNotificationBuilder()
.AddArgument("action", "ToastClick")
.AddArgument(Common.scenarioTag, ScenarioId.ToString())
.SetAppLogoOverride(new System.Uri("file://" + App.GetFullPathToAsset("Square150x150Logo.png")), AppNotificationImageCrop.Circle)
.AddText(ScenarioName)
.AddText("This is an example message using XML")
.AddButton(new AppNotificationButton("Open App")
.AddArgument("action", "OpenApp")
.AddArgument(Common.scenarioTag, ScenarioId.ToString()))
.BuildNotification();
AppNotificationManager.Default.Show(appNotification);
return appNotification.Id != 0; // return true (indicating success) if the toast was sent (if it has an Id)
}
public static void NotificationReceived(AppNotificationActivatedEventArgs notificationActivatedEventArgs)
{
// Complete in Step 5
}
}
// Call SendToast() to send a notification.
步骤 5:处理选择通知的用户
用户可以选择通知的正文或按钮。 应用需要处理调用,以响应与通知交互的用户。
有 2 种常见的处理方法:
- 你选择在特定 UI 上下文中启动应用,或者
- 你选择让应用评估特定于操作的行为, (如在通知正文中按下按钮) 而不呈现任何 UI。 也称为后台操作。
下面的代码示例(不是来自示例应用)说明了处理用户生成的操作的两种方法。 添加一个 launch
值 (对应于用户单击通知正文) , input
) (快速答复文本框的元素,具有 arguments
值 (对应于用户单击通知的 XML 有效负载) 按钮。 在 中 ProcessLaunchActivationArgs
,每个参数的大小写。
重要
对于桌面应用,忽略通知 XML 有效负载中的设置 activationType="background"
。 必须改为处理激活参数,并决定是否显示窗口,如此步骤中所述。
// 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>
void ProcessLaunchActivationArgs(const winrt::AppNotificationActivatedEventArgs& notificationActivatedEventArgs)
{
// If the user clicks on the notification body, your app needs to launch the chat thread window
if (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 launched
if (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 text
SendReplyToUser(replyBoxText);
}
}
请遵循以下准则:
- 如果用户选择了通知,并且你的应用未运行,则预期应用已启动,并且用户可以在通知的上下文中看到前台窗口。
- 如果用户选择了通知,并且应用已最小化,则预期应用将呈现在前台,并在通知的上下文中呈现一个新窗口。
- 如果用户调用通知后台操作 (例如,用户通过在通知文本框中键入并点击回复) 来响应通知,则应用在不呈现前台窗口的情况下处理有效负载。
有关更详细的示例,请参阅 GitHub 上的示例应用代码。
步骤 6:删除通知
当通知不再与用户相关时,请删除通知。
在此示例中,用户已在应用中看到来自群组聊天的所有消息,因此你清除了群组聊天中的所有通知。 然后,用户将好友静音,以便清除来自该好友的所有通知。 您首先将 Group 和 Tag 属性添加到通知中,然后再显示,以便立即识别它们。
void SendNotification(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);
}
其他功能
发送来自云的应用通知
若要从云发送应用通知,请按照快速入门:在Windows 应用 SDK中推送通知中的发送云源应用通知进行操作。
设置过期时间
如果通知中的消息仅在特定时间段内相关,请使用 Expiration
属性设置应用通知的过期时间。 例如,如果发送日历事件提醒,请将到期时间设置为日历事件的结束时间。
注意
默认和最长过期时间为 3 天。
class ToastWithAvatar
{
public static bool SendToast()
{
var appNotification = new AppNotificationBuilder()
.SetAppLogoOverride(new System.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);
return appNotification.Id != 0; // return true (indicating success) if the toast was sent (if it has an Id)
}
}
确保通知在重新启动时过期
ExpiresOnReboot
如果想要在重新启动时删除通知,请将 属性设置为 True。
class ToastWithAvatar
{
public static bool SendToast()
{
var appNotification = new AppNotificationBuilder()
.SetAppLogoOverride(new System.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);
return appNotification.Id != 0; // return true (indicating success) if the toast was sent (if it has an Id)
}
}
发送和更新进度栏通知
可以在通知中显示进度栏相关更新:
AppNotificationProgressData
使用 构造更新进度栏通知。
const winrt::hstring c_tag = L"weekly-playlist";
const winrt::hstring c_group = L"downloads";
// Send first Notification Progress Update
void SendUpdatableNotificationWithProgress()
{
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 payload
auto result = co_await winrt::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.
}
}
资源
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈