I'm making a library for Delphi to implement Windows 11 toast notifications easier using the interfaces from Winapi.UI.Notifications
.
I want to to post custom notifications to the action center using a IToastNotifier
to show the notifications and to be able to to receive events for when when the notification is dismissed or activated (clicked on). Thankfully, the IToastNotification
has the add_Activated()
, add_Dismissed()
and add_Failed()
methods to register a callback for when those events are triggered.
In C#, it's rather easy to add them, like so:
public void ShowToastNotification()
{
// Construct the toast content with data bound fields
var content = new ToastContentBuilder()
.AddText("Notification title")
.GetToastContent();
// Generate the toast notification
var toast = new ToastNotification(content.GetXml());
toast.Activated += Toast_Activated;
// Show the toast notification to the user
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
private void Toast_Activated(ToastNotification sender, object args)
{
ShowMessageDialog($"Toast activated!");
}
To do the same thing in Delphi, I wrote the following code snippet:
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">'+
' <visual>'+
' <binding template="ToastGeneric">'+
' <text>Hello world!</text>'+
' <text>This is a test notification.</text>'+
' <image src="C:\Windows\System32\@facial-recognition-windows-hello.gif" placement="hero" alt=""/>'+
' </binding>'+
' </visual>'+
'</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;
{ TToastActivatedHandler2 }
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.
The following notification appears:
But the Invoke()
method never gets called when I click on the notification, and I'm not sure why. I also tried the add_Dismissed()
one, but the same problem occurs for It as well.
I'm completely out of ideas on what to try. There must be a way to have the invoke method called, but I'm unsure on how this may be accomplished.
Note: The unit file is not needed to run the code above, I only took the snippets from It that are needed to show this example. But the full library can be found on GitHub here.
I've figured out the issue!
It seems that interfaces rely very heavily on their serialized GUIDs, and without the proper IDs added to a class, the registration will fail.
Since TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs
is a child interface of TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs_Delegate_Base
, I assumed It would inherit It's GUID as well, but It seems that's not the case.
The fix is really easy to apply, just add the TypedEventHandler_2__IToastNotification__IInspectable_Delegate_Base
and TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs_Delegate_Base
interface classes to the TToastActivatedHandler
and TToastDismissedHandler
object respectively.
Here is the correct declaration:
TToastActivatedHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IInspectable,
TypedEventHandler_2__IToastNotification__IInspectable_Delegate_Base)
public
procedure Invoke(sender: IToastNotification; args: IInspectable); safecall;
end;
TToastDismissedHandler = class(TInterfacedObject, TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs,
TypedEventHandler_2__IToastNotification__IToastDismissedEventArgs_Delegate_Base)
public
procedure Invoke(sender: IToastNotification; args: IToastDismissedEventArgs); safecall;
end;