I'm using the code below to send Windows notifications. It's working, but the notification window's title is showing the exe filename instead of the name of my application as defined in Application.Title
.
I want the notification to display 'My application' on the title, but instead, it's displaying 'notif', as displayed on the image below :
What am I missing?
Project source :
program notif;
uses
Vcl.Forms,
unit1 in '..\unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.Title := 'My application';
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Unit source :
unit unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Notification;
type
TForm1 = class(TForm)
Button1: TButton;
NotificationCenter1: TNotificationCenter;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
MyNotification: TNotification; //Defines a TNotification variable
begin
MyNotification := NotificationCenter1.CreateNotification; //Creates the notification
try
MyNotification.Name := 'My application name'; //Defines the name of the notification.
MyNotification.Title := 'Title'; //Defines the name that appears when the notification is presented.
MyNotification.AlertBody := 'Hello, im here'; //Defines the body of the notification that appears below the title.
NotificationCenter1.PresentNotification(MyNotification); //Presents the notification on the screen.
finally
MyNotification.Free; //Frees the variable
end;
end;
end.
The notificaiton library has now been published on a GitHub repository by me, with updated code and lots of new functions, such as notify events, controls, buttons and more.
The library with any required dependencies included can be found here.
The original response with the first iteration of the library is unchanged below this update.
You can use WinRT for this with the help of Winapi.UI.Notifications
. I have made a custom class to make this easier.
Still a work in progress, so not everything works as expected, but It should be able to do the basics.
Example:
{***********************************************************}
{ Codruts Notification Manager }
{ }
{ version 1.0 }
{ }
{ }
{ }
{ }
{ }
{ Copyright 2023 Codrut Software }
{***********************************************************}
{$SCOPEDENUMS ON}
unit Cod.NotificationManager;
interface
uses
// System
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Forms,
// Windows RT (Runtime)
Winapi.Winrt,
Winapi.Winrt.Utils,
Winapi.DataRT,
Winapi.UI.Notifications,
// Winapi
Winapi.CommonTypes,
Winapi.Foundation;
type
// Cardinals
TSoundEventValue = (
Default,
NotificationDefault,
NotificationIM,
NotificationMail,
NotificationReminder,
NotificationSMS,
NotificationLoopingAlarm,
NotificationLoopingAlarm2,
NotificationLoopingAlarm3,
NotificationLoopingAlarm4,
NotificationLoopingAlarm5,
NotificationLoopingAlarm6,
NotificationLoopingAlarm7,
NotificationLoopingAlarm8,
NotificationLoopingAlarm9,
NotificationLoopingAlarm10,
NotificationLoopingCall,
NotificationLoopingCall2,
NotificationLoopingCall3,
NotificationLoopingCall4,
NotificationLoopingCall5,
NotificationLoopingCall6,
NotificationLoopingCall7,
NotificationLoopingCall8,
NotificationLoopingCall9,
NotificationLoopingCall10
);
TWinBoolean = (Default, False, True);
TImagePlacement = (Default, Hero, LogoOverride);
TImageCrop = (Default, Circle);
TInputType = (Text, Selection);
TXMLInterface = Xml_Dom_IXmlDocument;
// Records
(* https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-header *)
TNotificationHeader = record
ID: string;
Title: string;
Arguments: string;
ActivationType: string;
function ToXML: string;
end;
(* https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=xml *)
TInputItem = record
ID: string;
Content: string;
function ToXML: string;
end;
TNotificationInput = record
ID: string;
InputType: TInputType;
PlaceHolder: string;
Title: string;
Selections: TArray<TInputItem>;
function ToXML: string;
end;
(* https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-image *)
TNotificationImage = record
ImageQuery: string;
Alt: string;
Source: string; // URL or Local file path
Placement: TImagePlacement;
HintCrop: TImageCrop;
function ToXML: string;
end;
(* https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-audio *)
TNotificationAudio = record
Sound: TSoundEventValue;
Loop: TWinBoolean;
Silent: TWinBoolean;
function ToXML: string;
end;
(* https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/element-progress *)
TNotificationProgress = record
Title: string;
Status: string; // Name, such as "installing" or "downloading"
Value: single; // 0.0 - 1.0
ValueOverride: string; // Override percentage text
function ToXML: string;
end;
// Classes
TNotification = class(TObject)
private
FTitle: string;
FText: string;
FTextExtra: string;
FXML: TXMLInterface;
FToast: IToastNotification;
function BuildXMLDoc: TXMLInterface;
public
// Basic
property Title: string read FTitle write FTitle;
property Text: string read FText write FText;
property TextExtra: string read FTextExtra write FTextExtra;
// Advanced
var
Image: TNotificationImage;
Audio: TNotificationAudio;
Progress: TNotificationProgress;
Input: TNotificationInput;
Header: TNotificationHeader;
property Toast: IToastNotification read FToast;
// Procs
function ToXML: string;
procedure UpdateToastInterface;
// Constructors
constructor Create;
destructor Destroy; override;
end;
TNotificationManager = class(TObject)
private
FNotifier: IToastNotifier;
FApplicationName: string;
// Notifier
procedure RebuildNotifier;
// Setters
procedure SetAppName(const Value: string);
public
// Notificaitons
procedure ShowNotification(Notification: TNotification);
procedure HideNotification(Notification: TNotification);
// App
property ApplicationName: string read FApplicationName write SetAppName;
// Utils
function CreateNewNotification: TNotification;
// Constructors
constructor Create;
destructor Destroy; override;
end;
// Utils
function AudioTypeToString(AType: TSoundEventValue): string;
function WinBooleanToString(AType: TWinBoolean): string;
function StringToRTString(Value: string): HSTRING; // Needs to be freed with WindowsDeleteString
// XML
function EncapsulateXML(Name: string; Container: string; Tags: string = ''): string;
function CreateNewXMLInterface: TXMLInterface;
function StringToXMLDocument(Str: string): TXMLInterface;
function XMLDocumentToString(AXML: TXMLInterface): string;
procedure XMLDocumentEdit(AXML: TXMLInterface; NewContents: string);
implementation
const
NOTIF_BUILD_ERR = 'Notification toast has not been built. E:UpdateToastInterface';
{ TNotificationAudio }
function AudioTypeToString(AType: TSoundEventValue): string;
begin
case AType of
TSoundEventValue.NotificationDefault: Result := 'ms-winsoundevent:Notification.Default';
TSoundEventValue.NotificationIM: Result := 'ms-winsoundevent:Notification.IM';
TSoundEventValue.NotificationMail: Result := 'ms-winsoundevent:Notification.Mail';
TSoundEventValue.NotificationReminder: Result := 'ms-winsoundevent:Notification.Reminder';
TSoundEventValue.NotificationSMS: Result := 'ms-winsoundevent:Notification.SMS';
TSoundEventValue.NotificationLoopingAlarm: Result := 'ms-winsoundevent:Notification.Looping.Alarm';
TSoundEventValue.NotificationLoopingAlarm2: Result := 'ms-winsoundevent:Notification.Looping.Alarm2';
TSoundEventValue.NotificationLoopingAlarm3: Result := 'ms-winsoundevent:Notification.Looping.Alarm3';
TSoundEventValue.NotificationLoopingAlarm4: Result := 'ms-winsoundevent:Notification.Looping.Alarm4';
TSoundEventValue.NotificationLoopingAlarm5: Result := 'ms-winsoundevent:Notification.Looping.Alarm5';
TSoundEventValue.NotificationLoopingAlarm6: Result := 'ms-winsoundevent:Notification.Looping.Alarm6';
TSoundEventValue.NotificationLoopingAlarm7: Result := 'ms-winsoundevent:Notification.Looping.Alarm7';
TSoundEventValue.NotificationLoopingAlarm8: Result := 'ms-winsoundevent:Notification.Looping.Alarm8';
TSoundEventValue.NotificationLoopingAlarm9: Result := 'ms-winsoundevent:Notification.Looping.Alarm9';
TSoundEventValue.NotificationLoopingAlarm10: Result := 'ms-winsoundevent:Notification.Looping.Alarm10';
TSoundEventValue.NotificationLoopingCall: Result := 'ms-winsoundevent:Notification.Looping.Call';
TSoundEventValue.NotificationLoopingCall2: Result := 'ms-winsoundevent:Notification.Looping.Call2';
TSoundEventValue.NotificationLoopingCall3: Result := 'ms-winsoundevent:Notification.Looping.Call3';
TSoundEventValue.NotificationLoopingCall4: Result := 'ms-winsoundevent:Notification.Looping.Call4';
TSoundEventValue.NotificationLoopingCall5: Result := 'ms-winsoundevent:Notification.Looping.Call5';
TSoundEventValue.NotificationLoopingCall6: Result := 'ms-winsoundevent:Notification.Looping.Call6';
TSoundEventValue.NotificationLoopingCall7: Result := 'ms-winsoundevent:Notification.Looping.Call7';
TSoundEventValue.NotificationLoopingCall8: Result := 'ms-winsoundevent:Notification.Looping.Call8';
TSoundEventValue.NotificationLoopingCall9: Result := 'ms-winsoundevent:Notification.Looping.Call9';
TSoundEventValue.NotificationLoopingCall10: Result := 'ms-winsoundevent:Notification.Looping.Call10';
else Result := '';
end;
end;
function WinBooleanToString(AType: TWinBoolean): string;
begin
case AType of
TWinBoolean.Default: Result := 'default';
TWinBoolean.False: Result := 'false';
TWinBoolean.True: Result := 'true';
end;
end;
function StringToRTString(Value: string): HSTRING;
begin
if NOT Succeeded(
WindowsCreateString(PWideChar(Value), Length(Value), Result)
)
then raise Exception.CreateFmt('Unable to create HString for %s', [ Value ] );
end;
function EncapsulateXML(Name: string; Container, Tags: string): string;
var
TagBegin, TagEnd: string;
begin
if Container <> '' then
TagEnd := Format('</%S>', [Name])
else
TagEnd := '';
TagBegin := Format('<%S', [Name]);
if Tags <> '' then
TagBegin := Format('%S %S', [TagBegin, Tags]);
if Container <> '' then
TagBegin := TagBegin + '>'
else
TagBegin := TagBegin + '/>';
Result := TagBegin + Container + TagEnd;
end;
function StringToXMLDocument(Str: string): TXMLInterface;
begin
Result := CreateNewXMLInterface;
XMLDocumentEdit(Result, Str);
end;
function CreateNewXMLInterface: TXMLInterface;
var
Manager: TToastNotificationManager;
begin
Manager := TToastNotificationManager.Create;
try
Result := Manager.GetTemplateContent(ToastTemplateType.ToastText01);
XMLDocumentEdit(Result, '<xml />');
finally
Manager.Free;
end;
end;
function XMLDocumentToString(AXML: TXMLInterface): string;
function HStringToString(Src: HSTRING): String;
var
c: Cardinal;
begin
c := WindowsGetStringLen(Src);
Result := WindowsGetStringRawBuffer(Src, @c);
end;
begin
Result := HStringToString(
( AXML.DocumentElement as Xml_Dom_IXmlNodeSerializer ).GetXml
);
end;
procedure XMLDocumentEdit(AXML: TXMLInterface; NewContents: string);
var
hXML: HSTRING;
begin
hXML := StringToRTString( NewContents );
try
(AXML as Xml_Dom_IXmlDocumentIO).LoadXml( hXML );
finally
WindowsDeleteString( hXML );
end;
end;
function TNotificationAudio.ToXML: string;
begin
Result := '';
if Sound <> TSoundEventValue.Default then
Result := Result + 'src="' + AudioTypeToString(Sound) + '" ';
if Loop <> TWinBoolean.Default then
Result := Result + 'loop="' + WinBooleanToString(Loop) + '" ';
if Silent <> TWinBoolean.Default then
Result := Result + 'silent="' + WinBooleanToString(Silent) + '"';
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('audio', '', Result);
end;
{ TNotification }
function TNotification.BuildXMLDoc: TXMLInterface;
begin
Result := StringToXMLDocument( ToXML );
end;
constructor TNotification.Create;
begin
inherited;
FTitle := 'Hello world!';
FText := 'This is a notification';
FXML := CreateNewXMLInterface;
end;
destructor TNotification.Destroy;
begin
FXML := nil;
inherited;
end;
function TNotification.ToXML: string;
function TextToXML(Value: string): string;
begin
if Value <> '' then
Result := EncapsulateXML('text', Value)
else
Result := '';
end;
begin
Result := '';
Result := Result + '<toast activationType="protocol">';
// Visual
Result := Result + '<visual>';
Result := Result + '<binding template="ToastGeneric">';
// Text
Result := Result + TextToXML(Title);
Result := Result + TextToXML(Text);
Result := Result + TextToXML(TextExtra);
// Extra
Result := Result + Image.ToXML;
Result := Result + Progress.ToXML;
Result := Result + '</binding>';
Result := Result + '</visual>';
// Input
const Input = Input.ToXML;
if Input <> '' then
Result := Result + EncapsulateXML('actions', Input);
// Audio
Result := Result + Audio.ToXML;
// Header
Result := Result + Header.ToXML;
Result := Result + '</toast>';
end;
procedure TNotification.UpdateToastInterface;
begin
FToast := nil;
FToast := TToastNotification.CreateToastNotification(BuildXMLDoc);
end;
{ TNotificationProgress }
function TNotificationProgress.ToXML: string;
begin
Result := '';
// Check disabled/invalid
if Status = '' then
Exit;
if Title <> '' then
Result := Result + 'title="' + Title + '" ';
Result := Result + 'status="' + Status + '" ';
Result := Result + 'value="' + Value.ToString + '" ';
if ValueOverride <> '' then
Result := Result + 'valueStringOverride="' + ValueOverride + '"';
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('progress', '', Result);
end;
{ TNotificationImage }
function TNotificationImage.ToXML: string;
begin
Result := '';
// Check disabled/invalid
if Source = '' then
Exit;
if ImageQuery <> '' then
Result := Result + 'addImageQuery="' + ImageQuery + '" ';
if Alt <> '' then
Result := Result + 'alt="' + Alt + '" ';
Result := Result + 'src="' + Source + '" ';
case Placement of
TImagePlacement.Hero: Result := Result + 'placement="hero" ';
TImagePlacement.LogoOverride: Result := Result + 'placement="appLogoOverride" ';
end;
case HintCrop of
TImageCrop.Circle: Result := Result + 'hint-crop="circle" ';
end;
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('image', '', Result);
end;
{ TInputItem }
function TInputItem.ToXML: string;
begin
Result := '';
// Check disabled/invalid
if ID = '' then
Exit;
Result := Result + 'id="' + ID + '" ';
Result := Result + 'content="' + Content + '" ';
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('selection', '', Result);
end;
{ TNotificationInput }
function TNotificationInput.ToXML: string;
var
Inputs: string;
I: integer;
begin
Result := '';
// Check disabled/invalid
if ID = '' then
Exit;
Result := Result + 'id="' + ID + '" ';
case InputType of
TInputType.Text: Result := Result + 'type="text" ';
TInputType.Selection: Result := Result + 'type="selection" ';
end;
if PlaceHolder <> '' then
Result := Result + 'placeHolderContent="' + PlaceHolder + '" ';
if Title <> '' then
Result := Result + 'title="' + Title + '" ';
// Inputs
Inputs := '';
if Length(Selections) > 0 then
for I := 0 to High(Inputs) do
Inputs := Inputs + Selections[I].ToXML;
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('input', Inputs, Result);
end;
{ TNotificationManager }
constructor TNotificationManager.Create;
begin
FApplicationName := ExtractFileName(Application.ExeName);
RebuildNotifier;
end;
function TNotificationManager.CreateNewNotification: TNotification;
begin
Result := TNotification.Create;
end;
destructor TNotificationManager.Destroy;
begin
FNotifier := nil;
inherited;
end;
procedure TNotificationManager.HideNotification(Notification: TNotification);
begin
if Notification.Toast = nil then
raise Exception.Create(NOTIF_BUILD_ERR);
FNotifier.Hide(Notification.Toast);
end;
procedure TNotificationManager.RebuildNotifier;
var
AName: HSTRING;
begin
FNotifier := nil;
AName := StringToRTString(FApplicationName);
FNotifier := TToastNotificationManager.CreateToastNotifier(AName);
WindowsDeleteString(AName);
end;
procedure TNotificationManager.SetAppName(const Value: string);
begin
if FApplicationName = Value then
Exit;
FApplicationName := Value;
RebuildNotifier;
end;
procedure TNotificationManager.ShowNotification(Notification: TNotification);
begin
if Notification.Toast = nil then
raise Exception.Create(NOTIF_BUILD_ERR);
FNotifier.Show(Notification.Toast);
end;
{ TNotificationHeader }
function TNotificationHeader.ToXML: string;
begin
Result := '';
// Check disabled/invalid
if (ID = '') or (Title = '') or (Arguments = '') then
Exit;
Result := Result + 'id="' + ID + '" ';
Result := Result + 'title="' + Title + '" ';
Result := Result + 'arguments="' + Arguments + '" ';
if ActivationType <> '' then
Result := Result + 'activationType="' + ActivationType + '" ';
// Encapsulate
if Result <> '' then
Result := EncapsulateXML('header', '', Result);
end;
end.
To open a notification, do as follows:
var
Manager: TNotificationManager;
Notif: TNotification;
begin
Manager := TNotificationManager.Create;
try
Manager.ApplicationName := 'Awesome Application';
Notif := Manager.CreateNewNotification;
Notif.Title := 'This is the title';
Notif.Text := 'This is the text';
Notif.Image.Source := 'C:\Windows\System32\@facial-recognition-windows-hello.gif';
Notif.Image.Placement := TImagePlacement.Hero;
Notif.Audio.Sound := TSoundEventValue.NotificationIM;
Notif.Progress.Value := 0.5;
Notif.Progress.Status := 'Downloading data';
Notif.UpdateToastInterface;
Manager.ShowNotification(Notif);
finally
Manager.Free;
end;