在 Delphi / C++ Builder 中使用 WebView (EdgeHTML)

Posted

技术标签:

【中文标题】在 Delphi / C++ Builder 中使用 WebView (EdgeHTML)【英文标题】:Using WebView (EdgeHTML) in Delphi / C++ Builder 【发布时间】:2019-03-16 21:37:58 【问题描述】:

我是否正确理解 Edgehtml 现在可用于 Windows 10 中的桌面(Win32/Win64 应用程序)?根据这些博客文章:

https://blogs.windows.com/msedgedev/2018/05/09/modern-webview-winforms-wpf-apps/ https://blogs.windows.com/msedgedev/2018/10/04/edgehtml-18-october-2018-update/ https://docs.microsoft.com/en-us/windows/communitytoolkit/controls/wpf-winforms/webview

微软似乎已经为 Windows 桌面 (Win32) 应用程序添加了 EdgeHTML WebViewControl,这些应用程序到目前为止还不能用于桌面应用程序(只有基于 Trident 的 MSHTML 控件可用于桌面应用程序)。

如果这是真的,是否有可能在 Delphi/C++ Builder 中使用它,还是我们必须等待 RAD Studio 新更新中的新 TWebView 控件?如果可能的话 - 是否有任何代码示例可供查看(C++ Builder 或 Delphi)? .NET 的要求是否意味着它不能在 RAD Studio 制作的常规 Win32/Win64 应用程序中使用?

【问题讨论】:

【参考方案1】:

据我所知,我们现在无法从 C++ 访问 EdgeHtml,有人在 uservoice 网站上提交了建议。我建议你可以投票。 Expose EdgeHTML C++ API

【讨论】:

【参考方案2】:

此答案已过时,但了解技术背景可能会很有趣。 RAD Studio 10.4 Sydney 现在支持使用开箱即用的 Edge 浏览器。见my other answer。


WebView 控件是通过 WinRT 提供的,不依赖于 .net。您可以在普通的 Win32 应用程序中使用它。

WinRT(Windows 运行时),现在在 Windows 10 中更名为 UWP(通用 Windows 平台),类似于 COM 的继承者。

与 COM 一样,它在很大程度上基于接口,并且可用接口在类型库中定义。对于 WinRT,类型库存储在 Windows 系统目录中的 *.WinMD 文件中。包含我们需要嵌入 Edge 浏览器的功能的类型库是Windows.Web.winmd

Delphi 确实支持使用 WinRT 组件,并且它附带了一些类型库的翻译以及一些额外的帮助函数和类以与 WinRT 一起使用。

但是,目前没有工具可以自动将 WinMD 文件或从 WinMD 文件派生的 IDL 文件转换为 Delphi 代码。如果您想使用 Delphi 未附带的 WinRT 功能,您必须手动将类型定义转换为 Delphi 代码。

WinRT 大量使用与 Delphi 中泛型接口的工作方式不兼容的泛型接口(带有类型参数的接口)。这需要在翻译类型定义时进行一些手动调整。

如果您安装 Windows 平台 SDK,您会在 Drive:\Windows Kits\10\Include\10.0.17134.0\winrt 之类的目录中找到 WinRT 类型库的 IDL 和 C++ 翻译。

我使用这些文件作为模板来创建一个非常基本的概念证明 Delphi 项目(适用于 Delphi 10.2),它使用嵌入式 Edge 浏览器。您可以在下面找到代码。为了测试这一点,只需创建一个新的 VCL 项目,粘贴代码并将FormCreateFormDestroyFormResize 事件与表单连接。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  System.Types,
  Winapi.Winrt,
  System.Win.WinRT,
  WinAPI.Foundation,
  WinAPI.Foundation.Types;

const
  SWebViewControlProcess = 'Windows.Web.UI.Interop.WebViewControlProcess';

type
  // Interface with functionality to interact with WebBrowser Control
  // https://docs.microsoft.com/en-us/uwp/api/windows.web.ui.iwebviewcontrol
  IWebViewControl = interface(IInspectable)
  ['3F921316-BC70-4BDA-9136-C94370899FAB']
    procedure Placeholder_SourceGet; safecall;
    procedure Placeholder_SourcePut; safecall;
    procedure Placeholder_DocumentTitle; safecall;
    procedure Placeholder_CanGoBack; safecall;
    procedure Placeholder_CanGoForward; safecall;
    procedure Placeholder_DefaultBackgroundColorPut; safecall;
    procedure Placeholder_DefaultBackgroundColorGet; safecall;
    procedure Placeholder_ContainsFullScreenElement; safecall;
    procedure Placeholder_Settings; safecall;
    procedure Placeholder_DeferredPermissionRequests; safecall;
    procedure Placeholder_GoForward; safecall;
    procedure Placeholder_GoBack; safecall;
    procedure Placeholder_Refresh; safecall;
    procedure Placeholder_Stop; safecall;
    procedure Navigate(source: IUriRuntimeClass); stdcall;
    procedure NavigateToString(text: HString); stdcall;
    // TODO: Declare further properties and functions of IWebViewControl
  end;

  IWebViewControlProcess = interface;

  // Declare  IWebViewControlSite
  IWebViewControlSite = interface(IInspectable)
  ['133F47C6-12DC-4898-BD47-04967DE648BA']
    function get_Process: IWebViewControlProcess; safecall;
    procedure put_Scale(value: Double); safecall;
    function get_Scale: Double; safecall;
    procedure put_Bounds(value: TRectF); safecall;
    function get_Bounds: TRectF; safecall;
    procedure put_IsVisible(value: Boolean); safecall;
    function get_IsVisible: Boolean; safecall;

    // TODO: Declare further properties and functions of IWebViewControlSite

    property Process: IWebViewControlProcess read get_Process;
    property Scale: Double read get_Scale write put_Scale;
    property Bounds: TRectF read get_Bounds write put_Bounds;
    property IsVisible: Boolean read get_IsVisible write put_IsVisible;
  end;

  // types for reacting to when the WebView has finished initialization
  IAsyncOperation_1__IWebViewControl = interface;

  IAsyncOperationCompletedHandler_1__IWebViewControl = interface(IUnknown)
  ['d61963d6-806d-50a8-a81c-75d9356ad5d7']
    procedure Invoke(asyncInfo: IAsyncOperation_1__IWebViewControl; asyncStatus: AsyncStatus); safecall;
  end;

  IAsyncOperation_1__IWebViewControl = interface(IInspectable)
  ['ac3d28ac-8362-51c6-b2cc-16f3672758f1']
    procedure put_Completed(handler: IAsyncOperationCompletedHandler_1__IWebViewControl); safecall;
    function get_Completed: IAsyncOperationCompletedHandler_1__IWebViewControl; safecall;
    function GetResults: IWebViewControl; safecall;
    property Completed: IAsyncOperationCompletedHandler_1__IWebViewControl read get_Completed write put_Completed;
  end;

  TWebViewControlCompleted = procedure(asyncInfo: IAsyncOperation_1__IWebViewControl; aasyncStatus: AsyncStatus) of object;

  TWebViewControlCompletedHandler = class(TInspectableObject,
      IAsyncOperationCompletedHandler_1__IWebViewControl
      )
  private
    FEvent: TWebViewControlCompleted;
  public
    procedure Invoke(asyncInfo: IAsyncOperation_1__IWebViewControl; aasyncStatus: AsyncStatus); safecall;
    constructor Create(AEvent: TWebViewControlCompleted);
  end;

   // The interface for interacting with the process hosting the web view control
   // https://docs.microsoft.com/en-us/uwp/api/windows.web.ui.interop.webviewcontrolprocess
  [WinRTClassNameAttribute(SWebViewControlProcess)]
  IWebViewControlProcess = interface(IInspectable)
  ['02C723EC-98D6-424A-B63E-C6136C36A0F2']
    function get_ProcessId: Cardinal; safecall;
    function get_EnterpriseId: HSTRING; safecall;
    function get_IsPrivateNetworkClientServerCapabilityEnabled: Boolean; safecall;
    function CreateWebViewControlAsync(hostWindowHandle: Int64; bounds: TRectF): IAsyncOperation_1__IWebViewControl; safecall;
    procedure Placeholder_GetWebViewControls; safecall;
    procedure Terminate; safecall;

    property ProcessId: Cardinal read get_ProcessId;
    property EnterpriseId: HSTRING read get_EnterpriseId;
    property IsPrivateNetworkClientServerCapabilityEnabled: Boolean read get_IsPrivateNetworkClientServerCapabilityEnabled;

    // TODO:
    //[eventadd] HRESULT ProcessExited([in] Windows.Foundation.TypedEventHandler<Windows.Web.UI.Interop.WebViewControlProcess*, IInspectable*>* handler, [out] [retval] EventRegistrationToken* token);
    //[eventremove] HRESULT ProcessExited([in] EventRegistrationToken token);
  end;

  // The CoClass to create an IWebViewControlProcess instance
  TWebViewControlProcess = class(TWinRTGenericImportI<IWebViewControlProcess>)
  end;

type

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
     Private declarations 
    FProcess: IWebViewControlProcess;
    FBrowser: IWebViewControl;
    FBrowserSite: IWebViewControlSite;
    procedure WebViewCompleted(asyncInfo: IAsyncOperation_1__IWebViewControl; aasyncStatus: AsyncStatus);
  public
     Public declarations 
  end;

var
  Form1: TForm1;

implementation

$R *.dfm

procedure TForm1.FormCreate(Sender: TObject);
var
  Rect: TRectF;
  AsyncOperation: IAsyncOperation_1__IWebViewControl;
    CompletedHandler: IAsyncOperationCompletedHandler_1__IWebViewControl;
begin
  CompletedHandler:=TWebViewControlCompletedHandler.Create(WebViewCompleted);

  // Size for browser
  Rect:= TRectF.Create(0, 0, ClientWidth, ClientHeight);

  // Create hosting process
  FProcess:= TWebViewControlProcess.Create();

  // Create WebView Control
  AsyncOperation:= FProcess.CreateWebViewControlAsync(self.Handle, Rect);

  // We will get notified when the control creation is finished
  AsyncOperation.Completed:= CompletedHandler;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // If there is a hosting process, then terminate it
  if Assigned(FProcess) then
  begin
    FProcess.Terminate;
  end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  if Assigned(FBrowserSite) then
  begin
    FBrowserSite.Bounds := TRectF.Create(0,0,ClientWidth, ClientHeight);
  end;
end;

procedure TForm1.WebViewCompleted(
  asyncInfo: IAsyncOperation_1__IWebViewControl;
  aasyncStatus: AsyncStatus);
var
  WinS: TWindowsString;
  Uri: IUriRuntimeClass;
begin
  // Initializing the WebView control was successful

  // Remember reference to control
  FBrowser:= asyncInfo.GetResults();
  FBrowserSite := FBrowser as IWebViewControlSite;

  // Load web page into control
  WinS:= TWindowsString.Create('http://www.whatismybrowser.com');
  Uri:= TUri.CreateUri(WinS);
  FBrowser.Navigate(Uri);
end;

 TWebViewControlCompletedHandler 

constructor TWebViewControlCompletedHandler.Create(
  AEvent: TWebViewControlCompleted);
begin
  FEvent := AEvent;
end;

procedure TWebViewControlCompletedHandler.Invoke(
  asyncInfo: IAsyncOperation_1__IWebViewControl;
  aasyncStatus: AsyncStatus);
begin
  FEvent(asyncInfo, aasyncStatus);
end;

end.

【讨论】:

感谢您的精彩回答和有用的示例! CreateWebViewControlAsync 对 VCL 控件的句柄很紧。如果手柄从不改变,这很好。在 Delphi 中,我们可以更改 FormStyle(mdi,normal)甚至主题。这将使 VCL 控件的句柄发生变化。一旦把手改变了。嵌入的 Edge 会引发错误。 @ChauCheeYang 说得好。正如我所写,答案中的代码只是概念证明。 Edge 控件的真正实现必须添加更多东西来处理所有用例。 您将TWebViewControlProcess 声明为class(TWinRTGenericImportI&lt;IWebViewControlProcess&gt;)。根据official documentation,它还有第二个带参数的构造函数。我需要访问它来创建一个允许显示本地文件的进程。能否详细说明一下如何在Delphi中调用这个构造函数? @GünthertheBeautiful 创建一个新问题,我会看看。【参考方案3】:

RAD Studio 10.4 Sydney 增强了对 Microsoft 新的基于 Chromium 的 Edge 浏览器的支持。

有一个新的控件TEdgeBrowser 可用于直接使用 Edge 浏览器引擎,以及允许经典的TWebBrowser 控件在新的 Edge 渲染引擎通过TWebBrowser.SelectedEngine 财产。

Embarcadero 在这篇博文中的详细解释:

Using TEdgeBrowser Component and Changes to the TWebBrowser Component

【讨论】:

感谢您的后续回答。是的,它确实有效,唯一的问题是目前它需要安装 Edge 浏览器的 Canary 通道版本 (microsoftedgeinsider.com/en-us/download),希望当基于 Chromium 的 Edge 最终成为不再需要的 1.0+ 版本时。此外,其他包装器也出现了,不仅仅是 Embarcadero 一个。

以上是关于在 Delphi / C++ Builder 中使用 WebView (EdgeHTML)的主要内容,如果未能解决你的问题,请参考以下文章

我应该在 Delphi 而不是 C++ Builder 中编写组件吗?如何向组件添加事件?

在 Delphi / C++ Builder 中使用 WebView (EdgeHTML)

C++ Builder / Delphi 2010 应用程序清单模板

如何在颤动中使我的文本与 listview.builder 一起滚动

使用 c++ builder 2009 reinit.pas 进行本地化

在 Rad Studio XE 6 中找不到 C++ Builder 项目