Автор: Сергей Осколков
Оригинал статьи >>
В продолжение нескольких статей
о работе с компонентом TWebBrowser хочу затронуть
пару вопросов работы с Internet Explorer, которые
раньше, кажется, не обсуждались. Почти все уже было в ответах
Круглого стола, здесь - более подробно. Сначала немного
теории
На рисунке изображена архитектура Internet Explorer (IE). Для того, чтобы соединить компоненты в
целое, используются элементы ActiveX и интерфейсы ActiveDocument. Сам исполняемый файл IE
мал (у меня на машине IE6 - 89 КБ). Он предоставляет окно и панель инструментов и
непосредственно управляет элементом-браузером WebBrowser (ShDocVw.dll). Этот элемент, в свою очередь,
управляет компонентом MSHTML.dll, который осуществляет парсинг (разбор) html и его
отображение в окне браузера, а также предоставление документа в виде объектной
модели. MSHTML, в свою очередь, управляет скриптовыми движками, плагинами и т.д. для
отображения своего содержимого. WebBrowser также управляет активными документами,
которые могут быть в него загружены, например документами MS Office. Как WebBrowser,
так и MSHTML предоставляют свои интерфейсы для внешних программ. Первый из них может
использоваться как элемент ActiveX. Компонент TWebBrowser из палитры компонентов Дельфи - это
просто обертка для него.
Практические выводы из написанного следующие: для управления браузером в целом
обычно мы используем методы TWebBrowser. Например, для загрузки документа или его печати. Для
доступа к элементам документа мы используем интерфейсы, объявленные в MSHTML,
основной из которых - IHtmlDocument2, получаемый через свойство TWebBrowser.Document. Еще я бы отметил
интерфейс IHtmlWindow, который соответсвует объекту window в javascript. Через него также можно
выполнить ряд полезных действий и получить доступ к элементам страницы. Теперь - к
более конкретным вопросам.
Напомню, что компонент TWebBrowser и интерфейс IWebBrowser2 - основной интерфейс для управления
браузером, объявлены в модуле ShDocVw.pas. Для работы с интерфейсами MSHTML нужно импортировать одноименную
библиотеку типов MSHTML.tlb (меню Project->Import Type Library, выбрать Microsoft Html Object Library).
Первая задача: запустить Internet Explorer и открыть в нем документ. Для запуска можно, конечно, воспользоваться функциями CreateProcess или
ShellExecute, как для любой другой программы. Однако мы воспользуемся рассматриваемыми методами.
procedure TMainForm.Open(URL: string);
var WB: IWebBrowser2;
begin
WB:=CoInternetExplorer.Create;
WB.Visible:=True;
WB.Navigate(URL, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
WB:=nil;
end; |
Здесь мы запускаем IE и открываем в нем нужный документ с диска или Веб-страницу.
Если не уничтожать переменную WB сразу же, как в примере, то через нее мы имеем доступ к
загруженному документу и также можем управлять экземпляром IE. Например, закрыть
его. Непосредственно в интерфейсе IWeBrowser2 метода для этого нет. Однако в ShDocVw.pas
объявлен интерфейс IWebBrowserApp = interface(IWebBrowser),
который содержит метод Quit. Я не очень понимаю, почему это так, но работает и (WB as IWebBrowserApp).Quit,
и просто WB.Quit - закрывается запущенный экземпляр IE.
В модуле ShDocView также определен тип TInternetExplorer. Им тоже можно
пользоваться в описанных целях.
...
type
TMainForm = class(TForm)
....
procedure MyBeforeNavigate2(Sender: TObject; var pDisp: OleVariant;
var URL: OleVariant; var Flags: OleVariant;
var TargetFrameName: OleVariant; var PostData: OleVariant;
var Headers: OleVariant; var Cancel: OleVariant);
public
IE: TInternetExplorer;
....
end;
...
implementation
...
procedure TMainForm.btnIECreateClick(Sender: TObject);
begin
if IE=nil then
begin
IE:=TInternetExplorer.Create(nil);
IE.Visible:=True;
IE.OnBeforeNavigate2:=MyBeforeNavigate2;
IE.Navigate('d:\Internet\update.htm');
end;
end;
procedure TMainForm.MyBeforeNavigate2(Sender: TObject; var pDisp, URL,
Flags, TargetFrameName, PostData, Headers, Cancel: OleVariant);
begin
Memo.Lines.Add(URL);
end;
procedure TMainForm.btnIECloseClick(Sender: TObject);
begin
IE.Quit;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
IE.Free;
end;
|
В примере запускается Internet Explorer, мы подключаемся к его событию OnBeforeNavigate2 и открывается
страница (здесь - с жесткого диска). При дальнейших переходах IE на другие страницы,
строка адреса (URL) будет добавляться в элемент Memo. Мы также имеем
возможность закрыть этот экземпляр IE из своей программы методом
IE.Quit.
Следующая задача - подключиться к уже запущенному экземпляру IE. Если попытаться
использовать функцию
GetActiveOleObject('InternetExporer.Application');
то мы получим сообщение об ошибке EOleSysError с сообщением "Операция недоступна".
Дело в том, что, видимо из соображений безопасности, IE как сервер
автоматизации, после запуска недоступен внешним программам. Это осуществлено так:
при старте любой сервер автоматизации регистрирует себя с помощью функции
CoRegisterClassObject. Если установить соответствующий флаг (REGCLS_SINGLEUSE) в этой
функции, то объект будет недоступен другим приложениям.
Однако, подключиться к интерфейсу IWebBrowser2 запущенного IE все-таки можно! В том же
модуле ShDocVw.pas объявлен интерфейс IShellWindows. Через него можно подключиться ко всем
открытым окнам IE и Проводника (Explorer) Windows. Отличить первые от вторых можно по наличию
свойства Document. Для доступа воспользуемся стандартными для коллекций методом Item(i)
и свойством Count.
Здесь я натолкнулся на один подводный камень. Попробуем вывести адреса
загруженных страниц во всех экземплярах IE в компонент Memo следующим образом:
procedure TMainForm.Button2Click(Sender: TObject);
var Winds: IShellWindows;
i: integer;
begin
Winds:=CoShellWindows.Create;
for i:=0 to Winds.Count-1 do
if (Winds.Item(i) as IWEbBrowser2).Document <> nil
then Memo.Lines.Add(((Winds.Item(i) _
as IWEbBrowser2).Document as IHtmlDocument2).url);
end; |
При выполнении этот код вызывал ошибку Interface not supported. Оказалось, что у окон
проводника свойство Document может быть не равно nil и они благополучно проходят
проверку, но при применении оператора as (Document as IHtmlDocument2) возникает исключение, т.к.
получить интерфейс IHtmlDocument2 не удается. Как же правильно провести проверку? Здесь можно
воспользоваться тем, что в применении к интерфейсам оператор as является
оберткой для вызова метода QueryInterface и при компиляции преобразуется в вызовы
указанного метода. Метод IUnknown.QueryInterface я и
применил. Если окно
является окном IE, то мы получим интерфейс IHtmlDocument2, а функция возвратит
результат S_OK. В другом случае результат функции будет иным. Работающий код таков:
procedure TMainForm.Button2Click(Sender: TObject);
var Winds: IShellWindows;
IEWB: IWebBrowser2;
i: integer;
Doc: IHtmlDocument2;
begin
Memo.Clear;
Winds:=CoShellWindows.Create;
for i:=0 to Winds.Count-1 do
if (Winds.Item(i) as IWEbBrowser2).Document<>nil then
begin
IEWB:=Winds.Item(i) as IWEbBrowser2;
if IEWB.Document.QueryInterface(IhtmlDocument2, Doc)= S_OK
then Memo.Lines.Add(Doc.url);
end;
end; |
Кстати говоря, окна Проводника тоже поддерживают интерфейс IWebBrowser2, и через него можно определить, какая папка открыта в окне в
данный момент.
Подключившись к окну IE мы далее можем управлять им и получить доступ к
загруженному в него документу. Например, можно закрыть все окна, где адрес страницы не отвечает заданным
условиям. Можно также получить доступ к событиям IWebBrowser2. Кроме того, в модуле ShDocVw
объявлен интерфейс событий DShellWindowsEvents
DShellWindowsEvents = dispinterface
['{FE4106E0-399A-11D0-A48C-00A0C90A8F39}']
procedure WindowRegistered(lCookie: Integer); dispid 200;
procedure WindowRevoked(lCookie: Integer); dispid 201;
end;
Если подключиться к нему, то можно отслеживать события возникновения и уничтожения окон IE и Windows Explorer.
Получив указатель на интерфейс Document:
IHtmlDocument2, мы можем через него получить
доступ к интерфейсу IHtmlWindow2, который
соответствует объекту window в javasript.
var W: IHtmlWindow2;
W:=Document.ParentWindow;
Не буду описывать все его свойства, их можно
найти в MSHTML_TLB.pas, упомяну только процедуру
procedure Alert(const message: WideString);
Эта процедура выводит окно с сообщением в браузере. Другой, на мой взгляд, необычный
для Дельфи способ использования этого интерфейса - обращение к именованным
объектам страницы. Как известно, в javascript объект window является объектом самого
высокого уровня в иерархии, включающим в себя все остальные. К объектам, имеющим имя,
можно обращаться через него - window.myObjectName. Если использовать тип OleVariant,
т.е. позднее связывание, то это можно использовать и в Дельфи. Пусть на странице есть
сценарий javascript, в котором описана функция showsearch(). Открыв эту страницу в TWebBrowser или в IE, как
описано раньше, мы можем вызвать эту функцию.
procedure TMainForm.btnDoSearchClick(Sender: TObject);
var W: OleVariant;
begin
W:=(WB.Document as IHtmlDocument2).parentWindow;
W.showsearch;
end; |