Версия для печати


Как запустить Internet Explorer или подключиться к нему.
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1134

Сергей Осколков
дата публикации 06-05-2005 07:46

Как запустить Internet Explorer или подключиться к нему

В продолжение нескольких статей на Королевстве о работе с компонентом TWebBrowser хочу затронуть пару вопросов работы с Internet Explorer, которые раньше, кажется, не обсуждались. Почти все уже было в ответах Круглого стола, здесь - более подробно. Сначала немного теории

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 //проверка наличия свойства Document
  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&lt;&gt;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.

Интерфейс IHtmlWindow2

Получив указатель на интерфейс 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, как описано раньше, мы можем вызвать эту функцию.

//WB: TWebBrowser

procedure TMainForm.btnDoSearchClick(Sender: TObject);
var W: OleVariant;
begin
  W:=(WB.Document as IHtmlDocument2).parentWindow;
  W.showsearch;
end;