Often, I am asked how to implement something similar to the CodeGear RAD Studio (aka BDS) Welcome Page. Well, to be honest, I didn't know myself. All I have done for Borland/CodeGear so far, was implementing the whole HTML and JavaScript side, as well as XML and XSL. But this is all open source, and can be found in the $(BDS)\Welcomepage folder. No big secrets here.
Sample application acting on button click in HTML
But what you really want to know is, how to give HTML/JavaScript access to your application or how to implement your own URL, such as bds:/default.htm. Well, I took the time and investigated just that, today. And let me tell you, it is easy, once you know where to look. You need to know two interfaces:
First, let me give you the interface declarations, you need:
type PDOCHOSTUIINFO = ^TDOCHOSTUIINFO; TDOCHOSTUIINFO = record cbSize: ULONG; dwFlags: DWORD; dwDoubleClick: DWORD; chHostCss: POleStr; chHostNS: POleStr; end; IDocHostUIHandler = interface(IUnknown) ['{bd3f23c0-d43e-11cf-893b-00aa00bdce1a}'] function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT; const CommandTarget: IUnknown; const Context: IDispatch): HRESULT; stdcall; function GetHostInfo(var pInfo: TDOCHOSTUIINFO): HRESULT; stdcall; function ShowUI(const dwID: DWORD; const pActiveObject: IOleInPlaceActiveObject; const pCommandTarget: IOleCommandTarget; const pFrame: IOleInPlaceFrame; const pDoc: IOleInPlaceUIWindow): HRESULT; stdcall; function HideUI: HRESULT; stdcall; function UpdateUI: HRESULT; stdcall; function EnableModeless(const fEnable: BOOL): HRESULT; stdcall; function OnDocWindowActivate(const fActivate: BOOL): HRESULT; stdcall; function OnFrameWindowActivate(const fActivate: BOOL): HRESULT; stdcall; function ResizeBorder(const prcBorder: PRECT; const pUIWindow: IOleInPlaceUIWindow; const fRameWindow: BOOL): HRESULT; stdcall; function TranslateAccelerator(const lpMsg: PMSG; const pguidCmdGroup: PGUID; const nCmdID: DWORD): HRESULT; stdcall; function GetOptionKeyPath(out pchKey: POleStr; const dw: DWORD): HRESULT; stdcall; function GetDropTarget(const pDropTarget: IDropTarget; out ppDropTarget: IDropTarget): HRESULT; stdcall; function GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall; function TranslateUrl(const dwTranslate: DWORD; const pchURLIn: POleStr; out ppchURLOut: POleStr): HRESULT; stdcall; function FilterDataObject(const pDO: IDataObject; out ppDORet: IDataObject): HRESULT; stdcall; end; ICustomDoc = interface(IUnknown) ['{3050f3f0-98b5-11cf-bb82-00aa00bdce0b}'] function SetUIHandler(const pUIHandler: IDocHostUIHandler): HRESULT; stdcall; end;
type
record
end
interface
'{bd3f23c0-d43e-11cf-893b-00aa00bdce1a}'
function
const
stdcall
var
'{3050f3f0-98b5-11cf-bb82-00aa00bdce0b}'
Once you figured those out, the rest is straight forward. Create a new VCL application and place a TWebBrowser component on your form. Before you can assign your own object (in the sample, it is the form itself) you have to load some document. I recommend loading about:blank, that is fast and works always.
procedure TForm8.FormCreate(Sender: TObject); begin // initialize web browser wbbDisplay.Navigate('about:blank'); while wbbDisplay.Busy do Application.ProcessMessages; // we have to handle some Interface requests, register this form as IDocHostUIHandler (wbbDisplay.Document as ICustomDoc).SetUIHandler(Self); end;
procedure
begin
// initialize web browser
'about:blank'
while
do
// we have to handle some Interface requests, register this form as IDocHostUIHandler
as
Implement the IDocHostUIHandler interface in your form. Next you need to implement two methods:
function TForm8.GetExternal(out ppDispatch: IDispatch): HRESULT; begin ppDispatch := TMyApp.Create as IDispatch; Result := S_OK; end; function TForm8.TranslateUrl(const dwTranslate: DWORD; const pchURLIn: POLESTR; out ppchURLOut: POLESTR): HRESULT; var NewURL: WideString; begin if WideSameText(Copy(pchURLIn, 1, 6), 'app://') then begin NewURL := ExtractFilePath(Application.ExeName) + Copy(pchURLIn, 7, MaxInt); if NewUrl[Length(NewURL)] = '/' then SetLength(NewUrl, Length(NewUrl) - 1); ppchURLOut := POLESTR(NewURL); Result := S_OK; end else begin Result := S_FALSE; end; end;
if
'app://'
then
'/'
else
Now, you need to add a type library to your project. Once done, add an automation object (not COM-Object) to your application. This object you will return from the method GetExternal (see above, TMyApp there). Implement your methods as needed and you are done. In the code above, the application will change all URLs starting with app:// to load the files directly from the application folder. For some reason, web browser seems to add an additional slash (/) to the file requested, which has to be removed (see code TranslateUrl).
From JavaScript, you call your applications interfaced object by using the external. statement. If you name your method TestMe, you call it with external.TestMe(); from JavaScript.
That's it already. The sample projected can be downloaded (ZIP, 280 Kb).
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
Theme design by Jelle Druyts