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

12

我理解得对吗,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://learn.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应用程序中使用?
3个回答

17

此答案已过时,但了解技术背景可能会很有趣。RAD Studio 10.4 Sydney现在支持直接使用Edge浏览器。请参见我的其他回答


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

WinRT(Windows Runtime)现在在Windows 10中重新命名为UWP(Universal 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://learn.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://learn.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.

enter image description here


1
感谢您提供出色的答案和有用的示例! - Coder12345
1
CreateWebViewControlAsync 方法与 VCL 控件的句柄相关联。如果该句柄永远不会更改,那么这很好。在 Delphi 中,我们可能会更改 FormStyle(mdi、normal)甚至主题。这将导致 VCL 控件的句柄更改。一旦句柄更改,嵌入式 Edge 就会引发错误。 - Chau Chee Yang
你将TWebViewControlProcess声明为class(TWinRTGenericImportI<IWebViewControlProcess>)。根据官方文档,它还有一个带参数的第二个构造函数。我需要访问这个构造函数来创建一个允许显示本地文件的进程。你能详细说明如何在Delphi中调用这个构造函数吗? - Günther the Beautiful
非常抱歉。我按照您的指示尝试进行操作,但出现了访问冲突问题。已经安装了WebView2运行时,但可能存在什么问题。先在此谢过了。 - Sevast
@Sevast 我建议升级到RAD Studio 10.4,并使用默认提供的组件。如果遇到问题,请在Stackoverflow上创建一个问题并提供所有信息([mcve])和所有错误信息(如完整堆栈跟踪)。 - NineBerry
显示剩余5条评论

4

1
感谢您的跟进回答。是的,它确实有效,唯一的问题是目前需要安装Edge浏览器的金丝雀频道版本(https://www.microsoftedgeinsider.com/en-us/download),希望当基于Chromium的Edge最终成为1.0+版本时,这将不再需要。此外,其他包装器也在出现,不仅仅是Embarcadero。 - Coder12345

0
据我所知,现在我们无法从C++访问EdgeHtml,有人已经在uservoice网站上提交了建议。我建议您可以投票支持它。公开EdgeHTML C++ API

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接