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:
- ICustomDoc - allows you to assign your own document handler for the web browser instance
- IDocHostUIHandler - allows you to return an interfaced object, that will be accessible from JavaScript and to manipulate the URL requested (as well as many other functions)
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;
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
wbbDisplay.Navigate('about:blank');
while wbbDisplay.Busy do
Application.ProcessMessages;
(wbbDisplay.Document as ICustomDoc).SetUIHandler(Self);
end;
Implement the IDocHostUIHandler interface in your form. Next you need to implement two methods:
- GetExternal - allows you to return an interfaced object (IDispatch), which you can access from JavaScript
- TranslateUrl - allows you to manipulate the URL before the web browser will attempt to load the file
- Return for all other methods either E_NOTIMPL or S_FALSE according to MSDN documentation.
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;
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).