UWP から WinUI 3 への移行に関するトースト通知

トースト通知コードを UWP から WinUI 3 に移行する場合の唯一の違いは、通知のアクティブ化を処理することです。 トースト通知の送信と管理はまったく同じです。

アクティブ化の相違点

カテゴリ UWP WinUI 3
フォアグラウンドアクティブ化エントリポイント OnActivatedApp.xaml.cs のメソッドが呼び出されます イベント (C++ の場合は COM クラス) にサブスクライブする ToastNotificationManagerCompat.OnActivated
バックグラウンドアクティブ化エントリポイント バックグラウンドタスクとして個別に処理されます 同じ ToastNotificationManagerCompat.OnActivated イベント (C++ の場合は COM クラス) を介して受信する
ウィンドウのアクティブ化 フォアグラウンドのアクティブ化が発生すると、ウィンドウが自動的にフォアグラウンドになります 必要に応じて、ウィンドウを前面に表示する必要があります。

C# アプリの移行

手順 1: NuGet ライブラリをインストールする

Visual Studio ソリューション内で、プロジェクトを右クリックし、[NuGet パッケージの管理] をクリックします。NuGet パッケージ バージョン 7.0 以降を検索してインストールします。

このパッケージは API を ToastNotificationManagerCompat 追加します。

手順 2: マニフェストを更新する

Package.appxmanifestで、次のように追加します。

  1. xmlns:com のための宣言
  2. xmlns:desktop のための宣言
  3. IgnorableNamespaces 属性に comdesktop を追加
  4. トースト アクティベーター CLSID (任意の新しい GUID を使用) を宣言するための、windows.toastNotificationActivationdesktop:Extension
  5. MSIX のみ: COM アクティベーターの com:Extension (手順 4 の GUID を使用)。 通知から起動されたことがわかるように、必ず Arguments="-ToastActivated" を含めます

Package.appxmanifest

<!--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 toast 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="-ToastActivated" DisplayName="Toast activator">
              <com:Class Id="replaced-with-your-guid-C173E6ADF0C3" DisplayName="Toast activator"/>
            </com:ExeServer>
          </com:ComServer>
        </com:Extension>

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

手順 3: アクティブ化を処理する

アプリのスタートアップコード (通常は App.xaml) で、次のようにコードを変更します。

  1. アプリレベルを定義して取得する DispatcherQueue
  2. イベントに登録する ToastNotificationManagerCompat.OnActivated
  3. ウィンドウの起動/アクティブ化コードを専用 LaunchAndBringToForegroundIfNeeded のメソッドにリファクタリングして、複数の場所から呼び出すことができるようにします。
  4. が true を返す場合 ToastNotificationManagerCompat.WasCurrentProcessToastActivated() は、ウィンドウを起動しないようにします (そのメソッドが true の場合、 OnActivated イベントは次に呼び出され、イベントコールバック内のウィンドウを表示するように選択できます)。
  5. ToastNotificationManagerCompat.OnActivatedでは、ウィンドウの表示や ui の更新など、ui 関連のコードを実行する前に、アプリまたはウィンドウのディスパッチャーにディスパッチするようにしてください。
  6. トーストアクティブ化を処理した古い UWP コードを新しい ToastNotificationManagerCompat.OnActivated イベントハンドラーに移行し、バックグラウンドタスクトーストアクティブ化コードを新しい ToastNotificationManagerCompat.OnActivated イベントハンドラーに移行します。

移行された app.xaml

public static DispatcherQueue DispatcherQueue { get; private set; }

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    // Get the app-level dispatcher
    DispatcherQueue = global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();

    // Register for toast activation. Requires Microsoft.Toolkit.Uwp.Notifications NuGet package version 7.0 or greater
    ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompat_OnActivated;

    // If we weren't launched by a toast, launch our window like normal.
    // Otherwise if launched by a toast, our OnActivated callback will be triggered
    if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
    {
        LaunchAndBringToForegroundIfNeeded();
    }
}

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 toast, it doesn't
        // activate the window correctly
        WindowHelper.ShowWindow(m_window);
    }
    else
    {
        WindowHelper.ShowWindow(m_window);
    }
}

private void ToastNotificationManagerCompat_OnActivated(ToastNotificationActivatedEventArgsCompat e)
{
    // Use the dispatcher from the window if present, otherwise the app dispatcher
    var dispatcherQueue = m_window?.DispatcherQueue ?? App.DispatcherQueue;

    dispatcherQueue.TryEnqueue(delegate
    {
        var args = ToastArguments.Parse(e.Argument);

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