CEF4Delphi 是由 Salvador Díaz Fau 创建的一个开源项目,用于在使用 Delph 或 Lazarus/FPC为 Windows、Linux 和 MacOS 制作的应用程序中嵌入基于 Chromium 的浏览器。
CEF4Delphi 基于 DCEF3 和 fpCEF3。这些项目的原始许可证仍然适用于 CEF4Delphi。
CEF4Delphi 使用 CEF 103.0.9,其中包括 Chromium 103.0.5060.114。
CEF4Delphi 是在 Delphi 11.1 上开发和测试的,它已经在 Delphi 7、Delphi XE、Delphi 10、Delphi 10.2、Delphi 10.3、Delphi 10.4 和 Lazarus 2.2.2/FPC 3.2.2 中进行了测试。CEF4Delphi 包括 VCL、FireMonkey (FMX) 和 Lazarus 组件。
在 Delphi 中构建和安装 CEF4delphi
1.下载 CEF4Delphi 的最新版本。
2.将 CEF4Delphi 解压到一个目录中,并确保您的用户在该目录中拥有写入权限。
3.运行 Delphi。
4.把 CEF4Delphi 的 source 目录添加到 Delphi 的开发环境中去,在开发环境导航栏中点击 Tools->Options-> Environment Options->Delphi Options->Library,选择一个正确的平台(32或64),把目录添加到 Library path中。
5.在 Delphi 中打开 CEF4Delphi_D7.dpk 文件。
6.点击 Compile 编译按钮。
7.点击 Install 安装按钮。
在 Lazarus 中构建和安装 CEF4delphi
1.下载 CEF4Delphi 的最新版本。
2.将CEF4Delphi解压到一个目录中,并确保您的用户在该目录中拥有写入权限。
3.运行Lazarus。
4.在 Package->Open Package File (*.lpk) 菜单中打开 cef4delphi_lazarus.lpk 文件
5.在 package 窗口点击 Compile 编译按钮
6.点击 Use 按钮,选择 Install 选项。
安装完成后,如下图所示:
“Chromium” 选项卡:
“Chromium Views Framework”选项卡:
“Chromium” 选项卡:
Chromium嵌入式框架的一般接口,它基本上允许您访问 CEF API,并且可以看作是浏览器的 API。
CEF 浏览器可以使用的面板,它提供双重缓冲。
实现小型webserverand websocket服务器的小型非视觉组件。
一个 TWinControl 子类,可由 CEF 浏览器托管其窗口使用。通常不会使用这个,应该使用 TBrowserWindow。
一个 TWinControl 子类,可由 CEF 浏览器托管其窗口使用。通常不会使用这个,应该使用TBrowserWindow
一个 TWinControl 子类,可由 CEF 浏览器托管其窗口使用。通常不会使用这个,应该使用TBrowserWindow
封装 Tcustomcfurlrequestclient 的组件,用于处理请求进度。
可用于控制浏览器何时工作以及何时空闲的一个组件。
本质上是 TCEFLinkedWindowParentcontrol 和 TChromium 组件的组合的一个组件。通常这对于大多数应用来说是已经足够使用。
类似于 TBrowserWindow 的组件,使用OSR (屏幕外渲染) 显示浏览器窗口。
可用于检查 CEF 浏览器状态的组件,关闭窗口时,窗口准备好发送命令时,必须小心。
嵌入浏览器通常不需要 “Chromium Views Framework” 选项卡上的组件。它们是 CEF 类的包装器,在正常情况下,我们不需要,但它们是为了完整性而提供的。
这可能看起来像很多组件只是为了实现浏览器,但对于简单的应用程序,只需要一个组件: TBrowserWindow。此可视化控件是内置的 TChromium 组件,它使用此组件来控制浏览器。
要创建一个简单的浏览器,不需要太多的代码。我们创建了一个应用程序程序。在窗口的顶部放置一个 Panel 组件,设置其 Align 属性为 alTop,在 Panel 上放置一个 Edit 组件(命名为 edtAddress)和一个 Button 组件(命名为 btnGo),设置 Button 组件的 Align 属性为 alRight,Edit 组件的 Align 属性为 alClient,并放置占据窗口其余部分的 TBrowserWindow 组件,命名为bwBrowser。在按钮的 OnClick 处理程序中,我们放置以下代码:
bwBrowser.LoadURL(UTF8Decode(edtAddress.Text));UTF8Decode 是必需的,因为 LCL 对控件中的所有文本都使用 UTF8,但是CEF API期望它的 API 中有一个宽字符串 (或 Unicode 字符串)。
上面的代码指示 TBrowserWindow 组件加载一个 URL 地址。一切足够简单,但也不简单。因为:Chromium 浏览器是一个复杂的软件。为了使其正常工作,需要进行一些初始化,并且在关闭时,还需要一些额外的代码来确保CEF浏览器正确关闭。也就是说,我们不能只关闭浏览器窗口所在的表单。在表单的 OnCloseQuery 中,在允许关闭表单之前,必须检查浏览器窗口是否实际上已关闭。
procedure TForm1.FormCloseQuery(Sender: TObject; varCCanClose: Boolean);
begin
With bwBrowser do
begin
CloseBrowser(True);
CanClose:=IsClosed;
end;
end;第一行告诉浏览器关闭,参数 True 表示强制关闭。第二行检查浏览器窗口是否实际上已关闭,如果是,则允许关闭窗体。
但是如果浏览器还没有关闭呢?如何关闭窗体呢?对于 TBrowserWindow 组件的 OnBrowserClosed 事件,当浏览器实际关闭时触发。我们可以使用此事件再次关闭主窗体:
procedure TForm1.bwBrowserBrowserClosed(Sender: TObject);
begin
Close;
end;这 2 个事件处理程序的效果是以下事件序列:
1.用户关闭主窗体
2.OnCloseQuery 事件被触发时,告诉浏览器窗口关闭。如果窗口尚未关闭,则禁止主窗体关闭。
3.当浏览器窗口关闭时,触发 OnBrowserClosed 事件关闭其自身。
4.再次触发 OnCloseQuery 事件但是这一次,IsClosed 为True,并且允许主窗体关闭。
此外,在 Windows 上,需要一些额外的代码来处理菜单的使用。菜单使用单独的模态循环,并且必须将其传达给CEF 浏览器。可以在窗体声明中插入以下代码:
{$IFDEF WINDOWS}
procedure WMEnterMenuLoop(var aMessage: TMessage); message WM_ENTERMENULOOP;
procedure WMExitMenuLoop(varaaMessage: TMessage); message WM_EXITMENULOOP;
{$ENDIF}方法必须实现如下:
uses
uCEFApplication, Messages;
{$IFDEF WINDOWS}
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;
{$ENDIF} uCEFApplication 单元包含 GlobalCEFApp 对象实例,该实例需要与 CEF 库进行通信。设置其 osmodaloop 属性通知 CEF 库菜单全局事件循环处于活动状态 (或不处于活动状态)。
上面的代码并不是那么困难的,并且对于使用 CEF 的每个应用程序都是相同的。仅仅这些不够,这还不是必须做的一切。
CEF 库本身也必须初始化和停止。因为 CEF browser 在一个单独的进程中运行 (这是一个安全措施),所以这需要花费一定的时间来设置,并且需要进行大量的初始化。幸运的是,CEF 组件处理了设置。
不幸的是,这必须在不同的时间点完成,这取决于您正在使用的操作系统: 在 Linux 上,初始化必须在 LCL 本身初始化之前进行。在 Windows 和 Mac 上,LCL 必须在启动 CEF 之前进行初始化。
做到这一点的最好方法是将所有代码放在一个单独的单元中。我们将其命名为 CEFControl:
unit CEFControl;
{$mode objfpc}
{$h+}
interface
uses
uCEFApplication, uCEFWorkScheduler;
Procedure InitCEF;
Procedure StopCEF;
implementation
Procedure InitCEF;
var
Dir: String;
begin
if Assigned(GlobalCEFApp) then exit;
{$IFDEF DARWIN}
AddCrDelegate;
{$ENDIF}
CreateGlobalCEFApp;
// Set the location of the CEF libs.
{$IFDEF WINDOWS}
Dir:=’c:\CEF\Current\Dist’;
{$ELSE}
Dir:=’/opt/CEF/current/dist’;
{$ENDIF}
GlobalCEFApp.FrameworkDirPath:=Dir;
GlobalCEFApp.ResourcesDirPath:=Dir;
GlobalCEFApp.LocalesDirPath:=Dir+PathDelim+’locales’;
if not GlobalCEFApp.StartMainProcess then
begin
StopCEF;
halt(0); // exit the subprocess
end;
end;
Procedure StopCEF;
begin
if GlobalCEFWorkScheduler <> nil then
GlobalCEFWorkScheduler.StopScheduler;
DestroyGlobalCEFApp;
DestroyGlobalCEFWorkScheduler;
end;
{$IFDEF LINUX}
initialization
InitCEF;
{$ENDIF}
end.在 Linux 上,InitCEF 过程在单元的初始化部分调用。如果将 CEFControl 单元插入程序的 uses 子句中的Interfaces 单元之前,则结果是 CEF 将在 LCL 初始化之前初始化。
要在 MacOS 和 Windows 上初始化 CEF,在主窗体单元的初始化中调用 InitCEF:
initialization
InitCEF;
finalization
StopCEF;
end.由于主窗体的初始化部分仅在 LCL 初始化后执行,因此只有在 LCL 初始化后才会初始化 CEF 库,并且 CEF 会在LCL 最终确定之前停止。
让我们更详细地看看初始化期间会发生了什么:
InitCEF 过程的第一行检查全局 CEF 应用程序是否存在。如果存在,它将退出。这确保即使在 Linux 上,CEF 库也只能初始化一次。
在 MacOS 上,在此之后,将创建一个应用程序委托,这是一个特定于 MacOS 的例程,需要处理与 CEF 的通信。
对 CreateGlobalCEFApp 的调用将创建实际的 GlobalCEFApp 实例,并进行一些配置。
procedure PumpWork(const aDelayMS: int64);
begin
if (GlobalCEFWorkScheduler <> nil) then
GlobalCEFWorkScheduler.ScheduleMessagePumpWork(aDelayMS);
end;
procedure CreateGlobalCEFApp;
begin
if GlobalCEFApp <> nil then
exit;
GlobalCEFWorkScheduler:=TCEFWorkScheduler.Create(nil);
GlobalCEFApp:=TCefApplication.Create;
GlobalCEFApp.ExternalMessagePump:=True;
GlobalCEFApp.MultiThreadedMessageLoop:=False;
GlobalCEFApp.OnScheduleMessagePumpWork:=@PumpWork;
{$IFDEF LINUX}
GlobalCEFApp.DisableZygote::=True;
{$ENDIF}
end;配置与消息的处理方式有关; 在上述情况下,消息泵在泵中处理。还有其他方法可以处理此问题,但它不在本文的范围内,无法处理所有可能性: 在大多数情况下,可以简单地使用上述代码。
调用 CreateGlobalCEFApp 后,几行设置了全局 GlobalCEFApp 实例的一些路径属性。这需要解释一下。
默认情况下,CEF4Delphi 在与应用程序相同的目录中查找 CEF 库和辅助文件。这意味着必须将 resource 和Release 的完整内容 (约1.2 Gb)复制到应用程序目录中。
当然,在我们制作各种应用程序时,这不是很方便。更好的方法可能是将这两个目录的内容复制到单个目录 (在上面的示例代码中,这是 CEF/current/dist),并告诉 CEF4Delphi 使用该目录中的库和支持文件。
{$IFDEF WINDOWS}
Dir:=’c:\CEF\Current\Dist’;
{$ELSE}
Dir:=’/opt/CEF/current/dist’;
{$ENDIF}
GlobalCEFApp.FrameworkDirPath:=Dir;
GlobalCEFApp.ResourcesDirPath:=Dir;
GlobalCEFApp.LocalesDirPath:=Dir+PathDelim+’locales’;必须设置 3 条路径。请注意,如果计划分发应用程序,也必须分发该目录的内容,最终用户系统上的目录可能是其他内容。
完成此配置后,是时候实际加载并初始化CEF库了。这是用如下代码实现的:
if not GlobalCEFApp.StartMainProcess then
begin
StopCEF;
halt(0); // exit the subprocess
end;就这样。现在程序准备好了。运行该程序的结果如下图所示:
| 留言与评论(共有 0 条评论) “” |