You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:42:34