Reputation: 9106
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
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
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