11

Am I understanding correctly that EdgeHTML is now available to desktop (Win32/Win64 applications) now in Windows 10? According to these blog posts:

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

It appears that Microsoft has added EdgeHTML WebViewControl for Windows desktop (Win32) apps which hasn't been available for desktop apps so far (only Trident based MSHTML control was available for desktop apps).

If this is true, is there a possibility to utilize this in Delphi/C++ Builder or do we have to wait for a new TWebView control in the new update of RAD Studio? If possible - are there any code samples to look at (C++ Builder or Delphi that is)? And does the requirement of .NET means it can't be utilized in regular Win32/Win64 app as produced by RAD Studio?

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
Coder12345
  • 3,431
  • 3
  • 33
  • 73

3 Answers3

16

This answer is outdated but may be interesting to learn about the technical background. RAD Studio 10.4 Sydney now supports using the Edge browser out of the box. See my other answer.


The WebView control is offered via WinRT and does not depend on .net. You can use it from normal Win32 applications.

WinRT (Windows Runtime), now in Windows 10 rebranded as UWP (Universal Windows Platform), is something like the successor of COM.

Like COM it is heavily based on interfaces and the available interfaces are defined in type libraries. For WinRT, the type libraries are stored in *.WinMD files in the Windows system directory. The type library which contains the functionality we need to embed the Edge browser is Windows.Web.winmd.

Delphi does support using WinRT components and it comes with translations of some of the type libraries and some additional helper functions and classes to work with WinRT.

However, there is currently no tool to automatically translate WinMD files or IDL files derived from WinMD files to Delphi code. If you want to use WinRT functionality that does not ship with Delphi, you have to manually translate the type definitions to Delphi code.

WinRT heavily uses generic interfaces (interfaces with type parameters) that are not compatible with the way generic interfaces work in Delphi. This requires some manual adjustments when translating the type definitions.

If you install the Windows Platform SDK, you find IDL and C++ translations of the WinRT type libraries in a directory like Drive:\Windows Kits\10\Include\10.0.17134.0\winrt.

I have used these files as templates to create a very basic Proof of Concept Delphi project (for Delphi 10.2) which uses an embedded Edge browser. You can find the code below. In order to test this, just create a new VCL project, paste the code and connect the FormCreate, FormDestroy and FormResize events with the form.

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

NineBerry
  • 26,306
  • 3
  • 62
  • 93
  • 1
    Thank you for the great answer and useful example! – Coder12345 Oct 15 '18 at 00:27
  • 1
    The `CreateWebViewControlAsync` is tight to Handle of a VCL control. This is fine if the handle never change. In Delphi, we may change the FormStyle (mdi, normal) or even the theme. This will make the VCL control's handle change. Once the handle has changed. The embedded Edge will raise error. – Chau Chee Yang May 24 '19 at 07:50
  • @ChauCheeYang That's a good point. As I wrote, the code in the answer is just a proof of concept. A real implementation of an Edge control would have to add a lot more stuff to handle all use cases. – NineBerry May 24 '19 at 08:58
  • You declared the `TWebViewControlProcess` as `class(TWinRTGenericImportI)`. According to the [official documentation](https://learn.microsoft.com/en-us/uwp/api/windows.web.ui.interop.webviewcontrolprocess.-ctor), it also has a second constructor with parameters. I would need to access this to create a process that allows to display local files. Can you elaborate on how to call this constructor in Delphi? – Günther the Beautiful Oct 22 '19 at 17:50
  • @GünthertheBeautiful create a new question, and I'll have a look. – NineBerry Oct 22 '19 at 18:58
  • @NineBerry Do you have an example for this? https://stackoverflow.com/questions/60018071/using-chromium-edge-webview2-in-delphi-or-c-builder – Coder12345 Feb 03 '20 at 13:15
  • @Coder12345 Sorry, I don't have time looking into this right now. I would think that the answer in this topic would automatically use Edge with the Chromium engine on computers where the newest Edge is installed. Is that not the case? – NineBerry Feb 03 '20 at 15:46
  • Thank you for this answer. I am hoping to one day be able to use something like your code from older versions of Delphi + Lazarus. (Preferably integrated into the same code so IE is used when Edge is not available.) Still seems some way out, but this is definitely the best information I have found so far on the topic. – Tom Jul 27 '20 at 08:53
  • I am sorry. I tried to follow your instructions and get an access violation. WebView2 runtime is installed. What I am missing. Thanks in advance. – Sevast May 22 '21 at 11:02
  • @Sevast I'd propose updating to RAD Studio 10.4 and using the component that is provided out of the box with it. If you have a problem, create a question on Stackoverflow and provide all information ([mcve]) and all error information (like full stack trace) – NineBerry May 22 '21 at 11:32
3

RAD Studio 10.4 Sydney comes with enhanced support for the new Chromium-based Edge browser by Microsoft.

There is both a new Control TEdgeBrowser that can be used to use the Edge browser engine directly as well as the possibility to allow the classic TWebBrowser control to use the new Edge rendering engine automatically when it is available via the TWebBrowser.SelectedEngine property.

Detailed explanation in this blog entry by Embarcadero:

NineBerry
  • 26,306
  • 3
  • 62
  • 93
  • 1
    Thanks for the followup answer. Yes, it works indeed, the only problem is that at the moment it requires installation of the Canary channel build of Edge browser (https://www.microsoftedgeinsider.com/en-us/download), hopefully when Chromium based Edge finally becomes 1.0+ version that won't be required any longer. Also, other wrappers are appearing as well, not just Embarcadero one. – Coder12345 Jun 22 '20 at 12:40
-1

As far as I know, we can't access the EdgeHtml from C++ now, Someone has submitted a suggestion on the uservoice site. I suggest you could vote it. Expose EdgeHTML C++ API

Zhi Lv
  • 18,845
  • 1
  • 19
  • 30