Использование компонента TWebBrowser

Анатолий Тенцер

Базовые операции

Тонкая настройка

Доступ к документной модели TWebBrowser

 

Во многих современных программах необходимо работать с данными в формате HTML. В качестве средства для просмотра таких данных в Delphi применяется компонент TWebBrowser, который использует элемент управления ActiveX WebBrowser, входящий в состав Microsoft Internet Explorer. Таким образом, этот компонент имеется на любом компьютере, на котором установлен Internet Explorer. Все последние версии Windows содержат TWebBrowser в своем составе и без него практически неработоспособны.

Базовые операции

Для того чтобы использовать TWebBrowser в своей программе, следует разместить на форме соответствующий компонент, имеющийся на закладке Internet. Затем, для отображения в нем страницы HTML, необходимо вызвать его метод Navigate:

procedure TForm1.Button1Click(Sender: TObject);
var
Flags, TargetFrameName, PostData, Headers: OleVariant;
begin
WebBrowser1.Navigate(‘http://www.borland.com’, Flags,
TargetFrameName, PostData, Headers);
end;

Рассмотрим подробнее параметры, передаваемые в метод Navigate.

Первым параметром является строка с URL, указывающим адрес, из которого должна осуществляться загрузка. Поддерживаются все протоколы, доступные в IE, например file:// — загрузка файла, res:// — загрузка из ресурса.

Остальные параметры не являются обязательными и служат для передачи дополнительной информации (табл. 1).

Наиболее интересным является параметр PostData, позволяющий передать на Web-сервер данные, полученные в результате заполнения формы, если этот сервер требует HTTP — транзакции POST. Так, следующий фрагмент кода передает на сервер имя пользователя и пароль, заполненные в форме Delphi:

var
LoginDialog: TLoginDialog;
Flags, TargetFrameName, PostData, Headers: OleVariant;
S: String;

...
with TLoginDialog.Create(Application) do
try
if ShowModal = mrOk then
begin
S := Format(‘UserName=%s&Password=%s’, _
[Edit1.Text, Edit2.Text]);
PostData := VarArrayCreate([1, _
Length(S) + 1], varByte);
System.Move(S[1], VarArrayLock(PostData)^, _
Length(S) + 1);
VarArrayUnlock(PostData);
Headers :=
‘Content-Type: application/x-www-form-urlencoded’#10#13;
WebBrowser1.Navigate(‘http://intranetserver/secretpage’, Flags,
TargetFrameName, PostData, Headers);
end;
finally
Free;
end;

Например, на Web-сервере этот запрос может быть обработан, следующим ASP-скриптом:

Dim sConnect
Dim sUserName
Dim sPassword

sUserName = Request.Form(“User”)
sPassword = Request.Form(“Pass”)

sConnect = “Provider=SQLOLEDB.1;Persist Security Info=True;” & _
“Initial Catalog=Katren;Data Source=DBSERVER;” & _
“Password=” & sPassword & _
“;User ID=” & sUserName
Session(“ConnectString”) = sConnect

После того как данные получены, необходимо предоставить пользователю возможность работы с ними. Многие функции TWebBrowser доступны через метод ExecWB, предоставляющий простой способ обращения к интерфейсу IOleCommandTarget. Этот метод имеет такой вид:

procedure TWebBrowser.ExecWB(
cmdID: OLECMDID; // Идентификатор команды
cmdexecopt: OLECMDEXECOPT; // Параметры выполнения
var pvaIn, // Дополнительные параметры,
pvaOut: OleVariant // зависящие от команды
); safecall;

CmdID может быть одной из констант OLECMDID, определенных в файле ShDocVw.pas.

Параметр cmdexecopt может принимать одно из четырех значений, приведенных в табл. 2.

Параметры pvaIn и pvaOut являются дополнительными и зависят от конкретной команды.

Имеется возможность запросить у TWebBrowser доступность той или иной команды при помощи функции:

function TWebBrowser.QueryStatusWB(
   cmdID: OLECMDID // Идентификатор команды
): OLECMDF; safecall;

Функция возвращает битовую маску из значений, приведенных в табл. 3.

Следовательно, можно настраивать интерфейс в зависимости от поддерживаемых текущей версией TWebBrowser возможностей:

var
Flags: OLECMDF;

...

Flags := WebBrowser1.QueryStatusWB(OLECMDID_COPY);
ActionCopy.Visible := (Flags and OLECMDF_SUPPORTED) =
OLECMDF_SUPPORTED;
ActionCopy.Enabled := (Flags and OLECMDF_ENABLED) =
OLECMDF_ENABLED;

Для печати содержимого TWebBrowser служит команда OLECMDID_PRINT. Метод печати может выглядеть, в частности, следующим образом:

procedure TForm1.ActionPrintExecute(Sender: TObject);
var
A, B: OleVariant;
UserAction: Cardinal;
begin
if Sender = ActionPrintWithSetup then
UserAction := OLECMDEXECOPT_PROMPTUSER
else
UserAction := OLECMDEXECOPT_DONTPROMPTUSER;
try
WebBrowser1.ExecWB(OLECMDID_PRINT, UserAction, A, B);
except
end;
end;

Блок try … except … end необходим по той причине, что TWebBrowser при выполнении любой команды при помощи ExecWB генерирует исключение EOleException с кодом:

-2147221248 ($80040100) Trying to revoke _
a drop target that has not been registered.

Начиная с Internet Explorer 5 документированы дополнительные команды, поддерживаемые через интерфейс IOleCommandTarget. Они существенно расширяют возможности по управлению компонентом, однако недоступны либо не документированы в версии 4. Это создает определенные сложности при программировании. Так, чтобы организовать поиск внутри загруженной страницы, необходим следующий код:

const
// Недокументированная константа
CGID_IE4: TGUID = ‘{ed016940-bd5b-11cf-ba4e-00c04fd70816}’;
// Документировано в IE5 SDK
CGID_MSHTML: TGUID = ‘{DE4BA900-59CA-11CF-9592-444553540000}’;
IDM_FIND = 67; procedure TForm1.ActionFindExecute(Sender: TObject);
var
A, B: OleVariant;
Target: IOleCommandTarget;
OleCmd: TOLECMD;
begin
// Получаем интерфейс IOleCommandTarget
Target := wbMain.Document as IOLECommandtarget;
with OleCmd do
begin
cmdId := IDM_FIND;
cmdf := 0;
end;
// Запрашиваем, поддерживается ли команда
Target.QueryStatus(@CGID_MSHTML, 1, @OleCmd, NIL);
if (OleCmd.cmdf and OLECMDF_SUPPORTED) = OLECMDF_SUPPORTED then
// Да, у нас IE5+, поэтому вызываем документированным способом
Target.Exec(@CGID_MSHTML, IDM_FIND,
OLECMDEXECOPT_DODEFAULT, A, B)
else
// Нет, у нас IE4, поэтому вызываем недокументированным способом
Target.Exec(@CGID_IE4, 1, OLECMDEXECOPT_DODEFAULT, A, B);
end;

Использование недокументированного вызова в данном случае оправданно, так как в версии 4 этот вызов уже не будет изменяться, а в версии 5 мы обнаруживаем и используем документированный метод. В то же время IE4 пока еще достаточно распространен, так что будет нецелесообразно полностью лишать программу возможности поиска на таких компьютерах.

В начало В начало

Тонкая настройка

Если требуется более тонкая настройка компонента, то необходимо реализовать интерфейс IDocHostUIHandler, позволяющий программисту взять под контроль поведение TWebBrowser.

Интерфейс объявлен как:

type
TDocHostInfo = packed record
cbSize: ULONG;
dwFlags: DWORD;
dwDoubleClick: DWORD;
end; const
DOCHOSTUIFLAG_DIALOG = 1;
DOCHOSTUIFLAG_DISABLE_HELP_MENU = 2;
DOCHOSTUIFLAG_NO3DBORDER = 4;
DOCHOSTUIFLAG_SCROLL_NO = 8;
DOCHOSTUIFLAG_DISABLE_SCRIPT_INACTIVE = 16;
DOCHOSTUIFLAG_OPENNEWWIN = 32;
DOCHOSTUIFLAG_DISABLE_OFFSCREEN = 64;
DOCHOSTUIFLAG_FLAT_SCROLLBAR = 128;
DOCHOSTUIFLAG_DIV_BLOCKDEFAULT = 256;
DOCHOSTUIFLAG_ACTIVATE_CLIENTHIT_ONLY = 512; const
DOCHOSTUIDBLCLK_DEFAULT = 0;
DOCHOSTUIDBLCLK_SHOWPROPERTIES = 1;
DOCHOSTUIDBLCLK_SHOWCODE = 2; type
IDocHostUIHandler = interface(IUnknown)
[‘{bd3f23c0-d43e-11cf-893b-00aa00bdce1a}’]
function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT;
const pcmdtReserved: IUnknown;
const pdispReserved: 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(var 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; var ppchURLOut: POLESTR): HRESULT;
stdcall;
function FilterDataObject(const pDO: IDataObject;
out ppDORet: IDataObject): HRESULT; stdcall;
end;

Наследник TWebBrowser, реализующий этот интерфейс, должен быть объявлен так:

type
TCustomizedWebBrowser = class(TWebBrowser, IDocHostUIHandler)
// Реализация методов IDocHostUIHandler
end;

На нашем CD-ROM приведен код такого компонента, реализующего минимальную функциональность. Вы можете использовать его как основу для создания своих расширенных наследников TWebBrowser.

А теперь рассмотрим наиболее интересные, с точки зрения программиста, методы интерфейса IDocHostUIHandler.

Начнем с метода ShowContextMenu:

function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT;
const pcmdtReserved: IUnknown;
const pdispReserved: IDispatch): HRESULT;

Эта функция вызывается в том случае, когда TWebBrowser должен показать контекстное меню. Если вы отображаете собственное меню или хотите подавить меню, то функция должна вернуть S_OK, а если меню должен показать TWebBrowser — то S_FALSE.

В функцию передаются следующие параметры:

1. DwID — идентификатор меню, который может принимать одно из следующих значений:

const
CONTEXT_MENU_DEFAULT = 0;
CONTEXT_MENU_IMAGE = 1;
CONTEXT_MENU_CONTROL = 2;
CONTEXT_MENU_TABLE = 3;
CONTEXT_MENU_DEBUG = 4;
CONTEXT_MENU_1DSELECT = 5;
CONTEXT_MENU_ANCHOR = 6;
CONTEXT_MENU_IMGDYNSRC = 7;

В зависимости от значения идентификатора вы можете вывести подходящее меню.

2. ppt — координаты, в которых должно быть показано меню.

3. pcmdtReserved — интерфейс IOleCommandTarget, позволяющий запросить состояние команд и их выполнение.

4. pdispReserved — интерфейс IDispatch объекта, для которого вызывается меню.

Простейшая реализация этого метода может выглядеть следующим образом:

function TcustomizedWebBrowser.ShowContextMenu(const dwID: DWORD;
const ppt: PPOINT; const pcmdtReserved: IUnknown;
const pdispReserved: IDispatch): HRESULT;
begin
// Предполагаем, что поле FPopupMenu хранит ссылку
// на компонент TPopupMenu
if Assigned(FPopupMenu) then begin
pmContext.Popup(ppt.X, ppt.Y);
Result := S_OK;
end
else Result := S_FALSE;
end;

Для полного запрета контекстного меню метод должен всегда возвращать S_OK.

Следующий метод, который мы рассмотрим — GetHostInfo:

unction GetHostInfo(var pInfo: TDocHostInfo): HRESULT; stdcall;

Приложение может заполнить структуру pInfo, определенную как:

TDocHostInfo = packed record
cbSize: ULONG;
dwFlags: DWORD;
dwDoubleClick: DWORD;
end;

Здесь dwFlags — битовая маска из следующих флагов, приведенных в табл. 4, а dwDoubleClick задает реакцию на двойной щелчок мышью и может принимать одно из значений, приведенных в табл. 5.

Метод должен вернуть S_OK или код ошибки OLE.

Например, чтобы создать окно с плоскими полосами прокрутки и без трехмерной рамки, необходимо реализовать этот метод следующим образом:

function TCustomizedWebBrowser.GetHostInfo(
var pInfo: TDocHostInfo): HRESULT; stdcall;
begin
with pInfo do
dwFlags := dwFlags or DOCHOSTUIFLAG_NO3DBORDER or
DOCHOSTUIFLAG_FLAT_SCROLLBAR;
Result := S_OK;
end;

Метод

function TranslateAccelerator(const lpMsg: TMsg;
const pguidCmdGroup: TGUID; nCmdID: DWORD): HRESULT; stdcall;

позволяет перехватить исполнение команд и обработку «горячих» клавиш и заменить ее на свою.

Метод

function GetOptionKeyPath(var pchKey: PWideChar; 
dwReserved: DWORD): HRESULT; stdcall;

позволяет задать путь в реестре, который TWebBrowser будет использовать для хранения настроек. Это дает возможность, в частности, сделать используемый в программе компонент независимым от текущих настроек Internet Explorer.

Путь должен содержаться в ключе реестра HKEY_CURRENT_USER.

Этот метод должен выделить память под строку функцией CoTackMemAlloc. Даже в случае ошибки параметр pchKey нужно инициализировать значением NIL или адресом строки. Метод возвращает S_OK в случае успеха, а в противном случае — S_FALSE.

Типичная реализация этого метода может выглядеть так:

function TCustomizedWebBrowser.GetOptionKeyPath(
var pchKey: PWideChar; dwReserved: DWORD): HRESULT;
var
ResultLen: Integer;
begin
Result := S_FALSE;
// В поле TCustomizedWebBrowser.FOptionKeyPath: String
// хранится путь к настройкам
if Length(FOptionKeyPath) > 0 then
begin
// Получаем длину строки UNICODE
ResultLen := MultiByteToWideChar(CP_ACP, 0,
PChar(FOptionKeyPath), -1, NIL, 0);
// Выделяем память под буфер
pchKey := CoTaskMemAlloc(ResultLen * SizeOf(WideChar));
// Если выделение успешно, копируем строку в буфер
if Assigned(pchKey) then
begin
MultiByteToWideChar(CP_ACP, 0, PChar(FOptionKeyPath), -1,
pchKey, ResultLen);
Result := S_OK;
end;
end else begin
// Свойство не задано — инициализируем параметр в NIL
pchKey := NIL;
end;
end;

Существует ряд настроек, которые, несмотря на наличие обработчика GetOptionKeyPath, в любом случае берут из стандартных параметров Internet Explorer. Наиболее важными из них являются колонтитулы, используемые при печати. В версиях Internet Explorer до 5.5 включительно единственным способом изменить (или подавить) колонтитулы является запись новых значений в ключ реестра:

HKCU\Software\Microsoft\Internet Explorer\PageSetup 

перед печатью и восстановление их после печати.

А теперь поговорим о методе

function GetExternal(var ppDispatch: IDispatch): HRESULT; stdcall;

Он позволяет вернуть указатель на реализованный в вашем приложении интерфейс IDispatch, который будет доступен для скриптов в TWebBrowser. Если вы не реализуете этот интерфейс, то параметр ppDispatch должен быть установлен равным NIL. Метод возвращает S_OK в случае успеха или код ошибки OLE в случае ошибки.

Методы этого интерфейса доступны из скриптов, которые выполняют в TWebBrowser следующим образом:

window.external.MethodName

Реализовать IDispatch можно, например, при помощи класса TAutoObject.

Метод TranslateURL позволяет изменить URL, по которому осуществляется загрузка страницы.

function TranslateURL(dwTranslate: DWORD; pchURLIn: PWideChar;
   var ppchURLOut: PWideChar): HRESULT; stdcall;

pchURLIn указывает на строку, содержащую исходный URL. Если ваше приложение осуществляет трансляцию, то оно должно выделить память под новое значение, используя функцию CoTaskMemAlloc, заполнить буфер новым значением URL и вернуть S_OK.

В противном случае вы должны присвоить ppchURLOut значение NIL и вернуть S_FALSE. При возникновении ошибки метод должен вернуть OLE-код ошибки.

Обработчик вызывается только при интерактивном переходе по ссылке из TWebBrowser и не вызывается при переходе с помощью метода Navigate.

В начало В начало

Доступ к документной модели TWebBrowser

В Internet Explorer реализовано расширение HTML, называемое Dynamic HTML (DHTML). Эта модель представляет все элементы HTML-документа в виде набора коллекций объектов, доступных для изменения. Скрипты, встроенные в страницы и приложения и имеющие доступ к этим коллекциям, могут находить и изменять их элементы, а также добавлять новые, причем изменения будут немедленно отражены в окне TWebBrowser. Иерархическое объектное представление HTML-объектов называется DOM (Document Object Model).

Программисту DOM в элементе управления IE ActiveX доступна в виде набора COM-интерфейсов. Отправной точкой для доступа к ней служит свойство:

property Document: IDispatch;

Это свойство обеспечивает доступ к интерфейсу IHtmlDocument2, позволяющему работать с содержимым документа. Для получения интерфейса необходимо запросить его при помощи оператора as:

var
  Document: IHtmlDocument2;
...

Document := WebBrowser.Document as IHtmlDocument2;

Документ в DOM представляет собой набор коллекций элементов. Для доступа к коллекции служит интерфейс IHtmlElementCollection, а к элементу коллекции — IHtmlElement. Следующий пример выводит все тэги, имеющиеся в текущем документе и текст внутри тэгов:

procedure TForm1.Button1Click(Sender: TObject);
var
HtmlDocument: IHtmlDocument2;
HtmlCollection: IHtmlElementCollection;
HtmlElement: IHtmlElement;
I: Integer;
begin
Memo1.Lines.Clear;
HtmlDocument := WebBrowser.Document as IHtmlDocument2;
HtmlCollection := HtmlDocument.All;
for I := 0 to HtmlCollection.Length - 1 do begin
HtmlElement := HtmlCollection.Item(i, 0) as IHtmlElement;
Memo1.Lines.Add(HtmlElement.TagName + ‘ ‘ +
HtmlElement.InnerText);
end;

Кроме того, возможно динамическое создание документов в памяти, без необходимости записи их на диск и вызова метода Navigate с протоколом ‘file://’

Проиллюстрируем работу с документной модели TWebBrowser на конкретном примере. Расположим на форме компоненты TWebBrowser, TMemo и три TButton, а потом создадим следующие обработчики событий:

uses MSHTML, ActiveX;


procedure TForm1.FormCreate(Sender: TObject);
begin
// Инициализируем пустой документ в TWebBrowser
WebBrowser1.Navigate(‘about:blank’);
end; procedure TForm1.Button1Click(Sender: TObject);
var
Document: IHTMLDocument2;
V: OleVariant;
begin
// Этот метод переписывает в TWebBrowser HTML-
// документ из TMemo
Document := WebBrowser1.Document as IHtmlDocument2;
V := VarArrayCreate([0, 0], varVariant);
V[0] := Memo1.Text;
Document.Write(PSafeArray(TVarData(v).VArray));
Document.Close;
end; procedure TForm1.Button3Click(Sender: TObject);
var
Document: IHTMLDocument2;
Collection: IHTMLElementCollection;
Element: IHTMLElement;
I: Integer;
begin
// Этот метод модифицирует текст документа при помощи DHTML
Document := WebBrowser1.Document as IHtmlDocument2;
Collection := Document.all;
Collection := Collection.Tags(‘BODY’) as IHTMLElementCollection;
Element := Collection.Item(NULL, 0) as IHTMLElement;
Element.InnerText := ‘Modifyed by DHTML’;
end; procedure TForm1.Button2Click(Sender: TObject);
var
Document: IHTMLDocument2;
begin
// Этот метод позволяет просмотреть в TMemo код HTML
// документа из TWebBrowser
Document := WebBrowser1.Document as IHtmlDocument2;
Memo1.Text := (Document.all.Item(NULL, 0)
as IHTMLElement).OuterHTML;
end;

В Memo1.Lines запишем такой текст:

<HTML>
<HEAD>
<TITLE>Hello World</TITLE>
</HEAD>
<BODY>
Hello again !
</BODY>
</HTML>

Итак, мы получили возможность динамически создавать HTML-документы и предоставлять их пользователю.

КомпьютерПресс 6'2001