本篇介紹 Bridge WPF 專案整合 Windows Notification Service(WNS),並處理相關的事件。
對於 WNS 運作方式,可參考 Universal App - 整合 Windows Notification Service (WNS) for Server 與 Universal App - 整合 Windows Notification Service (WNS) for Client 的介紹,瞭解 WNS 與 UWP 的整合。
根據 Toast notifications from desktop apps 的介紹,由於 Desktop apps(Desktop Bridge and classic Win32) 因爲架構不同,整合有不同的選擇,但 COM activator 來處理 Windows 10 Notifications 是最好的選擇,因爲所有的功能都有支援,如下圖: 加上官方有寫好的 library: Send a local toast notification from destkop C# apps 與 Send a local toast notification from destkop C++ WRL apps 更方便我們完成整合。
需注意:
- Desktop Bridge apps 的整合會更接近 UWP,用戶點擊 toast 時,要能啓動 app 並帶入 toast 中的參數;
- Classic Win32 apps 需設定 AUMID 來傳送 toast,或搭配 CLSID 設定捷徑。
- 可利用亂數 GUID 來設定 GUID CLSID;
- 切勿新增 COM Server / COM activator;
- 可以設定 stub COM CLSID,讓 Action Center 保存您的通知;
- 只能使用 protocol 類型的 toast,因爲 stub COM CLSID 會中斷其他任何 toast 的啓動。因此要更新 App 讓它支援 protocol 啓動;
以 Desktop Bridge (WPF) 程式爲例,整合 WNS 需要以下步驟:
- 建立並完成 Desktop Bridge 的基本設定,可參考WPF 使用 Windows 10 APIs - 1;
- 修改 WPF 專案檔,加入:<TargetPlatformVersion /> 設定預計支援 Windows 10 的最小版本,如下:
<TargetFrameworkVersion>...</TargetFrameworkVersion> <TargetPlatformVersion>10.0.10240.0</TargetPlatformVersion>
- 加入必要的參考:Windows.Data 與 Windows.UI,如下圖:
如果您已經加入過 Program Files (x86)\Windows Kits\10\UnionMetadata\Windows.winmd 與 Windows\Microsoft.NET\Framework\v4.0.30319\System.Runtime.WindowsRuntime.dll 的話,不需要在加入 Windows.UI 與 Window.Data 因爲 Windows.winmd 已經有了。
- 下載 DesktopNotificationManagerCompat.cs file from GitHub 並加入到專案中;該 compact library 簡化複雜 desktop application 整合 notifications 的方式,並加入處理 notifications 與 apps 之間的互動;
- 實作 activator 處理用戶點擊 toast 時與 App 的互動。
延申 NotificationActivator 類別與加入 3 個必要的屬性宣告,並為 App 加入 unique GUID CLSID,GUID 可以是亂數產生的。 CLSID(class identify) 用在向 Action Center 讓他知道收到 toast 對應到哪一個 COM activate。
// GUID CLSID 必須是唯一值。 [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(INotificationActivationCallback))] [Guid("replaced-with-your-guid-C173E6ADF0C3"), ComVisible(true)] public class MyNotificationActivator : NotificationActivator { public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId) { // 負責處理收到 OnActivated 的事件 } }
- 向 notification platform 注冊,這裏使用 Desktop Bridge 爲例,如果您是 classic Win32 請參考 Register with notification platform:
在 Package.appxmanifest 加入 xmlns:com 與 xmlns:desktop 的宣告,並設定 windows.comServer(com extension) 與 windows.toastNotificaiton (desktop extension),兩者要記得使用與上一步的 GUID 一致。
<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> <!--Register COM CLSID LocalServer32 registry key--> <com:Extension Category="windows.comServer"> <com:ComServer> <!-- YourProject\YourProject.exe 需注意專案輸出的目錄,不可以用 $targetnametoken$.exe --> <com:ExeServer Executable="YourProject\YourProject.exe" Arguments="-ToastActivated" DisplayName="Toast activator"> <com:Class Id="replaced-with-your-guid-C173E6ADF0C3" DisplayName="Toast activator"/> </com:ExeServer> </com:ComServer> </com:Extension> <!--Specify which CLSID to activate when toast clicked--> <desktop:Extension Category="windows.toastNotificationActivation"> <desktop:ToastNotificationActivation ToastActivatorCLSID="replaced-with-your-guid-C173E6ADF0C3" /> </desktop:Extension> </Extensions> </Application> </Applications> </Package>
- 注冊 AUMID 與 COM Server:
AUMID 的取得可參考:Find the AUMID (Application User Model ID) of an installed UWP app,如果您沒有特別設定通常是 {Package family name}!App。
protected override void OnStartup(StartupEventArgs e) { // Register AUMID and COM server (for Desktop Bridge apps, this no-ops) DesktopNotificationManagerCompat.RegisterAumidAndComServer<MyNotificationActivator>("{AUMID}"); // Register COM server and activator type DesktopNotificationManagerCompat.RegisterActivator<MyNotificationActivator>(); }
- 如果抓不到 AUMID,請先安裝一次您的 App;
- 如果同時支援 Desktop Bridge 與 classic Win32 apps 可以隨時使用 RegisterAumidAndComServer 方法;如果是 Desktop Bridge 只需要在 OnStartup() 時使用;AUMID 在注冊 COM Server 時會一起被記錄在 LocalServer32 的 register key 裏面;
- 不管是 Desktop Bridge 或 classic Win32 apps 都需要使用 DesktopNotificationManagerCompat.RegisterActivator 注冊 notification action type (例如上面範例的 MyNotificationActivator);
- DesktopNotificationManagerCompat 包裝了發送 ToastNotifier 幫忙發送 toast 訊息,減少我們自己撰寫 toast 的 XML 造成的錯誤;或者選擇安裝 Notifications library 也可以方便使用。詳細使用方式可參考 Step 7: Send a notification;
- 在 MyNotificationActivator.OnActivated 與 App.OnStartup 加入處理 notification 的機制:
開發過 UWP 熟悉 toast 被用戶點擊時會啓動 app,而存在兩個狀況:app 未開啓時會觸發 OnLaunch;app 已開啓時會觸發 OnActivated。同樣第以 WPF 爲例:
- app 正開著,會進入:NotificationActivator.OnActivated;
- app 未被開啓,會進入 App.OnStartup 事件,並帶入啓動 toast 中夾帶 -ToastActivated 的參數;接著在呼叫 NotificationActivator.OnActivated;
// The GUID CLSID must be unique to your app. Create a new GUID if copying this code. [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(INotificationActivationCallback))] [Guid("replaced-with-your-guid-a3af-C173E6ADF0C3"), ComVisible(true)] public class MyNotificationActivator : NotificationActivator { public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId) { // 要記得使用 Application.Current.Dispatcher 包裝要操作的 UI 事件; // 因爲 com activator 是在另一個 thread Application.Current.Dispatcher.Invoke(() => { // 檢查是否有 window 在前景顯示 OpenWindowIfNeeded(); }); } private void OpenWindowIfNeeded() { // 檢查是否有 window 被開啓,如果沒有則需要建立一個; // == 0 的狀況會發生在 toast 被點擊但 app 還沒有被開啓的時候; if (App.Current.Windows.Count == 0) { new MainWindow().Show(); } // 啓動 window 讓 window 被系統 focus 跑到第一個 App.Current.Windows[0].Activate(); // 設定 window 要顯示的大小 App.Current.Windows[0].WindowState = WindowState.Normal; } }
protected override void OnStartup(StartupEventArgs e) { // 利用 DesktopNotificationManagerCompat 注冊 AUMID 到 COM Server DesktopNotificationManagerCompat.RegisterAumidAndComServer
("{AUMID}"); // 利用 DesktopNotificationManagerCompat 注冊處理 COM Server 的 Activator DesktopNotificationManagerCompat.RegisterActivator (); // 如果 App 被啓動是來自於 toast 被 click 的話,就會帶入 -ToastActivated 的參數 // -ToastActivated 也是我們在 Package.appxmanifest 宣告的 activator 參數 if (e.Args.Contains("-ToastActivated")) { // 如果是來自 toast 啓動 app 最後會走進 注冊的 OnActivated 中 // 由於上方的範例改由 OnActivated 判讀是否需要產生 window 這裏就不需要了 } else { // 一般啓動 app 需要顯示 window // 在 App.xaml 中務移除 StartupUri 以便預設情況下不會創建 window,因爲我們需要自己控制。而且有時候我們只希望在沒有 window 下處理任務。 new MainWindow().Show(); } base.OnStartup(e); } - 清除 toast 或是指定特定 tag 的 taost:
// Remove the toast with tag "Message2" DesktopNotificationManagerCompat.History.Remove("Message2"); // Clear all toasts DesktopNotificationManagerCompat.History.Clear();
上面介紹讓 Desktop bridge 或 Win32 app 能夠整合 toast,接著繼續介紹整合 Windows Notification Service。
如何拿到 WNS 的 server 端,可參考 Universal App - 整合 Windows Notification Service (WNS) for Server。
下面以 WPF 爲例介紹取得 WNS channel:
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
try
{
// 與 UWP 一樣使用 PushNotificationChannelManager 拿到 channel
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
channel.PushNotificationReceived += Channel_PushNotificationReceived;
Debug.WriteLine(channel.Uri);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
private void Channel_PushNotificationReceived(PushNotificationChannel sender, PushNotificationReceivedEventArgs args)
{
// 可根據自己的需要,處理收到的訊息,但需注意這裏的寫法只有 app 在前景時有用;
// 可以寫到 BackgroundTask 包裝起來;
switch (args.NotificationType)
{
case PushNotificationType.Raw:
break;
case PushNotificationType.Tile:
break;
case PushNotificationType.TileFlyout:
break;
case PushNotificationType.Toast:
break;
}
args.Cancel = true;
}
[補充]
- DesktopNotificationManagerCompat 該檔案幫忙寫好處理注冊 COM 與接受訊息時,呼叫 kernal32.dll 來轉發到注冊的 *.exe,並套入既有 Application 的 life cycle。
[範例程式]
DotblogsSampleCode/Samples/34-WpfPushNotification/
======
隨著微軟陸續開發 How x86 emulation works on ARM 的方向,加上 Bridge Desktop apps 與 PWA apps 不斷地在 Microsoft Store 上架,發現除了 UWP 之外還有其他開發方式,可以讓 Apps 在 Windows 10 設備上執行。
Desktop Bridge 是延續企業軟體最好的機制,因此幫忙研究一些可能用到的技術,希望對大家有所幫忙。謝謝。
References:
- Toast notifications from desktop apps
- Send a local toast notification from desktop C# apps
- UWP APIs callable from a classic desktop app
- Calling Windows 10 APIs From a Desktop Application
- Microsoft.Toolkit.Uwp.Notifications
- Toast content
- Desktop app bridge to UWP Samples
- Adding UWP features to your existing PC software
- Desktop Applications with XAML. Part 2: Desktop Bridge
- Using the Desktop Bridge