0

My app has some tasks that need to run in background. I use the task scheduler to run the app when a task is needed and the app close it self when the task is finished. So far, so good.

The tasks could take some time to finish so I send notifications to action center in the begining and in the end of the task. For the first one I have no problems, but in the second notification I want to open a log file when the user click in it.

The action center show both of notifications but the log file does not open when notification is clicked. I guess it's because the app already close.

So far I tried the following.

uses
  System.SysUtils, System.Classes, System.Notification,
  System.Generics.Collections;

type
  TActionCenter = class(TDataModule)
    NotificationCenter: TNotificationCenter;
    procedure NotificationCenterReceiveLocalNotification(Sender: TObject;
      ANotification: TNotification);
  strict private
    FNotifications: TDictionary<string, TOnReceiveLocalNotification>;
  public
    class procedure AppRegister(const ACompanyName: string);
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    procedure SendNotification(const ATitle, AMessage: string); overload;
    procedure SendNotification(const ATitle, AMessage: string; AOnClick: TOnReceiveLocalNotification); overload;
  end;

var
  _ActionCenter: TActionCenter;

...

uses
  System.Hash, System.Win.Registry, Winapi.Windows;

procedure TActionCenter.AfterConstruction;
begin
  inherited;
  FNotifications := TDictionary<string, TOnReceiveLocalNotification>.Create;
end;

class procedure TActionCenter.AppRegister(const ACompanyName: string);
const
  ActionCenterKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\';
var
  ApplicationKey: string;
  Registry: TRegistry;
begin
  ApplicationKey := ACompanyName + '.DesktopToasts.' + THashBobJenkins.GetHashString(ParamStr(0));
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    if not Registry.KeyExists(ActionCenterKey + ApplicationKey) then
    begin
      Registry.Access := KEY_WRITE;
      if Registry.OpenKey(ActionCenterKey + ApplicationKey, True) then
        Registry.WriteInteger('ShowInActionCenter', 1);
    end;
  finally
    Registry.Free;
  end;
end;

procedure TActionCenter.BeforeDestruction;
begin
  inherited;
  FNotifications.Free;
end;

procedure TActionCenter.NotificationCenterReceiveLocalNotification(
  Sender: TObject; ANotification: TNotification);
var
  Pair: TPair<string, TOnReceiveLocalNotification>;
begin
  Pair := FNotifications.ExtractPair(ANotification.Name);
  if Assigned(Pair.Value) then
    Pair.Value(Sender, ANotification);
end;

procedure TActionCenter.SendNotification(const ATitle, AMessage: string);
begin
  SendNotification(ATitle, AMessage, nil);
end;

procedure TActionCenter.SendNotification(const ATitle, AMessage: string;
  AOnClick: TOnReceiveLocalNotification);
var
  Notification: TNotification;
begin
  Notification := NotificationCenter.CreateNotification;
  try
    Notification.Name := TGUID.NewGuid.ToString;
    Notification.Title := ATitle;
    Notification.AlertBody := AMessage;
    FNotifications.Add(Notification.Name, AOnClick);
    NotificationCenter.PresentNotification(Notification);
  finally
    Notification.Free;
  end;
end;

initialization
  TActionCenter.AppRegister('MyCompanyName');
  _ActionCenter := TActionCenter.Create(nil);

finalization
  _ActionCenter.Free;

...

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, ActionCenter.Classes;

type
  TMain = class(TForm)
    Send: TButton;
    procedure SendClick(Sender: TObject);
  strict private
    procedure NotificationClick1(ASender: TObject; ANotification: TNotification);
    procedure NotificationClick2(ASender: TObject; ANotification: TNotification);
    procedure NotificationClick3(ASender: TObject; ANotification: TNotification);
    procedure OpenLog(FileName: TFileName);
  end;

....

uses
  Winapi.ShellAPI;

procedure TMain.NotificationClick1(ASender: TObject; ANotification: TNotification);
begin
  OpenLog('C:\First.txt');
end;

procedure TMain.NotificationClick2(ASender: TObject; ANotification: TNotification);
begin
  OpenLog('C:\Second.txt');
end;

procedure TMain.NotificationClick3(ASender: TObject; ANotification: TNotification);
begin
  OpenLog('C:\Third.txt');
end;

procedure TMain.OpenLog(FileName: TFileName);
var
  Handle: THandle;
begin
  Handle := GetStdHandle(STD_INPUT_HANDLE);
  ShellExecute(Handle, nil, PChar(FileName), nil, nil, SW_SHOWNORMAL);
end;

Is there a way to do this? Do a OnReceiveLocalNotification event that Works even after the app is close?

  • According to documentation (http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_Notifications) clicking notification on Wondows does nothing while on OS X, iOS and Android clicking on notification launches your application. – SilverWarior Jan 10 '19 at 16:41
  • After some more researching it seems that you need to create so called toast notifications. You could find more info about this here: https://stackoverflow.com/a/46750371/3636228 – SilverWarior Jan 10 '19 at 16:48

1 Answers1

0

I solved my problem replacing TNotificationCenter and OnReceiveLocalNotification with TTrayIcon and OnBaloonClick. It seems the TTrayIcon notification keeps the application running for some seconds. It's not the perfect solution but it's enough for now.