如何在Delphi中接收Windows Toast通知的激活与关闭事件
Delphi中Windows 11 Toast通知事件回调无法触发的解决方案
问题背景
正在开发一个Delphi库,通过Winapi.UI.Notifications接口简化Windows 11 Toast通知的实现,目标是推送自定义通知到操作中心,并接收通知激活(点击)或关闭时的事件。IToastNotification提供了add_Activated()、add_Dismissed()和add_Failed()方法用于注册事件回调,但目前通知能正常显示,点击或关闭时对应的Invoke()方法从未触发。
C#参考实现
在C#中实现该功能较为直观:
public void ShowToastNotification() { // 构造包含数据绑定字段的Toast内容 var content = new ToastContentBuilder() .AddText("Notification title") .GetToastContent(); // 生成Toast通知 var toast = new ToastNotification(content.GetXml()); toast.Activated += Toast_Activated; // 显示通知 ToastNotificationManager.CreateToastNotifier().Show(toast); } private void Toast_Activated(ToastNotification sender, object args) { ShowMessageDialog($"Toast activated!"); }
Delphi现有代码
当前Delphi实现代码如下:
unit Unit1; interface uses Winapi.Windows, Vcl.Forms, Winapi.ui.Notifications, Winapi.CommonTypes, Winapi.Winrt, Vcl.Dialogs, Winapi.DataRT; type TToastActivatedHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IInspectable) procedure Invoke(sender: IToastNotification; args: IInspectable); safecall; end; TToastDismissHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs) procedure Invoke(sender: IToastNotification; args: IToastDismissedEventArgs); safecall; end; TForm1 = class(TForm) procedure FormCreate(Sender: TObject); end; var Form1: TForm1; ActivateHandle: TToastActivatedHandler; DismissHandle: TToastDismissHandler; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); var HStr: HSTRING; S: string; Instance: IInspectable; Notifier: IToastNotifier; begin // Notification maanger const xml = '<toast activationType="protocol">'+#13#10+' <visual>'+#13#10+' <binding template="ToastGeneric">'+#13#10+' <text>Hello world!</text>'+#13#10+' <text>This is a test notification.</text>'+#13#10+' <image src="C:\Windows\System32\@facial-recognition-windows-hello.gif" placement="hero" alt=""/>'+#13#10+' </binding>'+#13#10+' </visual>'+#13#10+'</toast>'; // Create XML S := 'Windows.Data.Xml.Dom.XmlDocument'; WindowsCreateString(PChar(S), Length(S), HStr); try RoActivateInstance(HStr, Instance); finally WindowsDeleteString(HStr); end; // Load XML WindowsCreateString(PChar(xml), Length(xml), HStr); try (Instance as Xml_Dom_IXmlDocumentIO).LoadXml( HStr ); finally WindowsDeleteString(HStr); end; // Create interfaces S := 'App.Test'; WindowsCreateString(PChar(S), Length(S), HStr); try Notifier := TToastNotificationManager.CreateToastNotifier(HStr); finally WindowsDeleteString(HStr); end; const Notification = TToastNotification.CreateToastNotification( Instance as Xml_Dom_IXmlDocument ); // Create activator ActivateHandle := TToastActivatedHandler.Create; DismissHandle := TToastDismissHandler.Create; // Prepare Notification.add_Activated( ActivateHandle ); Notification.add_Dismissed( DismissHandle ); // Show Notifier.Show( Notification ); end; { TToastDismissHandler } procedure TToastDismissHandler.Invoke(sender: IToastNotification; args: IToastDismissedEventArgs); begin ShowMessage('Toast was dismissed!'); end; { TToastActivatedHandler } procedure TToastActivatedHandler.Invoke(sender: IToastNotification; args: IInspectable); begin ShowMessage('Toast was activated'); end; end.
问题分析与解决方案
1. 事件注册令牌未保留
WinRT事件注册依赖EventRegistrationToken跟踪回调,若未保存该令牌,回调可能被系统自动取消。需修改代码保存令牌:
- 在单元变量区添加令牌声明:
var Form1: TForm1; ActivateHandle: TToastActivatedHandler; DismissHandle: TToastDismissHandler; ActivateToken: EventRegistrationToken; DismissToken: EventRegistrationToken; Notification: IToastNotification; // 将Notification改为全局变量,方便后续取消注册
- 修改注册代码:
// Prepare Notification.add_Activated(ActivateHandle, ActivateToken); Notification.add_Dismissed(DismissHandle, DismissToken);
2. AppUserModelID未注册
Windows Toast通知需要应用拥有有效的AppUserModelID,否则系统无法将事件路由到应用。在FormCreate开头添加注册代码:
procedure TForm1.FormCreate(Sender: TObject); var // ... 原有变量 AppID: string; HAppID: HSTRING; begin // 注册AppUserModelID AppID := 'App.Test'; WindowsCreateString(PChar(AppID), Length(AppID), HAppID); try SetCurrentProcessExplicitAppUserModelID(HAppID); finally WindowsDeleteString(HAppID); end; // 后续原有代码... end;
3. 调整Toast的ActivationType
若无需协议激活,将XML中的activationType改为foreground,确保点击通知时事件能正确触发:
const xml = '<toast activationType="foreground">'+#13#10+' <visual>'+#13#10+' <binding template="ToastGeneric">'+#13#10+' <text>Hello world!</text>'+#13#10+' <text>This is a test notification.</text>'+#13#10+' <image src="C:\Windows\System32\@facial-recognition-windows-hello.gif" placement="hero" alt=""/>'+#13#10+' </binding>'+#13#10+' </visual>'+#13#10+'</toast>';
4. 正确释放资源与取消事件注册
在FormDestroy中手动取消事件注册并释放资源:
procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(Notification) then begin Notification.remove_Activated(ActivateToken); Notification.remove_Dismissed(DismissToken); end; ActivateHandle.Free; DismissHandle.Free; end;
修改后的完整Delphi代码
unit Unit1; interface uses Winapi.Windows, Vcl.Forms, Winapi.ui.Notifications, Winapi.CommonTypes, Winapi.Winrt, Vcl.Dialogs, Winapi.DataRT; type TToastActivatedHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IInspectable) procedure Invoke(sender: IToastNotification; args: IInspectable); safecall; end; TToastDismissHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs) procedure Invoke(sender: IToastNotification; args: IToastDismissedEventArgs); safecall; end; TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); end; var Form1: TForm1; ActivateHandle: TToastActivatedHandler; DismissHandle: TToastDismissHandler; ActivateToken: EventRegistrationToken; DismissToken: EventRegistrationToken; Notification: IToastNotification; implementation {$R *.dfm} procedure TForm1.FormCreate(Sender: TObject); var HStr: HSTRING; S: string; Instance: IInspectable; Notifier: IToastNotifier; AppID: string; HAppID: HSTRING; begin // 注册AppUserModelID AppID := 'App.Test'; WindowsCreateString(PChar(AppID), Length(AppID), HAppID); try SetCurrentProcessExplicitAppUserModelID(HAppID); finally WindowsDeleteString(HAppID); end; // Notification XML const xml = '<toast activationType="foreground">'+#13#10+' <visual>'+#13#10+' <binding template="ToastGeneric">'+#13#10+' <text>Hello world!</text>'+#13#10+' <text>This is a test notification.</text>'+#13#10+' <image src="C:\Windows\System32\@facial-recognition-windows-hello.gif" placement="hero" alt=""/>'+#13#10+' </binding>'+#13#10+' </visual>'+#13#10+'</toast>'; // Create XML Document S := 'Windows.Data.Xml.Dom.XmlDocument'; WindowsCreateString(PChar(S), Length(S), HStr); try RoActivateInstance(HStr, Instance); finally WindowsDeleteString(HStr); end; // Load XML content WindowsCreateString(PChar(xml), Length(xml), HStr); try (Instance as Xml_Dom_IXmlDocumentIO).LoadXml(HStr); finally WindowsDeleteString(HStr); end; // Create Toast Notifier S := 'App.Test'; WindowsCreateString(PChar(S), Length(S), HStr); try Notifier := TToastNotificationManager.CreateToastNotifier(HStr); finally WindowsDeleteString(HStr); end; // Create Toast Notification Notification := TToastNotification.CreateToastNotification(Instance as Xml_Dom_IXmlDocument); // Create event handlers ActivateHandle := TToastActivatedHandler.Create; DismissHandle := TToastDismissHandler.Create; // Register events with tokens Notification.add_Activated(ActivateHandle, ActivateToken); Notification.add_Dismissed(DismissHandle, DismissToken); // Show notification Notifier.Show(Notification); end; procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(Notification) then begin Notification.remove_Activated(ActivateToken); Notification.remove_Dismissed(DismissToken); end; ActivateHandle.Free; DismissHandle.Free; end; { TToastDismissHandler } procedure TToastDismissHandler.Invoke(sender: IToastNotification; args: IToastDismissedEventArgs); begin ShowMessage('Toast was dismissed!'); end; { TToastActivatedHandler } procedure TToastActivatedHandler.Invoke(sender: IToastNotification; args: IInspectable); begin ShowMessage('Toast was activated'); end; end.
内容的提问来源于stack exchange,提问作者Codrut
相关产品推荐
相关产品推荐

