Обнаружение изменений активного элемента в документе TWebBrowser
есть ли события, я могу подключиться к процессу, чтобы определить, когда активный элемент на веб-странице? Например, когда пользователь фокусирует поле редактирования.
Я знаю, что могу проверить активный элемент в таймере, но я бы предпочел избежать этого, если это возможно.
1 ответов
это не совсем полный ответ на ваш вопрос, но, надеюсь, вы получите большую часть пути.
(для будущих читателей, которые прибудут сюда через аналогичный q:
Предположим, у вас есть единица импорта библиотеки типов для сервера automation/Com, например SHDocVw, MSHTML или MS Word. Иногда импортер библиотеки типов Delphi добавляет поддержку событий в оболочку Delphi TObject-descendant, которую он генерирует, например, события для TWebBrowser, OnNavigateComplete и т. д. В других случаях он не может или не будет генерировать класс-оболочку Delphi, но вы все равно можете присоединять к событиям объектов сервера одним из нескольких методов, таких как создание EventObject, подобного приведенному ниже, который подключается между событиями объекта сервера и обработчиком событий в коде Delphi.
обработка событий интерфейса в основном включает определение класса Delphi, который реализует интерфейс IDispatch, а затем подключение этого интерфейса к объект Ole или COM, о событиях которого вы хотите получать уведомления. Затем, когда события происходят в Ole / COM "позади" интерфейса, он вызывает ваш IDispatch так же, как вы вызываете его. То, что вы делаете с уведомлениями о событиях, полностью зависит от вас; код ниже передает их методу TForm1. )
EventObject ниже тесно основан на одном, опубликованном в Borland NGs в ноябре 2003 года Деборой Пейт из TeamB (у нее есть действительно хороший раздел о ней сайт об автоматизации с помощью Delphi -http://www.djpate.freeserve.co.uk/Automation.htm). Объект является довольно общим, поскольку он не ограничивается обработкой событий любого конкретного объекта Ole/COM.
// The following code is intended to illustrate methods of detecting that the
// active element in an Html page has changed. See the comments in the AnEvent
// procedure about how exactly to detect such a change.
//
// The code also illustrates how to handle a single event, e.g. onbeforeeditfocus
// of an Events objects such as HtmlDocumentEvents or HtmlDocumentEvents2 (see MSHTML.Pas)
// or all the events the events interface contains.
type
TInvokeEvent = procedure(Sender : TObject; DispIP : Integer) of Object;
TEventObject = class(TInterfacedObject, IDispatch)
private
FOnEvent: TInvokeEvent;
FSinkAllEvents : Boolean;
protected
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
public
constructor Create(const AnEvent : TInvokeEvent; SinkAll : Boolean);
property OnEvent: TInvokeEvent read FOnEvent write FOnEvent;
property SinkAllEvents: Boolean read FSinkAllEvents;
end;
type
TForm1 = class(TForm)
[ ... ]
private
{ Private declarations }
procedure AnEvent(Sender : TObject; DispID : Integer);
procedure AnotherEvent(Sender : TObject; DispID : Integer);
public
{ Public declarations }
Doc : IHtmlDocument3;
DocEvent,
DocEvent2: OleVariant;
Cookie : Longint;
CPC : IConnectionPointContainer;
Sink : IConnectionPoint;
PrvActiveElement : IHTMLElement;
Events : Integer;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TEventObject.Create(const AnEvent: TInvokeEvent; SinkAll : Boolean);
begin
inherited Create;
FOnEvent := AnEvent;
FSinkAllEvents := SinkAll;
end;
function TEventObject.GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TEventObject.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HResult;
begin
Result := E_NOTIMPL;
end;
function TEventObject.GetTypeInfoCount(out Count: Integer): HResult;
begin
Result := E_NOTIMPL;
end;
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
begin
if SinkAllEvents then begin
if Assigned(FOnEvent) then
FOnEvent(Self, DispID);
Result := S_OK;
end
else begin
if (Dispid = DISPID_VALUE) then begin
if Assigned(FOnEvent) then
FOnEvent(Self, DispID);
Result := S_OK;
end
else Result := E_NOTIMPL;
end;
end;
procedure TForm1.AnEvent(Sender : TObject; DispID : Integer);
var
Doc2 : IHTMLDocument2;
E : IHTMLElement;
begin
Inc(Events);
Doc.QueryInterface(IHTMLDocument2, Doc2);
E := Doc2.activeElement;
// NB: When an <INPUT> text edit is receiving focus, the following code is triggered twice
// or more with different values of Pointer(Doc2.activeElement). So, "(E <> PrvActiveElement)"
// doesn't seem a very effective test that the active element has changed. However,
// testing E's Name, ID, etc should provide a useful test.
if (E <> Nil) and (E <> PrvActiveElement) and E.isTextEdit then begin
if PrvActiveElement <> Nil then
PrvActiveElement := E;
Caption := Format('Something happened: Element Tagname: %s, Name: %s, %d, %d, %p',
[E.TagName, E.GetAttribute('Name', 0), DispID, Events, Pointer(Doc2.activeElement)]);
end;
end;
procedure TForm1.AnotherEvent(Sender : TObject; DispID : Integer);
begin
Caption := Format('Something else happened: %d', [DispID]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.LoadFromFile('D:\aaad7\html\postdata.htm');
end;
procedure TForm1.btnLoadClick(Sender: TObject);
var
V : OleVariant;
Doc2 : IHtmlDocument2;
begin
WebBrowser1.Navigate('about:blank');
Doc := WebBrowser1.Document as IHTMLDocument3;
Doc.QueryInterface(IHTMLDocument2, Doc2);
V := VarArrayCreate([0, 0], varVariant);
V[0] := Memo1.Lines.Text;
try
Doc2.Write(PSafeArray(TVarData(v).VArray));
finally
Doc2.Close;
end;
DocEvent := TEventObject.Create(Self.AnEvent, cbSinkAll.Checked) as IDispatch;
if cbsinkAll.Checked then begin
CPC := Doc2 as IConnectionPointContainer;
Assert(CPC <> Nil);
OleCheck(CPC.FindConnectionPoint(HTMLDocumentEvents, Sink));
OleCheck((Sink as IConnectionPoint).Advise(DocEvent, Cookie));
end
else
Doc.onbeforeeditfocus := DocEvent;
end;
обратите внимание на комментарии в TForm1.События. Если вы установите флажок cbSinkAll и запустите код на странице с несколькими полями ввода, вы заметите, что AnEvent срабатывает несколько раз при входе в то же самое коробка входного сигнала, с различным стоимость Doc2.ActiveElement каждый раз. Я не уверен, почему это так,но это означает, что сравнение текущего значение Doc2.Свойство ActiveElement с предыдущим значением не эффективно для обнаружения изменения в фокусе на Html странице. Однако сравнение атрибута элемента, например его имени или идентификатора, по-видимому, обеспечивает надежную проверку.
два вопроса:
- в исходном коде Деборы Пейт она сохраняет предыдущий обработчик событий (если есть) в OleVariant так что он может быть восстановлен позже.
- если вы хотите подключиться к событиям нескольких Html-страниц подряд, вы должны освободить EventObject между ними.
[извлечение из MSHTML.Pas]
HTMLDocumentEvents = dispinterface
['{3050F260-98B5-11CF-BB82-00AA00BDCE0B}']
function onhelp: WordBool; dispid -2147418102;
[...]
procedure onbeforeeditfocus; dispid 1027;
end;