我一直在按照以下步骤构建一个截取Windows 10通知的应用,并按照此处的说明将它们添加到ItemsControl:https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/notification-listener#requesting-access-to-the-listener
该应用不起作用。我对如何为这样的应用程序编写单元测试或如何测试或调试它感到茫然。任何关于它的指针都将非常感激。
更新:根据安德鲁的评论,我安装了Notification Visualizer并弹出通知。我的应用程序捕获它们并按照设计处理它们。然而,问题仍然是应用程序无法处理来自Facebook等外部网站的推送通知。在我看来,浏览器使用自己独立于Windows的通知机制。有没有办法拦截谷歌浏览器或微软边缘的通知?
该应用程序具有以下XAML和三个代码页:
MainPage.xaml中
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,150,0">
<Button Content="Run Listener" HorizontalAlignment="Left" Margin="18,10,0,0" VerticalAlignment="Top" RenderTransformOrigin="-1.492,0.049" Click="Button_Click"/>
<ItemsControl Name="NotificationsIC" HorizontalAlignment="Left" Height="376" Margin="25,61,0,0" VerticalAlignment="Top" Width="775"/>
</Grid>
</Page>
App.xml.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace App1
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
protected override async void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
var deferral = args.TaskInstance.GetDeferral();
switch (args.TaskInstance.Task.Name)
{
case "UserNotificationChanged":
// Call your own method to process the new/removed notifications
// The next section of documentation discusses this code
await NotifcationsProcessor.SyncNotifications();
break;
}
deferral.Complete();
}
}
}
MainPage.xaml.cs中
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Foundation.Metadata;
using Windows.UI.Notifications.Management;
using Windows.Media.SpeechSynthesis;
using Windows.UI.Notifications;
using Windows.ApplicationModel.Background;
using System.Collections.ObjectModel;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App1
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public static ObservableCollection<string> items = new ObservableCollection<string>();
public static UserNotificationListener listener = UserNotificationListener.Current;
public MainPage()
{
this.InitializeComponent();
NotificationsIC.ItemsSource = items;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
MediaElement mediaElement = new MediaElement();
SpeechSynthesizer synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();
await IsListenerSupported(mediaElement, synth);
// Get the listener
UserNotificationListenerAccessStatus accessStatus =
await listener.RequestAccessAsync();
if(accessStatus != UserNotificationListenerAccessStatus.Allowed)
{
await SaySomething(mediaElement, synth, "Access to notifications was not allowed. I can not do anything more. Exiting. Sorry.");
return;
}
// Get the toast notifications
NotifcationsProcessor.GetNotificationsAndProcessThem();
//Request / check background task access via BackgroundExecutionManager.RequestAccessAsync
var requestStatus =
await Windows.ApplicationModel.Background.BackgroundExecutionManager.RequestAccessAsync();
if (requestStatus != BackgroundAccessStatus.AlwaysAllowed)
{ //AllowedSubjectToSystemPolicy is ok too see: https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.background.backgroundaccessstatus
// user has to give permission in the battery use settings:
//On devices that have a battery, the user has given the app permission in the battery use settings to always allow background access. Introduced in Windows 10, version 1607.
// Depending on the value of requestStatus, provide an appropriate response
// such as notifying the user which functionality won't work as expected
}
// If background task isn't registered yet
if (!BackgroundTaskRegistration.AllTasks.
Any(i => i.Value.Name.Equals("UserNotificationChanged")))
{
// Specify the background task
var builder = new BackgroundTaskBuilder()
{
Name = "UserNotificationChanged"
};
// Set the trigger for Listener, listening to Toast Notifications
builder.SetTrigger(new UserNotificationChangedTrigger(NotificationKinds.Toast));
// Register the task
builder.Register();
}
}
private static async System.Threading.Tasks.Task
IsListenerSupported(MediaElement mediaElement, SpeechSynthesizer synth)
{
string ListenerIsSupported;
if (ApiInformation.IsTypePresent("Windows.UI.Notifications.Management.UserNotificationListener"))
{
ListenerIsSupported = "Listener Is Supported";
}
else
{
ListenerIsSupported = "Oh no! Listener Is NOT Supported. Oh No!. You need to upgrade your windows to the latest version of Windows 10.";
}
await SaySomething(mediaElement, synth, ListenerIsSupported);
}
private static async System.Threading.Tasks.Task SaySomething(MediaElement mediaElement, SpeechSynthesizer synth, string WhatToSay)
{
Windows.Media.SpeechSynthesis.SpeechSynthesisStream stream =
await synth.SynthesizeTextToStreamAsync(WhatToSay);
mediaElement.SetSource(stream, stream.ContentType);
mediaElement.Play();
}
}
}
NotificationsProcessor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Notifications;
using Windows.UI.Notifications.Management;
namespace App1
{
public class NotifcationsProcessor
{
internal static async Task SyncNotifications()
{
NotifcationsProcessor.GetNotificationsAndProcessThem();
}
public static async void GetNotificationsAndProcessThem()
{
IReadOnlyList<UserNotification> notifs =
await MainPage.listener.GetNotificationsAsync(NotificationKinds.Toast);
foreach (var n in notifs)
{
// Get the toast binding, if present
NotificationBinding toastBinding = n.Notification.Visual.GetBinding(KnownNotificationBindings.ToastGeneric);
if (toastBinding != null)
{
// And then get the text elements from the toast binding
IReadOnlyList<AdaptiveNotificationText> textElements = toastBinding.GetTextElements();
string bodyText =
string.Join(" | ", textElements.Select(t => t.Text));
MainPage.items.Add(bodyText);
}
}
}
}
}
注意:我在调试器中运行了大量断点,希望在收到通知时,会遇到断点。没有这样的运气。我也在我的开发机器上加载了应用程序,仍然没有迹象表明它正在拦截通知。