2

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.

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
  • if you return the form handle in TWebBrowserExternal, like function GetformHandle(): Word ? – Passella Jun 22 '16 at 14:29
  • It is possible to use a [much simpler approach to implement external methods in Delphi](http://stackoverflow.com/a/22184159/859646), using the late-bound functionality provided by ObjComAuto.TObjectDispatch. This way you don't need to define any interfaces nor a type library. – JRL Dec 10 '16 at 10:30

2 Answers2

1

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.

Dsm
  • 5,870
  • 20
  • 24
  • Ah, sorry, I should have said that I have only forms that are created dynamically (updating my question). That does not mean I cannot access them but I would have to look them up. But I'm taking your advice about the decoupling. – Jan Doggen Jun 22 '16 at 12:17
  • The same principle applies. The point about the 'uses' clause in the implementation was that I did not expect the form to be in the same unit. This is the standard way to get around recursion with units. If necessary, use the global variable Application (in VCL.Forms or FMX.Forms as appropriate) to find the form that you need. – Dsm Jun 22 '16 at 12:40
1

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;
Community
  • 1
  • 1
Jan Doggen
  • 8,799
  • 13
  • 70
  • 144