4 votes

Comment invoquer une fonction Delphi à partir de JavaScript à l'aide de Cef4Delphi ?

Je suis débutant en Delphi. J'utilise actuellement la version Berlin de Delphi.

J'essaie d'invoquer une fonction/méthode Delphi à partir de JavaScript. Par exemple, je veux ouvrir un nouveau formulaire Delphi en cliquant sur le bouton html avec un attribut de données supplémentaires.

CODE HTML

<input type="button" name="btn" value="Button" id="edit" data-prop="24"></button>
<input type="button" name="btnAnother" value="Button2" id="edit2" data-prop="1"></button>

Lorsque l'on clique sur le bouton, un nouveau [deuxième formulaire] Delphi s'ouvre et affiche les éléments suivants données-propreté de bouton sur un TLabel.

[MISE À JOUR - 12-10-2020]

J'ai essayé de créer une application avec l'aide de la démo de JSExtension. J'ai essayé d'ajouter un événement de clic en javascript mais l'événement de clic sur le html ne se déclenche pas et le deuxième formulaire ne se charge pas. Voici une partie du code

HTML [jsExtensionClickEvent.html]

<!DOCTYPE html>
<html>
<body>

<form method="POST">
  <input type="button" name="btnEx" value="Button" id="edit" data-prop="1"></button>
  <input type="button" name="anotherBtn" value="Another Button" id="edit2" data-prop="24"></button>
</form>
</body>
</html>

DELPHI

Classe du gestionnaire d'extension [uExtensionHandler.pas].

    unit uExtensionHandler;

{$I cef.inc}

interface

uses
{$IFDEF DELPHI16_UP}
  Winapi.Windows,
{$ELSE}
  Windows,
{$ENDIF}
  uCEFRenderProcessHandler, uCEFBrowserProcessHandler, uCEFInterfaces,
  uCEFProcessMessage,
  uCEFv8Context, uCEFTypes, uCEFv8Handler;

const
  MOUSECLICK_MESSAGE_NAME = 'mouseclick';

type
  TExtensionHelper = class(TCefv8HandlerOwn)
  protected
    function Execute(const name: ustring; const object_: ICefv8Value;
      const arguments: TCefv8ValueArray; var retval: ICefv8Value;
      var exception: ustring): Boolean; override;
  end;

implementation

{ TExtensionHelper }

function TExtensionHelper.Execute(const name: ustring;
  const object_: ICefv8Value; const arguments: TCefv8ValueArray;
  var retval: ICefv8Value; var exception: ustring): Boolean;
var
  TempMessage: ICefProcessMessage;
  TempFrame: ICefFrame;
begin
  Result := False;

  try
    if (name = 'mouseclick') then
    begin
      if (length(arguments) > 1) and arguments[0].IsString and arguments[1].IsString
      then
      begin
        TempMessage := TCefProcessMessageRef.New(arguments[1].GetStringValue);
        TempMessage.ArgumentList.SetString(0, arguments[0].GetStringValue);

        TempFrame := TCefv8ContextRef.Current.Browser.MainFrame;

        if (TempFrame <> nil) and TempFrame.IsValid then
          TempFrame.SendProcessMessage(PID_BROWSER, TempMessage);
      end;

      Result := True;
    end;

  finally
    TempMessage := nil;
  end;

end;

end.

Formulaire principal [uMainForm.pas]

unit uMainForm;

interface

uses
{$IFDEF DELPHI16_UP}
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ComCtrls, System.IOUtils,
{$ELSE}
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, IOUtils,
{$ENDIF}
  uCEFChromium, uCEFWindowParent, uCEFInterfaces, uCEFApplication, uCEFTypes,
  uCEFConstants,
  uCEFWinControl, uCEFSentinel, uCEFChromiumCore;

const
  MINIBROWSER_SHOWSECONDFORM = WM_APP + $100;

type
  TForm1 = class(TForm)
    CEFWindowParent1: TCEFWindowParent;
    Chromium1: TChromium;
    Timer1: TTimer;
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Timer1Timer(Sender: TObject);
    procedure Chromium1ProcessMessageReceived(Sender: TObject;
      const browser: ICefBrowser; const frame: ICefFrame;
      sourceProcess: TCefProcessId; const message: ICefProcessMessage;
      out Result: Boolean);
    procedure Chromium1AfterCreated(Sender: TObject;
      const browser: ICefBrowser);
    procedure Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser;
      const frame: ICefFrame; const targetUrl, targetFrameName: ustring;
      targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean;
      const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo;
      var client: ICefClient; var settings: TCefBrowserSettings;
      var extra_info: ICefDictionaryValue;
      var noJavascriptAccess, Result: Boolean);
    procedure Chromium1Close(Sender: TObject; const browser: ICefBrowser;
      var aAction: TCefCloseBrowserAction);
    procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
    procedure Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
      const frame: ICefFrame; httpStatusCode: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  protected
    Fid: string;
    // Variables to control when can we destroy the form safely
    FCanClose: Boolean; // Set to True in TChromium.OnBeforeClose
    FClosing: Boolean; // Set to True in the CloseQuery event.

    procedure BrowserCreatedMsg(var aMessage: TMessage);
      message CEF_AFTERCREATED;
    procedure BrowserDestroyMsg(var aMessage: TMessage); message CEF_DESTROY;
    procedure ShowSecondForm(var aMessage: TMessage);
      message MINIBROWSER_SHOWSECONDFORM;
    procedure WMMove(var aMessage: TWMMove); message WM_MOVE;
    procedure WMMoving(var aMessage: TMessage); message WM_MOVING;
    procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP;
    procedure WMExitMenuLoop(var aMessage: TMessage); message WM_EXITMENULOOP;
  end;

var
  Form1: TForm1;

procedure CreateGlobalCEFApp;

implementation

uses
  uSecondForm, uCEFMiscFunctions, uCEFDictionaryValue, uExtensionHandler;

procedure GlobalCEFApp_OnWebKitInitialized;
var
  TempExtensionCode: string;
  TempHandler: ICefv8Handler;
begin
  try
    TempExtensionCode := 'var myextension;' + 'if (!myextension)' +
      '  myextension = {};' + '(function() {' +
      '  myextension.mouseclick = function(b,c) {' +
      '    native function mouseclick();' + '    mouseclick(b,c);' + '  };'
      + '})();';

    TempHandler := TExtensionHelper.Create;

    if CefRegisterExtension('myextension', TempExtensionCode, TempHandler) then
{$IFDEF DEBUG}CefDebugLog('JavaScript extension registered successfully!'){$ENDIF}
    else
{$IFDEF DEBUG}CefDebugLog('There was an error registering the JavaScript extension!'){$ENDIF};
  finally
    TempHandler := nil;
  end;
end;

procedure CreateGlobalCEFApp;
begin
  GlobalCEFApp := TCefApplication.Create;
  GlobalCEFApp.OnWebKitInitialized := GlobalCEFApp_OnWebKitInitialized;
{$IFDEF DEBUG}
  GlobalCEFApp.LogFile := 'debug.log';
  GlobalCEFApp.LogSeverity := LOGSEVERITY_INFO;
{$ENDIF}
end;

{$R *.dfm}
{ TForm1 }

procedure TForm1.BrowserCreatedMsg(var aMessage: TMessage);
begin
  CEFWindowParent1.UpdateSize;
end;

procedure TForm1.BrowserDestroyMsg(var aMessage: TMessage);
begin
  CEFWindowParent1.Free;
end;

procedure TForm1.Chromium1AfterCreated(Sender: TObject;
  const browser: ICefBrowser);
begin
  PostMessage(Handle, CEF_AFTERCREATED, 0, 0);
end;

procedure TForm1.Chromium1BeforeClose(Sender: TObject;
  const browser: ICefBrowser);
begin
  FCanClose := True;
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;

procedure TForm1.Chromium1BeforePopup(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const targetUrl, targetFrameName: ustring;
  targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean;
  const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo;
  var client: ICefClient; var settings: TCefBrowserSettings;
  var extra_info: ICefDictionaryValue; var noJavascriptAccess, Result: Boolean);
begin
  Result := (targetDisposition in [WOD_NEW_FOREGROUND_TAB,
    WOD_NEW_BACKGROUND_TAB, WOD_NEW_POPUP, WOD_NEW_WINDOW]);
end;

procedure TForm1.Chromium1Close(Sender: TObject; const browser: ICefBrowser;
  var aAction: TCefCloseBrowserAction);
begin
  PostMessage(Handle, CEF_DESTROY, 0, 0);
  aAction := cbaDelay;
end;

procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; httpStatusCode: Integer);
var
  TempJSCode: string;
begin
  Chromium1.LoadURL('file:///jsExtensionClickEvent.html');
  TempJSCode := 'document.body.addEventListener("click", function (evt) { ' +
    ' function getpath(n) {' +
    ' var result = document.getElementById(n.id).getAttribute("data-prop"); ' +
    ' return result; ' + ' } '
  +' myextension.mouseclick(getpath(evt.target), ' +
    quotedstr(MOUSECLICK_MESSAGE_NAME) + ');});';
  frame.ExecuteJavaScript(TempJSCode, 'about:blank', 0);
end;

procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  sourceProcess: TCefProcessId; const message: ICefProcessMessage;
  out Result: Boolean);
begin
  Result := False;

  if (message = nil) or (message.ArgumentList = nil) then
    exit;

  // This function receives the messages with the JavaScript results

  if (message.Name = MOUSECLICK_MESSAGE_NAME) then
  begin
    Fid := message.ArgumentList.GetString(0);
    PostMessage(Handle, MINIBROWSER_SHOWSECONDFORM, 0, 0);
    // this doesn't create/destroy components
    Result := True;
  end;
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := FCanClose;

  if not(FClosing) then
  begin
    FClosing := True;
    Visible := False;
    Chromium1.CloseBrowser(True);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FCanClose := False;
  FClosing := False;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  // GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
  // If it's not initialized yet, we use a simple timer to create the browser later.
  if not(Chromium1.CreateBrowser(CEFWindowParent1, '')) then
    Timer1.Enabled := True;
end;

procedure TForm1.ShowSecondForm(var aMessage: TMessage);
begin
  Form2.Label1.Caption := Fid;
  Form2.ShowModal;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  if not(Chromium1.CreateBrowser(CEFWindowParent1, '')) and
    not(Chromium1.Initialized) then
    Timer1.Enabled := True;
end;

procedure TForm1.WMEnterMenuLoop(var aMessage: TMessage);
begin
  inherited;

  if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
    GlobalCEFApp.OsmodalLoop := True;

end;

procedure TForm1.WMExitMenuLoop(var aMessage: TMessage);
begin
  inherited;

  if (aMessage.wParam = 0) and (GlobalCEFApp <> nil) then
    GlobalCEFApp.OsmodalLoop := False;

end;

procedure TForm1.WMMove(var aMessage: TWMMove);
begin
  inherited;

  if (Chromium1 <> nil) then
    Chromium1.NotifyMoveOrResizeStarted;
end;

procedure TForm1.WMMoving(var aMessage: TMessage);
begin
  inherited;

  if (Chromium1 <> nil) then
    Chromium1.NotifyMoveOrResizeStarted;
end;

end.

Deuxième formulaire [uSecondForm.pas]

unit uSecondForm;

interface

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

type
  TForm2 = class(TForm)
    Label1: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

end.

Contenu du fichier journal

[1012/202103.335:ERROR:CEF4Delphi(1)] PID: 6708, TID: 2872, PT: Renderer - JavaScript extension registered successfully!
[1012/203832.621:ERROR:CEF4Delphi(1)] PID: 6660, TID: 6748, PT: Renderer - JavaScript extension registered successfully!
[1012/203832.688:ERROR:CEF4Delphi(1)] PID: 5436, TID: 6016, PT: Renderer - JavaScript extension registered successfully!

En cliquant sur le bouton, vous obtenez le journal des événements de débogage ci-dessous. Cef4DelphiJsExtension.exe est le nom de l'application.

Thread Start: Thread ID: 1732. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 1732. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 1180. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 2076. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 6592. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7200. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7220. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7276. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7276. Process Cef4DelphiJsExtension.exe (7156)
Thread Start: Thread ID: 7376. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7376. Process Cef4DelphiJsExtension.exe (7156)
Thread Exit: Thread ID: 7220. Process Cef4DelphiJsExtension.exe (7156)

Merci.

8voto

Salvador Díaz Fau Points 427

Vous pouvez le faire de deux manières :

  1. Ajoutez une fonction JavaScript dans l'événement "onclick" de ce bouton. Cette fonction devra seulement appeler "console.log()" avec "OpenMyNewFormInDelphi" ou ce que vous voulez comme paramètre texte. Utilisez ensuite l'événement TChromium.OnConsoleMessage et vérifiez le paramètre "aMessage". Si le paramètre aMessage contient "OpenMyNewFormInDelphi", envoyez un message Windows au formulaire principal pour afficher votre nouveau formulaire dans le fil d'application principal. Cette solution est la plus simple, elle n'est pas très élégante mais elle permet de faire le travail. Voir le DOMVisiteur démo pour plus de détails.
  2. Vous pouvez également enregistrer un " Extension JavaScript " avec CEF4Delphi pour exécuter du code Delphi à partir de JavaScript. Cette solution est de loin la plus compliquée car elle implique la création et l'enregistrement d'une classe personnalisée héritée de TCefv8HandlerOwn. Cette classe recevra les appels de votre code JS et vous pourrez envoyer un message IPC au processus principal du navigateur si votre application doit faire quelque chose en réponse à cet appel JS. Voir le JSExtension y JSRTTIExtension démos pour plus d'informations.

L'explication complète de l'extension JavaScript est un peu longue mais vous pouvez la lire ici : https://github.com/salvadordf/CEF4Delphi/blob/d44db3bf2a3ead0654ca90178161b09bfbe33602/demos/Delphi_VCL/JavaScript/JSExtension/uJSExtension.pas#L122

N'oubliez pas que tous les événements TChromium et GlobalCEFApp sont exécutés dans un thread CEF qui est différent du thread principal de l'application. La VCL n'est pas thread safe et vous pouvez avoir des problèmes si vous créez, détruisez ou modifiez des contrôles Windows dans ces événements. Les démos CEF4Delphi sont trop simplifiées et vous devez toujours déplacer le code VCL en dehors de ces événements. La première solution envoie un message Windows au formulaire principal pour cette raison.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X