Версия для печати
Как запустить 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<>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;