前言
从 Windows 8.1 开始,Windows 通知现在以 Toast 而非 Balloon 形式显示,并记录在通知中心中。到目前为止,为了检索通知的内容,您必须抓取窗口的句柄并尝试读取它的文本内容,或者其他类似的东西。
特别是在 Toast 可用之后,这样做变得很困难,但从 Windows 10 Anniversary Edition (10.0.14393.0) 版本开始, MS 实现了“通知侦听器” API(UserNotificationListener),允许您以与获取 Android 通知相同的方式获取 Windows 通知。
实现原理与应用
通知侦听器:访问所有通知 - Windows 开发人员中心:
虽然这是 UWP 应用程序的一项功能,但也可以通过使用 UWPDesktop (UwpDesktop 目前已停止更新,可以改用 C/C++ WinRT)从桌面应用程序获取该功能。
UWPDesktop 的使用方法请参考下面的文章:https://qiita.com/gpsnmeajp/items/607959d9eb76f908ef25。
你可以像下面这样得到它:
(测试代码还没有实现更新时获取的过程,更新版本见作者在 Github 上更新的工具,它使用 WebSocket 转发通知并与本地客户端交互)
代码如下:与 MS 提供的源代码几乎相同。
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI.Notifications.Management;
using Windows.Foundation.Metadata;
using System.Collections.Generic;
using Windows.UI.Notifications;
namespace ConsoleApplication2
{
class Program
{
static int Main(string[] args)
{
// 从控制台应用程序调用 Async 的 Task;
return MainAsync().Result;
}
// Async 的 Main
private static async Task<int> MainAsync()
{
if (!ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener"))
{
Console.WriteLine("IsTypePresent: NG");
return -1;
}
Console.WriteLine("IsTypePresent: OK");
UserNotificationListener listener = UserNotificationListener.Current;
Console.Write("listener: ");
Console.WriteLine(listener);
UserNotificationListenerAccessStatus accessStatus = await listener.RequestAccessAsync();
Console.Write("accessStatus: ");
Console.WriteLine(accessStatus);
if (accessStatus != UserNotificationListenerAccessStatus.Allowed)
{
Console.WriteLine("拒绝访问");
return -1;
}
Console.WriteLine("允许访问");
while(true)
{
IReadOnlyList<UserNotification> notifs = await listener.GetNotificationsAsync(NotificationKinds.Toast);
foreach (var n in notifs) {
NotificationBinding toastBinding = n.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
if (toastBinding != null)
{
IReadOnlyList<AdaptiveNotificationText> textElements = toastBinding.GetTextElements();
string titleText = textElements.FirstOrDefault()?.Text;
string bodyText = string.Join("\n", textElements.Skip(1).Select(t => t.Text));
Console.Write("Title:");
Console.WriteLine(titleText);
Console.Write("Body:");
Console.WriteLine(bodyText);
}
Thread.Sleep(1000);
}
}
return 0;
}
}
}
筛选新的通知:事件功能事实上是不能直接使用的,您必须首先声明它。
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.UI.Notifications.Management;
using Windows.Foundation.Metadata;
using System.Collections.Generic;
using Windows.UI.Notifications;
using Windows.Foundation;
using Windows.ApplicationModel.Background;
namespace ConsoleApplication2
{
class Program
{
static UserNotificationListener listener;
static int Main(string[] args)
{
// 从控制台应用程序调用 Async 使用 Task;
return MainAsync().Result;
}
// Async 的 Main
private static async Task<int> MainAsync()
{
if (!ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener"))
{
Console.WriteLine("IsTypePresent: NG");
return -1;
}
Console.WriteLine("IsTypePresent: OK");
// 并请求访问用户的通知(必须从UI线程调用)
listener = UserNotificationListener.Current;
UserNotificationListenerAccessStatus accessStatus = await listener.RequestAccessAsync();
Console.Write("accessStatus: ");
Console.WriteLine(accessStatus);
if (accessStatus != UserNotificationListenerAccessStatus.Allowed)
{
Console.WriteLine("拒绝访问");
return -1;
}
Console.WriteLine("允许访问");
while (true)
{
newnotice();
Thread.Sleep(1000);
}
return 0;
}
private static void show(UserNotification u)
{
NotificationBinding toastBinding = u.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
if (toastBinding != null)
{
// 然后从toast绑定中获取文本元素
IReadOnlyList<AdaptiveNotificationText> textElements = toastBinding.GetTextElements();
// 将第一个文本元素视为标题文本
string titleText = textElements.FirstOrDefault()?.Text;
// 我们将把所有后续文本元素视为正文文本,
// 并通过换行符将它们连接在一起。
string bodyText = string.Join("\n", textElements.Skip(1).Select(t => t.Text));
Console.Write("Title:");
Console.WriteLine(titleText);
Console.Write("Body:");
Console.WriteLine(bodyText);
}
}
static List<uint> notificationIds = new List<uint>(); // 通知列表
private static async void newnotice()
{
IReadOnlyList<UserNotification> userNotifications = await listener.GetNotificationsAsync(NotificationKinds.Toast);
List<uint> notificationIdsNow = new List<uint>();
// 检测新通知
foreach (UserNotification n in userNotifications)
{
if (!notificationIds.Contains(n.Id))
{
// 检测到新通知!
Console.WriteLine("------------- Add! : " + n.Id + "------------- ");
show(n);
notificationIds.Add(n.Id);
}
// 记录当前的通知
notificationIdsNow.Add(n.Id);
}
// 检测已经不存在的通知
List<uint> removeIdList = new List<uint>();
foreach (uint id in notificationIds)
{
if (!notificationIdsNow.Contains(id))
{
// 移除通知
Console.WriteLine("------------- Remove! : " + id + "------------- ");
removeIdList.Add(id);
}
}
// foreach 中不能删除,所以暂时放在别的里面删除。
foreach (uint id in removeIdList)
{
notificationIds.Remove(id);
}
}
}
}
在 Github 上的更新
于 2021 年 6 月 27 日添加:我创建了一个使用此 API 接收并使用 Websocket 传递它的工具。需要安装 .NET 5 运行时环境。
参考文献
1.使用 BLE GATT 和 Windows 桌面应用程序与温度计和血压计进行通信
2.在 Windows 10 上使用 GATT 与 BLE 设备通信的操作方法笔记
3.用于扫描 iBeacons 的 Windows 桌面应用程序
本文翻译自下面的文章。
文章标题:Windowsの通知センターの内容を取得する(通知リスナー)
文章链接:https://qiita.com/gpsnmeajp/items/b139f3e45597742c99d0
时间:发布于: 2018-10-24,上一次更新于: 2021-06-27。
翻译于:2024-03-03。