How to create toast notification in delphi with To

2020-07-26 13:42发布

问题:

I am developing on Desktop with delphi. I would like to create toast notification with ToastGeneric type notification LToastFactory.CreateToastNotification(LXMLTemplate);

In addition , i am using an xml as in https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-adaptive-interactive-toasts

My question is how can i make delphi to accept this xml, i haven't found a way to convert that string into Xml_Dom_IXmlDocument type.

回答1:

I had the same problem.

My objective was to create a Toast notification from Delphi that uses toastGeneric. However, I could not find any examples in any language that do this by manipulating the xml - all the examples use classes that do not appear to be accessible from Delphi.

My solution was to create a standard template and then overwrite the xml in that standard template with the xml required for a custom template. Below is some Delphi code that should give you the idea. It is a complete console app. This code compiles under Delphi 10.2 Tokyo. There may be some tweaks required for earlier versions. You will be interested in the OverwriteToastTemplateXML function.

My code was based around a standard toast template example referenced in the final comment in Marco Cantu's blog post here: http://blog.marcocantu.com/blog/2015-june-windows10-notifications-vcl-winrt.html

Note the reference in my XML to a 'hero' image jpg file. For the notification to work fully, make sure you have a jpg at c:\notifications\hero.jpg, or comment out that line in the xml.

As well as converting an XML string into a custom toast template, the code also converts a toast template back into a string, which is useful for debugging - this is the ToastTemplateToString function. These are my main functional modifications to the original example. For my own understanding, I have also changed the structure of the example code, so that variable scope and how each line of code relates to the others is also more apparent.

Let me know if this works for you - I have found that toast notification are hard work from Delphi!

Cheers

Steve

program ConsoleNotifier;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,

  // Required to create the Toast Notification
  WinAPI.WinRT,
  WinAPI.DataRT,
  WinAPI.UI.Notifications,
  WinAPI.ActiveX,
  WinAPI.CommonTypes,

  // Required for creating the Desktop Shell Link
  WinAPI.PropKey,
  WinAPI.PropSys,
  WinAPI.ShlObj,
  System.Win.ComObj,
  Windows
  ;

function CreateDesktopShellLink(const TargetName: string): Boolean;

  function GetStartMenuFolder: string;
  var
    Buffer: array [0 .. MAX_PATH - 1] of Char;
  begin
    Result := '';
    GetEnvironmentVariable(PChar('APPDATA'), Buffer, MAX_PATH - 1);
    Result := Buffer + '\Microsoft\Windows\Start Menu\Programs\Desktop Delphi Toasts App.lnk';
  end;

var
  IObject: IUnknown;
  ISLink: IShellLink;
  IPFile: IPersistFile;
  LinkName: string;

  LStore: WinAPI.PropSys.IPropertyStore;
  LValue: TPropVariant;
begin
  Result := False;

  IObject := CreateComObject(CLSID_ShellLink);
  ISLink := IObject as IShellLink;
  IPFile := IObject as IPersistFile;
  LStore := IObject as WinAPI.PropSys.IPropertyStore;

  with ISLink
  do begin
    SetPath(PChar( ParamStr(0) ));
  end;
  ISLink.SetArguments(PChar(''));

  if Succeeded(InitPropVariantFromStringAsVector(PWideChar('Delphi.DesktopNotification.Sample'), LValue))
  then begin
    if Succeeded(LStore.SetValue(PKEY_AppUserModel_ID, LValue))
    then LStore.Commit;
  end;

  LinkName := GetStartMenuFolder;

  if not FileExists(LinkName)
  then
    if IPFile.Save(PWideChar(LinkName), True) = S_OK
    then Result := True;
end;

function HStr( 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 ToastTemplateToString( Const Template:Xml_Dom_IXmlDocument ): String;

  function HStringToString(Src: HSTRING): String;
  var
    c: Cardinal;
  begin
    c := WindowsGetStringLen(Src);
    Result := WindowsGetStringRawBuffer(Src, @c);
  end;

begin
  Result := HStringToString(
    ( Template.DocumentElement as Xml_Dom_IXmlNodeSerializer ).GetXml
  );
end;

function GetFactory( Const Name:String; Const GUID:String ): IInspectable;
var
  FactoryHString : HString;
  FactoryGUID    : TGUID;
begin
  FactoryHString := HStr( Name );
  try
    FactoryGUID := TGUID.Create(GUID);

    if NOT Succeeded(
      RoGetActivationFactory(FactoryHString, FactoryGUID, Result)
    )
    then raise Exception.CreateFmt('Error creating factory: %s %s', [ Name, GUID ] );
  finally
    WindowsDeleteString( FactoryHString );
  end;
end;

procedure OverwriteToastTemplateXML( Const Template: Xml_Dom_IXmlDocument; Const XML:String );
var
  hXML: HSTRING;
begin
  hXML := HStr( XML );
  try
    (Template as Xml_Dom_IXmlDocumentIO).LoadXml( hXML );
  finally
    WindowsDeleteString( hXML );
  end;
end;

procedure SteveNotification( Const AppID:String; Const XML:String );
var
  ToastNotificationManagerStatics : IToastNotificationManagerStatics;
  ToastTemplate                   : Xml_Dom_IXmlDocument;
  LToastNotification              : IToastNotification;
  ToastNotificationManagerFactory : IInspectable;
  ToastNotificationFactory        : IInspectable;
  hAppID                          : HString;
begin
  ToastNotificationManagerFactory := GetFactory( sToastNotificationManager, '{50AC103F-D235-4598-BBEF-98FE4D1A3AD4}' );
  ToastNotificationManagerStatics := IToastNotificationManagerStatics(ToastNotificationManagerFactory);
  ToastTemplate := ToastNotificationManagerStatics.GetTemplateContent(ToastTemplateType.ToastText01);

  OverwriteToastTemplateXML( ToastTemplate, XML );

  WriteLn( 'XML: ', ToastTemplateToString( ToastTemplate ) );

  ToastNotificationFactory := GetFactory( SToastNotification, '{04124B20-82C6-4229-B109-FD9ED4662B53}' );

  LToastNotification := IToastNotificationFactory(ToastNotificationFactory).CreateToastNotification(ToastTemplate);

  hAppID := HStr( AppID );
  try
    ToastNotificationManagerStatics
    .CreateToastNotifier( hAppID )
    .Show(LToastNotification);
  finally
    WindowsDeleteString( hAppID );
  end;
end;

Const
  AppID = 'My Application ID';
  XML   = '<toast activationType="protocol" launch="http://www.ecutek.com" >'
        + '  <visual>'
        + '    <binding template="ToastGeneric">'
        + '      <text>Body Text ABC</text>'
        + '      <text>More Text</text>'
        + '      <image placement="hero" src="file:///c:\notifications\hero.jpg"/>'
        + '    </binding>'
        + '  </visual>'
        + '  <actions>'
        + '    <action content="Open Google" activationType="protocol" arguments="http://www.google.com" />'
        + '  </actions>'
        + '</toast>';

var
  c : char;
begin
  try
    if TOSVersion.Major < 10
    then raise Exception.Create('Windows 10 Required');

    RoInitialize(RO_INIT_MULTITHREADED);

    CreateDesktopShellLink( ParamStr(0) );

    SteveNotification( AppID, XML );

    // Wait for a KeyPress
    Read( c ); write( c );
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.