通过


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

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

Note

正在将术语“toast 通知”替换为“应用通知”。 两个术语指的是 Windows 的相同功能,但随着时间的推移,我们将逐步取消在文档中使用“toast 通知”。

Note

一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

激活差异

Category UWP WinUI
前台激活入口点 OnActivated 内调用 App.xaml.cs 方法 OnLaunched 中的方法在 App.xaml.cs 内被调用。
后台激活入口 作为后台任务单独处理 与前台激活相同。 OnLaunched 中的方法在 App.xaml.cs 内被调用。 使用 GetActivatedEventArgs 来确定应用是否应完全启动或仅处理任务并退出。
窗口激活 当前台激活发生时,窗口会自动切换到前台 如果需要,可以将窗口带到前台

C# 应用的迁移

步骤 1:安装 NuGet 库

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

步骤 2:更新清单

在 Package.appxmanifest,添加:

  1. xmlns:com 声明
  2. xmlns:desktop 声明
  3. IgnorableNamespaces 属性中,comdesktop
  4. windows.toastNotificationActivation 的 desktop:Extension,用于声明 toast 激活器 CLSID(使用你选择的新 GUID)。
  5. 仅限 MSIX:com:Extension 使用步骤 4 中的 GUID 为 COM 激活器。 务必包括 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 属性强制转换为 AppNotificationActivatedEventArgs 类型,并将其传递给帮助方法。
  9. ApplicationManager.NotificationInvoked处理器中,调用HandleNotification辅助方法。
  10. 在你的 HandleNotification 辅助方法中,确保在执行任何与 UI 相关的代码(例如显示窗口或更新 UI)之前调度到 App 或 Window 调度程序。
  11. 将您的旧 UWP 代码(用于处理应用通知激活)迁移到新的辅助方法。

迁移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
  {
      if (args.Argument.Contains("action"))
      {
          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;
          }
      }
      else
      {
          Debug.Print("Notification args is null");
      }
  });
}

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 App 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());