javascript - 如何使用 Cef4Delphi 从 JavaScript 调用 Delphi 函数

标签 javascript delphi cef4delphi

我是德尔福的初学者。目前使用 Delphi Berlin 版本。
我正在尝试从 JavaScript 调用 Delphi 函数/方法。例如,我想在单击带有附加数据属性的 html 按钮时打开一个新的 Delphi 表单。
代码


<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>

单击按钮时,将打开一个新的 Delphi [第二个窗体],它将显示 数据 Prop TLabel 上的按钮。
[更新 - 2020 年 12 月 10 日]
我尝试在 JSExtension 演示的帮助下创建应用程序。我尝试添加 javascript 点击事件,但 html 上的点击事件未触发且第二个表单未加载。
这是一些代码
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>
德尔福
扩展处理程序类 [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.

主窗体 [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.
第二种形式 [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.
日志文件的内容
[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!
单击“调试事件日志”下方的按钮时。 Cef4DelphiJsExtension.exe 是应用程序名称。
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)
谢谢你。

最佳答案

您可以通过两种方式做到这一点:

  • 在该按钮的“onclick”事件中添加一个 JavaScript 函数。该函数只需要使用“OpenMyNewFormInDelphi”或您想要的任何文本参数调用“console.log()”。然后使用 TChromium.OnConsoleMessage 事件并检查“aMessage”参数。如果 aMessage 具有“OpenMyNewFormInDelphi”,则向主窗体发送一条 Windows 消息,以在主应用程序线程中显示您的新窗体。这个解决方案是最简单的,它不是很优雅,但它可以完成工作。见 DOM访客演示了解更多详情。
  • 您还可以使用 CEF4Delphi 注册“JavaScript 扩展”以从 JavaScript 执行 Delphi 代码。这是迄今为止最复杂的解决方案,因为它涉及从 TCefv8HandlerOwn 继承的自定义类的创建和注册。该类将接收来自您的 JS 代码的调用,如果您的应用程序需要响应该 JS 调用,您可以向主浏览器进程发送 IPC 消息。见 JS扩展 JSRTTIExtension 演示以获取更多信息。

  • JavaScript 扩展的完整解释有点长,但你可以在这里阅读:
    https://github.com/salvadordf/CEF4Delphi/blob/d44db3bf2a3ead0654ca90178161b09bfbe33602/demos/Delphi_VCL/JavaScript/JSExtension/uJSExtension.pas#L122
    请记住,所有 TChromium 和 GlobalCEFApp 事件都在与主应用程序线程不同的 CEF 线程中执行。 VCL 不是线程安全的,如果您在这些事件中创建、销毁或修改 Windows 控件,您可能会遇到问题。 CEF4Delphi 演示过于简单,您应该始终将 VCL 代码移到这些事件之外。出于这个原因,第一个解决方案将 Windows 消息发送到主窗体。

    关于javascript - 如何使用 Cef4Delphi 从 JavaScript 调用 Delphi 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64303829/

    相关文章:

    javascript - 通过 max 属性从数组中获取对象

    javascript - 如何禁用浏览器的加载圈(靠近图标)

    delphi - 通过新 RTTI 处理索引属性时遇到问题 [D2010]

    delphi - 加载 midas.dll 时发生 Midas.dll 错误

    delphi - 如何使用 Chromium Embedded (CEF) 加载本地 html 文件?

    delphi - CEF4Delphi 和 DUnit

    javascript - 复选框上的事件未发生

    javascript - Facebook APP : Empty invitable friends list

    delphi - Delphi 7 的 TImageList 组件支持 PNG 图像