Jan Doggen
Jan Doggen

Reputation: 9106

Extending the TWebBrowser external object to execute Delphi code: how to access my form components?

I'm following the How to call Delphi code from scripts running in a TWebBrowser DelphiDabbler tutorial (by Peter Johnson) to allow Delphi to listen to TWebBrowser JavaScript events.

This works up to the point where I see my Delphi procedures getting called. However, from in there I need to update some form labels, and I see no way to access my form from those procedures.
The DelphiDabbler example code nicely circumvents 'direct form access' by creating THintAction.Create(nil); which will do it's thing:

This let's us decouple our external object implementation quite nicely from the program's form

But I want to access my form! Data to be passed are integers and strings.
I could use PostMessage() and WM_COPYDATA messages, but these would still need the form handle. And isn't there a 'direct' route to the form?

Relevant code:

type
   TWebBrowserExternal = class(TAutoIntfObject, IWebBrowserExternal, IDispatch)
   protected
      procedure SetVanLabel(const ACaption: WideString); safecall;  // My 3 procedures that are called...
      procedure SetNaarLabel(const AValue: WideString); safecall;   // ... declared in the type library.
      procedure SetDistanceLabel(AValue: Integer); safecall;
   public
      constructor Create;
      destructor Destroy; override;
   end;

type
   TExternalContainer = class(TNulWBContainer, IDocHostUIHandler, IOleClientSite)
   private
      fExternalObj: IDispatch;  // external object implementation
   protected
      { Re-implemented IDocHostUIHandler method }
      function GetExternal(out ppDispatch: IDispatch): HResult; stdcall;
   public
      constructor Create(const HostedBrowser: TWebBrowser);
   end;

constructor TExternalContainer.Create(const HostedBrowser: TWebBrowser);
begin
   inherited Create(HostedBrowser);
   fExternalObj := TWebBrowserExternal.Create;
end;

The form has a property FContainer: TExternalContainer;, in the FormCreate I do fContainer := TExternalContainer.Create(WebBrowser); (parameter is the design time TWebBrowser), so the TExternalContainer.fExternalObj is assigned to that.

Question:

  procedure TWebBrowserExternal.SetDistanceLabel(AValue: Integer);
  begin
     // **From here, how do I send AValue to a label caption on my form?**
  end;

I must confess that interfaces are not my forte ;-)

[Added:] Note: My forms are all created dynamically, there is no TForm instance in the current unit.

Upvotes: 2

Views: 1167

Answers (2)

Jan Doggen
Jan Doggen

Reputation: 9106

Taking the advice You say you want to access your form, but you really don't - at least not directly from Dsm in his/her answer, I have decided to use PostMessage/SendMessage (as I hinted at in my question).

First I pass the window handle in the constructors of TWebBrowserExternal and TExternalContainer and store it as a private property:

type
   TWebBrowserExternal = class(TAutoIntfObject, IWebBrowserExternal, IDispatch)
   private
      fHandle: HWND;
      procedure SendLocationUpdate(AWhere: Integer; ALocation: String);  // Helper for SetVanLabel/SetNaarLabel
   protected
      procedure SetVanLabel(const AValue: WideString); safecall;
      procedure SetNaarLabel(const AValue: WideString); safecall;
      procedure SetDistanceLabel(AValue: Integer); safecall;
   public
      constructor Create(AHandle: HWND);
      destructor Destroy; override;
   end;

type
   TExternalContainer = class(TNulWBContainer, IDocHostUIHandler, IOleClientSite)
   private
      fExternalObj: IDispatch;  // external object implementation
   protected
      { Re-implemented IDocHostUIHandler method }
      function GetExternal(out ppDispatch: IDispatch): HResult; stdcall;
   public
      constructor Create(const HostedBrowser: TWebBrowser; AHandle: HWND);
   end;

In the FormCreate the TExternalContainer is now created as

fContainer := TExternalContainer.Create(WebBrowser, Self.Handle);

The Set... methods are implemented as:

procedure TWebBrowserExternal.SetDistanceLabel(AValue: Integer);
begin
   PostMessage(fHandle,UM_UPDATEDIST,AValue,0);  //  const UM_UPDATEDIST = WM_USER + 101;
end;

procedure TWebBrowserExternal.SetNaarLabel(const AValue: WideString);
begin
   SendLocationUpdate(1,AValue);
end;

procedure TWebBrowserExternal.SetVanLabel(const AValue: WideString);
begin
   SendLocationUpdate(0,AValue);
end;

with helper function:

procedure TWebBrowserExternal.SendLocationUpdate(AWhere: Integer; ALocation: String);
var lCopyDataStruct: TCopyDataStruct;
begin
   lCopyDataStruct.dwData := AWhere;
   lCopyDataStruct.cbData := 2 * 2 * Length(ALocation);
   lCopyDataStruct.lpData := PChar(ALocation);
   SendMessage(fHandle, WM_COPYDATA, wParam(fHandle), lParam(@lCopyDataStruct));
end;

My form contains two message handlers that actually update the labels:

procedure UpdateDistMsgHandler(var Msg: TMessage); message UM_UPDATEDIST;
procedure WMCopyData(var Msg : TWMCopyData) ; message WM_COPYDATA;

procedure TFrmGoogleMapsLiveUpdate.UpdateDistMsgHandler(var Msg: TMessage);
begin
   LabelDistance.Caption := IntToStr(Msg.WParam);
end;

procedure TFrmGoogleMapsLiveUpdate.WMCopyData(var Msg: TWMCopyData);
var
   lWhere    : integer;
   lLocation : string;
begin
   lWhere := Msg.CopyDataStruct.dwData;
   lLocation := String(PChar(Msg.CopyDataStruct.lpData));
   if lWhere = 0 then
      LabelVan.Caption := lLocation
   else
      LabelNaar.Caption := lLocation;
end;

Upvotes: 1

Dsm
Dsm

Reputation: 6013

You say you want to access your form, but you really don't - at least not directly. You do want to 'decouple our external object implementation quite nicely from the program's form'. All you need to do really is write a function or procedure to do what you want inside your program, and then call that function or procedure from your web browser. This is what decoupling and interfaces are all about. You never handle data belonging to one application directly from another. Instead you use functions and procedures as your interface. Incidentally that is why interfaces only contain functions and procedure prototypes (and properties - but they are just translated internally as functions and procedures) - never data.

Now down to your specific question. Of course you can access your form - it is a global variable. Suppose your main form is of type TMainForm in a unit called Main.pas, there will be a global variable called MainForm

var
  MainForm : TMainForm;

so in your webbrowser unit, in the implementation section you would put

implementation

uses Main;

...

procedure TWebBrowserExternal.SetDistanceLabel(AValue: Integer);
begin
   // **From here, how do I send AValue to a label caption on my form?**
   FormMain.MyLabel.Caption := StrToInt( AValue );
end;

In the context of what I said, SetDistanceLabel is the interface function, and the Form is only directly accessed from within your Delphi application.

Upvotes: 1

Related Questions