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.

2
  • if you return the form handle in TWebBrowserExternal, like function GetformHandle(): Word ? Commented Jun 22, 2016 at 14:29
  • It is possible to use a much simpler approach to implement external methods in Delphi, using the late-bound functionality provided by ObjComAuto.TObjectDispatch. This way you don't need to define any interfaces nor a type library. Commented Dec 10, 2016 at 10:30

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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.
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.
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; 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.