Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  07:25[Войти] | [Зарегистрироваться]

События на web-странице

Сергей Осколков
дата публикации 07-06-2007 09:55

События на web-странице

События на веб-странице

Поводом для написания этой статьи послужил один вопрос на Круглом Столе. В нём автор хотел, чтобы по щелчку на изображении на странице TWebBrowser он мог бы как-то получать адрес (URL) этого изображения. Подобные вопросы были и раньше, в более общей форме их можно сформулировать так: как получить сообщение о событии, произошедшем с каким-нибудь из элементов страницы, загруженной в TwebBrowser? Как получить данные, связанные с этим событием?

Для обычных элементов управления Windows задача решается известным способом - события (events) в рамках VCL, сообщения (messages) - в Windows вообще. Но кнопки, выпадающие списки, поля ввода, изображения и т.д. на веб-страницах в Internet Explorer или WebBrowser не являются элементами управления Windows. И к ним этот подход не применим.

Решение - через события COM. Как известно, элементы веб-страницы представляются браузером в виде иерархии объектов так называемой объектной модели документа, DOM (document object model). В DOM у объектов есть и события. "Верхний" элемент иерарахии - объект "окно", среди его членов есть объект "документ", через который мы можем работать с элементами веб-страницы.

Доступ к этим объектам возможен, например, из сценариев JavaScript в рамках самого html-документа. Также мы имеем доступ к ним из Дельфи c помощью механизма COM, через интерфейсы, описанные в MSHTML_TLB.pas. Этот файл получается в результате импорта библиотеки типов Microsoft HTML object library. В этой библиотеке типов описаны и интерфейсы событий. Интерфейс, представляющий объект документ в целом - IHtmlDocumеnt2. Обратимся к интерфейсу событий этого объекта, а именно HtmlDocumentEvents2. (Есть еще интерфейс HtmlDocumentEvents, но я решил сразу обратиться к этому, по мнемоническому правилу - раз там Document2, то и здесь попробую Events2. :) ).

Для пробы, а именно задачу "попробовать" я ставил, выберем событие OnDoubleClick - если мы добавим обработчик этого события (двойного щелчка), то он не помешает нам "одинарно" щелкать (кликать) мышкой по документу в целях навигации. В интерфейсе HtmlDocumentEvents2 этому событию соответствует метод

function ondblclick(const pEvtObj: IHTMLEventObj): WordBool; dispid -601;

Реализация обработки события

События COM реализуются через интерфейсы обратного вызова, так называемые "стоки" (sink) событий. Клиенты, желающие получать оповещения о событиях на сервере, должны реализовать интерфейс IDispatch и передать серверу ссылку на него (передача осуществляется через метод Advise интерфейса IConnectionPoint сервера). Теперь, при возникновении соответствующего события на COM-сервере, сервер будет обращаться к этому интерфейсу клиента, при этом также передавая параметры произошедшего события. Нам остается только сделать так, чтобы при обращении сервера на клиенте выполнялись нужные нам процедуры обработки событий. Я не настолько большой знаток этой темы (события COM), чтобы излагать её в целом и подробно, поэтому приведу только практическое решение задачи для данного случая. В статье Анатолия Тенцера "Создание модулей расширения Microsoft Office" приводится код (правда, не полный) класса TBaseSink, служащего базовым классом стока, от которого можно наследовать классы, реализующие стоки для конкретных интерфейсов и событий и позаимствованного автором по его словам у Бина Ли (ссылки на статью и на сайт Бина Ли - внизу страницы). В статье также есть пример реализации наследника этого базового класса, в частности метода DoInvoke. Я в свое время основывался на этой статье, только добавил реализацию методов базового класса, отсутствовавших в ней и для данной задачи использовал этот класс.

В классе-потомке нужно переопределить защищенный метод DoInvoke, а именно, позволить в нем серверу вызывать обработчик нужного события и передавать в него параметры события. В общем случае метод должен вызывать соответствующий обработчик события по ID этого события, указанному в интерфейсе событий. Если отвлечься от передачи параметров в обработчики событий, то код в методе DoInvoke мог бы выглядеть примерно так:

case DispId of
   1: if Assigned(FOnEvent1)
    begin
      FOnEvent1;
      Result := S_OK;
    end;
   2:  if Assigned(FOnEvent2)
    begin
      FOnEvent2;
      Result := S_OK;
    end;
   3: if Assigned(FOnEvent3)
    begin
       FOnEvent3;
       Result := S_OK;
    end;
...
   end;
где вместо 1,2,3 и т.д. - ID соответствующих событий, взятые из объявления интерфейса в библиотеке типов.

В нашем конкретном случае это выглядит так:
function TDocSink.DoInvoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
  Flags: Word; var dps: TDispParams; pDispIds: PDispIdList; VarResult,
  ExcepInfo, ArgErr: Pointer): HResult;
type
  POleVariant = ^OleVariant;
begin
  Result := DISP_E_MEMBERNOTFOUND;
  try
  case DispId of
    -601: if Assigned(FOnDblClick)
    then begin
      FOnDblClick(IDispatch(dps.rgvarg^[pDispIds^[0]].dispVal));
      Result := S_OK;
    end;
  end;
  except
    Result := E_UNEXPECTED;
  end;
end;

Если посмотреть интерфейс HtmlDocumentEvents2, то можно увидеть, что событию OnDoubleClick соответствует ID=-601. В методе - это параметр функции DispId. Вся реализация сводится к тому, что к классу добавляется процедурное свойство OnDoubleClick следующего типа:

TSimpleEvent = function (const pEvtObj: IDispatch): WordBool of object;
и в случае DispID=-601, вызывается эта процедура (точнее - функция).

Откуда взялся этот тип? Из интерфейса событий, см. выше. Отличие в том, что я передаю параметр не типа IHTMLEventObj, а его предка IDispatch. Почему? Потому, что я знаю, как передать параметр этого типа в обработчик события.

Рассмотрим передачу параметров в обработчик события. Параметры передаются через параметр (извините повторение) метода DoInvoke dps: TDispParams;- это вариантный массив. Значения его элементов представлены типом TVariantArg (описан в модуле ActiveX), который представляет из себя запись с вариантами, из которой нужно извлечь значение нужного нам типа. В данном случае первый и единственный параметр, передаваемый в обработчик получается как

IDispatch(dps.rgvarg^[pDispIds^[0]].dispVal)

Если бы было несколько параметров разных типов, то они получались бы заменой индекса 0 на 1, 2 и т.д. и соответствующим типом. Например, если бы был ещё второй var параметр булевского типа, то мы могли бы получить его как

dps.rgvarg^[pDispIds^[1]].pbool^

При тестировании программы я столкнулся с тем, что при возникновении ошибки в обработчике события в дальнейшем этот обработчик уже начинал вызываться не при двойном, но и при одинарном щелчке на странице. Я решил, что это связано с тем, что функция в такой ситуации в результате исключительной ситуации не возвращает серверу нужный результат или что-то подобное. Добавление обработчика исключения и возврашение в это случае результата E_UNEXPECTED решило проблему.

Также я добавил в класс конструктор CreateConnected(pSource: IInterface), чтобы не вызывать сначала конструктор, а потом метод Connect класса, а сразу создавать его, подключенным к нужному экземпляру IHtmlDocument2. Код базового класса TBaseSink, а также класса TDocSink, реализующего сток для события двойного щелчка по документу - в модуле sink.pas тестового примера.

Наполняем обработчик события содержанием

Теперь создадим проект с одной формой, поместим на неё TWebBrowser, добавим в uses ссылку на модуль с классом-стоком, в секцию private формы добавим
private
 { Private declarations }
 Doc: IHtmlDocument2;
 DocSink: TDocSink; //класс, реализующий сток
 function DocOnDblClick(const pEvtObj: IDispatch): WordBool; //обработчик события
В обработчике OnCreate формы напишем
procedure TMainForm.FormCreate(Sender: TObject);
begin
  WB.Navigate('about:blank'); //чтобы свойство WB.Document было проинициализировано
  Doc := WB.Document as IHTMLDocument2;
  DocSink := TDocSink.CreateConnected(Doc);
  DocSink.OnDblClick := DocOnDblClick;
end;

Теперь давайте напишем что-то в обработчике события документа DocOnDoubleClick. Как мы видим, у функции есть параметр типа IDispatch, который на самом деле имеет тип IHTMLEventObj, к какому мы его и приведем. Посмотрим, что мы можем получить из этого параметра.И здесь нас ждёт приятный сюрприз: спасибо разработчикам HTML DOM, в этом параметре (Объект события) содержится уйма информации, в том числе ссылка на конкретный объект страницы, в котором произошло событие - свойство srcElement. Кого это интересует в практическом плане, можно посмотреть интерфейс IHTMLEventObj в модуле MSHTML_TLB.pas. Например, давайте напишем такой обработчик :

function TMainForm.DocOnDblClick(const pEvtObj: IDispatch): WordBool;
var EvtObject: IHTMLEventObj;
    Elt: IHtmlElement;
    TagName: string;
begin
  Result := True;
  if not pEvtObj.QueryInterface(IHtmlEventObj, EvtObject) = S_OK
  then exit;
  Memo.Lines.Add('x=' + IntToStr(EvtObject.clientX));
  Memo.Lines.Add('y=' + IntToStr(EvtObject.clientY));
  if (EvtObject.srcElement.QueryInterface(IHtmlElement, Elt) = S_OK)
  and (LowerCase(Elt.tagName) = 'img')
  then Memo.Lines.Add((Elt as IHTMLImgElement).src);
end;

По двойному щелчку мыши на веб-странице мы получаем в Memo клиентские координаты курсора мыши в этот момент и, если элемент, над которым произошел щелчок - картинка, то получаем её адрес. Всё, задача выполнена.

Понятно, что если мы хотим обработать другие события, то нужно в модуле стока объявить процедурный тип, соответствующий нужному методу интерфейса HtmlDocumentEvents2, добавить соответствующее свойство в класс, реализующий сток, в методе DoInvoke класса добавить в оператор case случай c соответствующим событию ID, и написать нужный код в обработчике события в форме, содержащей WebBrowser. В библиотеке типов MSHTML_TLB.pas описаны и событийные интерфейсы для других объектов, кроме документа, но поскольку через параметр pEvtObj мы получаем ссылку на конкретный объект, в котором произошло событие, то для обработки тех событий элементов страницы, которые входят в HtmlDocumentEvents2, можно использовать этот интерфейс. Для специфических событий каких-то элементов, нужно проделать аналогичное описанному здесь применительно к соответствующему интерфейсу, например IHtmlElement и его интерфейсу событий - HtmlElementEvents.

Код тестового приложения прилагается. Ссылки:
Сергей Осколков,
Специально для Королевства Delphi


К материалу прилагаются файлы:


Смотрите также материалы по темам:


 Обсуждение материала [ 26-05-2008 10:00 ] 27 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования