从 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 属性中,com桌面
  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 并检查返回对象的 AppActivationArguments.Kind 属性,以获取 ExtendedActivationKind.AppNotification
  7. 如果激活类型不是 AppNotification请调用 LaunchAndBringToForegroundIfNeeded 帮助程序方法。
  8. 如果激活类型为 AppNotification,请将 AppActivationArguments.Data 属性强制转换为 HandleNotification AppNotificationActivatedEventArgs,并将其传递给帮助程序方法。
  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());