【翻译】使用通知监听器 API 获取 Windows 通知中心的内容

前言

从 Windows 8.1 开始,Windows 通知现在以 Toast 而非 Balloon 形式显示,并记录在通知中心中。到目前为止,为了检索通知的内容,您必须抓取窗口的句柄并尝试读取它的文本内容,或者其他类似的东西。

特别是在 Toast 可用之后,这样做变得很困难,但从 Windows 10 Anniversary Edition (10.0.14393.0) 版本开始, MS 实现了“通知侦听器” API(UserNotificationListener),允许您以与获取 Android 通知相同的方式获取 Windows 通知。

实现原理与应用

通知侦听器:访问所有通知 - Windows 开发人员中心:

https://learn.microsoft.com/zh-cn/windows/apps/design/shell/tiles-and-notifications/notification-listener

虽然这是 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 运行时环境。

GitHub - gpsnmeajp/NotificationListenerThrowerContribute to gpsnmeajp/NotificationListenerThrower development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/gpsnmeajp/NotificationListenerThrower?tab=readme-ov-file

参考文献

1.使用 BLE GATT 和 Windows 桌面应用程序与温度计和血压计进行通信

2.在 Windows 10 上使用 GATT 与 BLE 设备通信的操作方法笔记

3.用于扫描 iBeacons 的 Windows 桌面应用程序

4.成功实现!C# 中的异步处理(任务和异步等待)

5.有关在 C# 控制台应用程序中执行异步处理的说明


本文翻译自下面的文章。

文章标题:Windowsの通知センターの内容を取得する(通知リスナー)

文章链接:https://qiita.com/gpsnmeajp/items/b139f3e45597742c99d0

作者:@gpsnmeajp(Segment)

时间:发布于: 2018-10-24,上一次更新于: 2021-06-27。

翻译于:2024-03-03。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值