从 UWP 迁移到 WinUI 3 的应用通知

将应用通知代码从 UWP 迁移到 WinUI 3 时,唯一的区别在于处理通知激活。 发送和管理应用通知保持不变。

注意

术语“toast 通知”将替换为“应用通知”。 这些术语都指 Windows 的相同功能,但随着时间的推移,我们将逐步淘汰文档中的“Toast 通知”的使用。

注意

一些信息与预发行产品相关,相应产品在商业发行之前可能会进行重大修改。 对于此处提供的信息,Microsoft 不作任何明示或暗示的担保。

激活差异

类别 UWP WinUI 3
前台激活入口点 OnActivated 调用 中的 App.xaml.cs 方法 OnLaunched 调用 中的 App.xaml.cs 方法。
后台激活入口点 作为后台任务单独处理 与前台激活相同。 OnLaunched 调用 中的 App.xaml.cs 方法。 使用 GetActivatedEventArgs 确定应用应完全启动还是只处理任务并退出。
窗口激活 当前台激活发生时,窗口会自动进入前台 如果需要,必须将窗口置于前台

C# 应用的迁移

步骤 1:安装 NuGet 库

对于 WinUI 3 应用,可以使用 AppNotificationManager 类处理通知激活。 此类由 Microsoft.WindowsAppSDK Nuget 包提供,默认情况下包含在 WinUI 3 Visual Studio 项目模板中。

步骤 2:更新清单

Package.appxmanifest 中,添加:

  1. xmlns:com 声明
  2. xmlns:desktop 声明
  3. IgnorableNamespaces 属性中,添加 comdesktop
  4. windows.toastNotificationActivation 的 desktop:Extension,用于声明 toast 激活器 CLSID(使用你选择的新 GUID)。
  5. 仅限 MSIX:使用步骤 4 中 GUID 的 COM 激活器的 com:Extension。 务必包括 Arguments="----AppNotificationActivated:",以便了解是从通知启动
<!--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:Extension Category="windows.toastNotificationActivation">
          <desktop:ToastNotificationActivation ToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> 
        </desktop:Extension>

        <!--Register COM CLSID LocalServer32 registry key-->
        <com:Extension Category="windows.comServer">
          <com:ComServer>
            <com:ExeServer Executable="YourProject.exe" Arguments="----AppNotificationActivated:" DisplayName="App notification activator">
              <com:Class Id="replaced-with-your-guid-C173E6ADF0C3" DisplayName="App notification activator"/>
            </com:ExeServer>
          </com:ComServer>
        </com:Extension>

      </Extensions>
    </Application>
  </Applications>
 </Package>

步骤 3:处理激活

在应用的启动代码 (通常为 App.xaml.cs) ,使用以下步骤更新代码:

  1. OnLaunched 中,获取 AppNotificationManager 类的默认实例。
  2. 注册 AppNotificationManager.NotificationInvoked 事件。
  3. 调用 Microsoft.Windows.AppNotifications.AppNotificationManager.Register 以注册应用以接收通知事件。 请务必在注册 NotificationInvoked 处理程序后调用此方法。
  4. 将窗口启动/激活代码重构为专用 LaunchAndBringToForegroundIfNeeded 帮助程序方法,以便可以从多个位置调用它。
  5. 创建一个 HandleNotification 帮助程序方法,以便可以从多个位置调用它。
  6. 调用 AppInstance.GetActivatedEventArgs 并为值 ExtendedActivationKind.AppNotification 检查返回对象的 AppActivationArguments.Kind 属性。
  7. 如果激活类型不是 AppNotification ,则调用 LaunchAndBringToForegroundIfNeeded 帮助程序方法。
  8. 如果激活类型为 AppNotification ,请将 AppActivationArguments.Data 属性转换为 AppNotificationActivatedEventArgs ,并将其传递给 HandleNotification 帮助程序方法。
  9. ApplicationManager.NotificationInvoked 处理程序中 HandleNotification ,调用帮助程序方法。
  10. HandleNotification在帮助程序方法中,在执行任何与 UI 相关的代码(例如显示窗口或更新 UI)之前,请务必调度到应用或窗口调度程序
  11. 处理应用通知激活的旧 UWP OnActivated 代码迁移到新的 HandleNotification 帮助程序方法。

迁移的 App.xaml.cs


protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    m_window = new MainWindow();

    // 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 += NotificationManager_NotificationInvoked;
    notificationManager.Register();

    var activatedArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
    var activationKind = activatedArgs.Kind;
    if (activationKind != ExtendedActivationKind.AppNotification)
    {
        LaunchAndBringToForegroundIfNeeded();
    } else
    {
        HandleNotification((AppNotificationActivatedEventArgs)activatedArgs.Data);
    }

}

private void LaunchAndBringToForegroundIfNeeded()
{
    if (m_window == null)
    {
        m_window = new MainWindow();
        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);
    }
}

private void NotificationManager_NotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
{
    HandleNotification(args);
}

private void HandleNotification(AppNotificationActivatedEventArgs args)
{
  // Use the dispatcher from the window if present, otherwise the app dispatcher
  var dispatcherQueue = m_window?.DispatcherQueue ?? DispatcherQueue.GetForCurrentThread();


  dispatcherQueue.TryEnqueue(async delegate
  {

      switch (args.Arguments["action"])
      {
          // Send a background message
          case "sendMessage":
              string message = 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;
      }
  });
}

private static class WindowHelper
{
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    public static void ShowWindow(Window window)
    {
        // Bring the window to the foreground... first get the window handle...
        var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);

        // Restore window if minimized... requires DLL import above
        ShowWindow(hwnd, 0x00000009);

        // And call SetForegroundWindow... requires DLL import above
        SetForegroundWindow(hwnd);
    }
}

生成应用通知内容

使用 Windows 应用 SDK,你仍然可以使用原始 xml 创建应用通知内容,但也可以使用新的 AppNotificationsBuilder API 创建应用通知内容,该 API 替换 Windows 社区工具包提供的 ToastContentBuilder 类。 通过调用 AppNotificationManager.Show 发送应用通知。 不建议混合使用 Windows 社区工具包和应用 SDK API。

using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppNotifications.Builder;

...

var builder = new AppNotificationBuilder()
    .AddText("Send a message.")
    .AddTextBox("textBox")
    .AddButton(new AppNotificationButton("Send")
        .AddArgument("action", "sendMessage"));

var notificationManager = AppNotificationManager.Default;
notificationManager.Show(builder.BuildNotification());