快速入門:Windows 應用程式 SDK 的應用程式通知

A screen capture showing an app notification above the task bar. The notification is a reminder for an event. The app name, event name, event time, and event location are shown. A selection input displays the currently selected value,

在本快速入門中,您將使用 Windows 應用程式 SDK 建立桌面 Windows 應用程式,讓它傳送與接收本機應用程式通知,亦即快顯通知。

重要

目前不支援高權限 (管理員) 應用程式通知。

必要條件

範例應用程式

本快速入門涵蓋的程式碼來自範例應用程式從 GitHub 找到的通知。

API 參考

如需應用程式通知的 API 參考文件,請參閱 Microsoft.Windows.AppNotifications 命名空間

步驟 1:新增命名空間宣告

為 Windows 應用程式 SDK 的應用程式通知 Microsoft.Windows.AppNotifications 新增命名空間。

using Microsoft.Windows.AppNotifications;

步驟 2:更新應用程式的資訊清單

如果您的應用程式是未封裝 (亦即在執行階段缺少套件身分識別),請跳至步驟 3:註冊以處理應用程式通知

如果應用程式已封裝 (包含具有外部位置的封裝):

  1. 開啟 Package.appxmanifest
  2. 新增 xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" 命名空間到 <Package>
  3. windows.toastNotificationActivation 新增 <desktop:Extension> 以宣告 COM 啟動器 CLSID。 您可以在 Visual Studio 中前往 [工具] 之下的 [建立 GUID],藉此取得 CLSID。
  4. 使用相同 CLSID 為 COM 啟動器新增 <com:Extension>
    1. Executable 屬性指定 .exe 檔案。 登錄應用程式通知時,.exe 檔案呼叫 Register() 的程序必須相同,詳情請見步驟 3。 在以下範例,我們使用 Executable="SampleApp\SampleApp.exe"
    2. 指定 Arguments="----AppNotificationActivated:",以便確保 Windows 應用程式 SDK 可將通知的承載視為 AppNotification 類型來處理。
    3. 指定 DisplayName

重要

警告:如果您以 <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::Default().NotificationInvoked,然後呼叫 AppNotificationManager::Default().Register。 呼叫的順序相當重要。

重要

您必須先呼叫 AppNotificationManager::Default().Register,之後再呼叫 AppInstance.GetCurrent.GetActivatedEventArgs

應用程式終止時,呼叫 AppNotificationManager::Default().Unregister() 即可清出 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:顯示應用程式通知

App notification with button

必須完成步驟 3:註冊以處理應用程式通知再繼續操作。

接下來您會使用 appLogoOverride 影像和按鈕顯示簡單的應用程式通知。

使用 AppNotificationBuilder 類別建構應用程式通知,然後呼叫 Show。 如需詳細了解如何使用 XML 建構應用程式通知,請參考快顯內容通知的 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:處理使用者選取通知的方式

使用者可能選取通知的內文或按鈕。 您的應用程式必須處理系統回應使用者與通知互動時的引動過程。

這個處理過程有兩種常見方式:

  1. 您可以選擇讓應用程式以特定 UI 脈絡啟動,或
  2. 您可以選擇讓應用程式評估特定動作的行為 (比如按下通知內文),但不呈現任何 UI。 這也稱為背景動作。

下方的程式碼範例並非來自範本應用程式,但它可說明這兩種處理使用者產生動作的方式。 將 launch 值 (對應使用者按一下通知內文)、input 元素 (快速回應文字框) 及內含 arguments 值的按鈕 (對應使用者按一下按鈕) 新增到通知的 XML 承載。 在 ProcessLaunchActivationArgs,封裝每個引數。

重要

桌面應用程式會忽視您在通知的 XML 承載設定的 activationType="background"。 您必須處理啟動引數,並決定是否要按照本步驟的描述顯示視窗。

App notification with reply

// 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&amp;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&amp;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);
    }
}

請按照以下指引操作

  1. 如果使用者選取通知時應用程式並非正在執行,應用程式預期會啟動,使用者可依照通知脈絡看到前景視窗。
  2. 如果使用者選取通知時應用程式已最小化,應用程式預期會顯示在前景,並依照通知的脈絡呈現新視窗。
  3. 如果使用者引發了通知背景動作 (比如使用者在通知的文字框輸入文字並點擊回覆,藉此回應通知),應用程式會處理承載,但不會呈現前景視窗。

請參閱 GitHub 的範例應用程式程式碼查看詳細範例。

步驟 6:移除通知

通知如與使用者不再相關,將之移除。

在本範例,使用者已看過應用程式中所有群組聊天的訊息,因此您要清除群組聊天的全部通知。 接下來,使用者會將好友設為靜音,因此您要清除該好友的全部通知。 顯示通知之前,您要先將 GroupTag 屬性新增到通知,接下來才能識別通知。


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)
    }
}

傳誦與更新進度列通知

您可以在通知中顯示與更新相關的進度列:

Notification with progress bar

使用 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.
    }
}

資源