检测TWebBrowser文档中的活动元素何时发生变化 [英] Detect when the active element in a TWebBrowser document changes

查看:123
本文介绍了检测TWebBrowser文档中的活动元素何时发生变化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有任何事件可以钩住以检测网页上的活动元素何时发生变化?例如,当用户聚焦编辑框时。



我知道我可以在定时器中检查活动元素,但是如果可能,我宁愿避免这种情况。 / p>

解决方案

这不是一个完整的答案,但希望能让你最了解你的方式。 / p>

(对于通过类似的q到达的未来读者:




  • 假设您有一个自动化/ Com服务器的类型库导入单元,如SHDocVw,MSHTML或MS Word。有时,Delphi的类型库导入器会为其生成的Delphi TObject-descendant包装器添加事件支持,如TWebBrowser的事件,OnNavigateComplete等。其他时候,它不能或不会生成Delphi包装器类,但是您仍然可以通过许多方法之一附加到服务器对象事件,例如通过创建如下所示的EventObject,它连接在服务器对象的事件和事件处理程序之间r Delphi代码


  • 处理接口事件基本上涉及定义一个Delphi类,它实现一个IDispatch接口,然后将该接口连接到Ole或COM对象,该对象的事件( s)你想得到通知。然后,当事件发生在Ole / COM后面的界面时,它会像你所说的那样调用你的IDispatch。您对事件通知的处理完全取决于您;下面的代码将它们传递给一个TForm1的方法。




下面的EventObject紧密依据2003年11月由Borland NGs发布的一个TeamB的Deborah Pate(她在她的网站上有一个非常好的部分,关于使用Delphi的自动化 - http://www.djpate .freeserve.co.uk / Automation.htm )。该对象是非常通用的,因为它不仅限于处理任何特定Ole / COM对象的事件。

  //以下代码旨在说明检测Html页面中的
//活动元素已更改的方法。请参阅AnEvent
//程序中关于如何准确检测到此类更改的注释。
//
//代码还说明了如何处理单个事件,例如onbeforeeditfocus
//的事件对象,如HtmlDocumentEvents或HtmlDocumentEvents2(请参阅MSHTML.Pas)
//或事件接口包含的所有事件。


类型

对象的TInvokeEvent =过程(Sender:TObject; DispIP:Integer)

TEventObject = class(TInterfacedObject,IDispatch)
private
FOnEvent:TInvokeEvent;
FSinkAllEvents:Boolean;
protected
function GetTypeInfoCount(out Count:Integer):HResult;标准
函数GetTypeInfo(Index,LocaleID:Integer; out TypeInfo):HResult;标准
函数GetIDsOfNames(const IID:TGUID; Names:Pointer;
NameCount,LocaleID:Integer; DispIDs:Pointer):HResult;标准
函数Invoke(DispID:Integer; const IID:TGUID; LocaleID:Integer;
标志:Word; var Params; VarResult,ExcepInfo,ArgErr:Pointer):HResult;标准
public
构造函数Create(const AnEvent:TInvokeEvent; SinkAll:Boolean);
属性OnEvent:TInvokeEvent读取FOnEvent写入FOnEvent;
属性SinkAllEvents:Boolean读取FSinkAllEvents;
结束

type
TForm1 = class(TForm)
[...]
private
{私有声明}
程序AnEvent(发件人: TObject; DispID:Integer);
procedure AnotherEvent(Sender:TObject; DispID:Integer);
public
{公开声明}
文件:IHtmlDocument3;
DocEvent,
DocEvent2:OleVariant;
Cookie:Longint;
CPC:IConnectionPointContainer;
Sink:IConnectionPoint;
PrvActiveElement:IHTMLElement;
事件:整数;
结束

var
Form1:TForm1;

实现

{$ R * .dfm}

构造函数TEventObject.Create(const AnEvent:TInvokeEvent; SinkAll:Boolean);
开始
继承创建;
FOnEvent:= AnEvent;
FSinkAllEvents:= SinkAll;
结束

函数TEventObject.GetIDsOfNames(const IID:TGUID; Names:Pointer;
NameCount,LocaleID:Integer; DispIDs:Pointer):HResult;
begin
结果:= E_NOTIMPL;
结束

函数TEventObject.GetTypeInfo(Index,LocaleID:Integer;
out TypeInfo):HResult;
begin
结果:= E_NOTIMPL;
结束

函数TEventObject.GetTypeInfoCount(out Count:Integer):HResult;
begin
结果:= E_NOTIMPL;
结束

函数TEventObject.Invoke(DispID:Integer; const IID:TGUID;
LocaleID:Integer; Flags:Word; var Params; VarResult,ExcepInfo,
ArgErr:Pointer):人力资源
begin
如果SinkAllEvents然后开始
如果分配(FOnEvent)然后
FOnEvent(Self,DispID);
结果:= S_OK;
end
else begin
如果(Dispid = DISPID_VALUE)然后开始
如果已分配(FOnEvent)然后
FOnEvent(Self,DispID);
结果:= S_OK;
end
else结果:= E_NOTIMPL;
结束
结束

procedure TForm1.AnEvent(Sender:TObject; DispID:Integer);
var
Doc2:IHTMLDocument2;
E:IHTMLElement;
begin
Inc(Events);
Doc.QueryInterface(IHTMLDocument2,Doc2);
E:= Doc2.activeElement;

// NB:当< INPUT>文本编辑正在接收焦点,以下代码被触发两次
//或更多与不同的指针值(Doc2.activeElement)。所以,(E - PrvActiveElement)
//似乎不是一个非常有效的测试,活动元素已经改变了。但是,
//测试E的Name,ID等应该提供一个有用的测试。

如果(E - Nil)和(E - PrvActiveElement)和E.isTextEdit然后开始
,如果PrvActiveElement<> Nil then
PrvActiveElement:= E;
标题:=格式('发生的事情:元素标记名:%s,名称:%s,%d,%d,%p',
[E.TagName,E.GetAttribute('Name' ,0),DispID,事件,指针(Doc2.activeElement)]);
结束
结束

procedure TForm1.AnotherEvent(Sender:TObject; DispID:Integer);
begin
Caption:= Format('Something else occurred:%d',[DispID]);
结束

procedure TForm1.FormCreate(Sender:TObject);
begin
Memo1.Lines.LoadFromFile('D:\aaad7\html\postdata.htm');
结束

程序TForm1.btnLoadClick(发件人: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;
结束

DocEvent:= TEventObject.Create(Self.AnEvent,cbSinkAll.Checked)作为IDispatch;

如果cbsinkAll.Checked然后开始
CPC:= Doc2作为IConnectionPointContainer;
Assert(CPC<> Nil);
OleCheck(CPC.FindConnectionPoint(HTMLDocumentEvents,Sink));
OleCheck((Sink as IConnectionPoint).Advise(DocEvent,Cookie));
end
else
Doc.onbeforeeditfocus:= DocEvent;
结束

请注意TForm1.AnEvent中的注释。如果您检查cbSinkAll复选框
并在具有多个INPUT框的页面上运行代码,则会注意到,在输入到相同的 INPUT框中,AnEvent会多次触发,其中包含每次不同的Doc2.ActiveElement值。我不知道为什么这样,但它的确意味着将Doc2.ActiveElement属性的当前
值与先前的值进行比较,无法在Html页面上检测到更改
。然而,比较元素的属性,例如,其名称或身份证件似乎提供了可靠的支票。



两个注意事项:




  • 在Deborah Pate的原始代码中,她将以前的事件处理程序(如果有的话)保存到OleVariant,以便稍后恢复。

  • 如果要连续连接几个Html页面的事件,应该在之间释放EventObject。



[从MSHTML.Pas中提取]

  HTMLDocumentEvents = dispinterface 
['{3050F260- 98B5-11CF-BB82-00AA00BDCE0B}']
函数onhelp:WordBool; dispid -2147418102;
[...]
procedure onbeforeeditfocus; dispid 1027;
结束


Is there any event I can hook into to detect when the active element on a web page changes? For example, when a the user focuses an edit box.

I know I could check the active element in a timer, but I'd rather avoid this if possible.

解决方案

This isn't - quite - a complete answer to your q, but hopefully will get you most of the way there.

(For future readers who arrive here via a similar q:

  • Suppose you have an type library import unit for an automation/Com server like SHDocVw, MSHTML or the one for MS Word. Sometimes, Delphi's type library importer adds event support to the Delphi TObject-descendant wrapper it generates, like the events for TWebBrowser, OnNavigateComplete, etc. Other times it can't or won't generate a Delphi wrapper class, but you can still attach to the server objects events by one of a number of methods, such as by creating an EventObject like the one below, which connects between a server object's events and an event-handler in your Delphi code.

  • Handling interface events basically involves defining a Delphi class which implements an IDispatch interface and then connecting that interface to the Ole or COM object whose event(s) you want to be notified about. Then, when events occur in the Ole/COM "behind" the interface it calls your IDispatch the same way you call its one. What you do with the event notifications is entirely up to you; the code below passes them on to a method of TForm1. )

The EventObject below is closely based on a one posted in the Borland NGs in November 2003 by Deborah Pate of TeamB (she has a really good section on her website about automation using Delphi - http://www.djpate.freeserve.co.uk/Automation.htm). The object is quite generic, in that it's not limited to handling the events of any particular Ole/COM object.

//  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;

Note the comments in TForm1.AnEvent. If you check the cbSinkAll checkbox and run the code on a page with a number of INPUT boxes, you'll notice that AnEvent fires several times on entry to the same INPUT box, with a different value of Doc2.ActiveElement each time. I'm not sure why that is,but it does mean that comparing the current value of the Doc2.ActiveElement property with a previous value isn't effective to detect a change in focus on the Html page. However, comparing an attribute of the element, e.g. its Name or ID, does seem to provide a reliable check.

Two caveats:

  • In Deborah Pate's original code, she saves the previous event handler (if any) to an OleVariant so that it could be reinstated later.
  • If you want to connect to the events of several Html pages in succession, you should free the EventObject in between.

[Extract from MSHTML.Pas]

  HTMLDocumentEvents = dispinterface
    ['{3050F260-98B5-11CF-BB82-00AA00BDCE0B}']
    function  onhelp: WordBool; dispid -2147418102;
    [...]
    procedure onbeforeeditfocus; dispid 1027;
  end;

这篇关于检测TWebBrowser文档中的活动元素何时发生变化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆