Определить, когда активный элемент в документе TWebBrowser изменяется

Есть ли какое-либо событие, которое я могу подключить, чтобы обнаружить изменение активного элемента на веб-странице? Например, когда пользователь фокусируется на поле редактирования.

Я знаю, что могу проверить активный элемент в таймере, но я бы предпочел избежать этого, если это возможно.

1 ответ

Решение

Это не совсем полный ответ на ваш вопрос, но, надеюсь, поможет вам в этом.

(Для будущих читателей, которые приезжают сюда через похожий вопрос:

  • Предположим, у вас есть модуль импорта библиотеки типов для сервера автоматизации /Com, такого как SHDocVw, MSHTML или для MS Word. Иногда импортер библиотек типов Delphi добавляет поддержку событий в создаваемую оболочку Delphi TObject-потомок, которую он генерирует, например события для TWebBrowser, OnNavigateComplete и т. Д. В других случаях он не может или не будет генерировать класс-оболочку Delphi, но вы все равно можете присоединить для сервера объекты событий одним из ряда методов, например, путем создания объекта EventObject, подобного приведенному ниже, который подключается между событиями объекта сервера и обработчиком событий в вашем коде Delphi.

  • Обработка событий интерфейса в основном включает определение класса Delphi, который реализует интерфейс IDispatch, а затем подключение этого интерфейса к объекту Ole или COM, о событиях которого вы хотите получать уведомления. Затем, когда события происходят в Ole/COM "позади" интерфейса, он вызывает ваш IDispatch так же, как вы его. Что вы делаете с уведомлениями о событиях, полностью зависит от вас; код ниже передает их методу TForm1.)

Приведенный ниже EventObject тесно связан с тем, который был опубликован в Borland NG в ноябре 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.AnEvent. Если вы установите флажок cbSinkAll и запустите код на странице с несколькими полями INPUT, вы заметите, что AnEvent запускается несколько раз при входе в один и тот же блок INPUT, каждый раз с другим значением Doc2.Active Element. Я не уверен, почему это так, но это действительно означает, что сравнение текущего значения свойства Doc2.Active Element с предыдущим значением не эффективно для обнаружения изменения фокуса на HTML-странице. Однако сравнение атрибута элемента, например его имени или идентификатора, действительно обеспечивает надежную проверку.

Два предостережения:

  • В исходном коде Деборы Пэйт она сохраняет предыдущий обработчик событий (если есть) в OleVariant, чтобы его можно было восстановить позже.
  • Если вы хотите подключиться к событиям нескольких HTML-страниц подряд, вы должны освободить EventObject между ними.

[Извлечение из MSHTML.Pas]

  HTMLDocumentEvents = dispinterface
    ['{3050F260-98B5-11CF-BB82-00AA00BDCE0B}']
    function  onhelp: WordBool; dispid -2147418102;
    [...]
    procedure onbeforeeditfocus; dispid 1027;
  end;
Другие вопросы по тегам