【问题标题】:Detect when the active element in a TWebBrowser document changes检测 TWebBrowser 文档中的活动元素何时更改
【发布时间】:2014-10-21 01:13:00
【问题描述】:

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

我知道我可以检查计时器中的活动元素,但如果可能的话,我宁愿避免这样做。

【问题讨论】:

  • html 文档是否在您的控制之下?
  • @whosdaddy - 是的,我可以访问 IHTMLDocumentx
  • 不,我指的是实际网页?
  • 您可以尝试将“propertyChangeListeners”添加到元素中。但我不确定焦点变化是否会触发这些。
  • @whosdaddy - 不,我无权访问该网页

标签: delphi twebbrowser delphi-xe6


【解决方案1】:

这不是 - 完全 - 对您的问题的完整答案,但希望能帮助您完成大部分工作。

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

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

  • 处理接口事件基本上涉及定义一个 Delphi 类,该类实现 IDispatch 接口,然后将该接口连接到您想要通知其事件的 Ole 或 COM 对象。然后,当事件发生在接口“后面”的 Ole/COM 中时,它调用 IDispatch 的方式与调用它的方式相同。您如何处理事件通知完全取决于您;下面的代码将它们传递给 TForm1 的方法。 )

下面的 EventObject 与 TeamB 的 Deborah Pate 于 2003 年 11 月在 Borland NGs 上发布的一个非常相似(她在她的网站上有一个关于使用 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 中的 cmets。如果您选中 cbSinkAll 复选框 并在带有多个 INPUT 框的页面上运行代码,您会注意到 AnEvent 在进入 same INPUT 框时触发了多次,每次都有不同的 Doc2.ActiveElement 值。我不确定为什么会这样,但这确实意味着比较当前 具有先前值的 Doc2.ActiveElement 属性的值对检测更改无效 专注于 Html 页面。但是,比较元素的属性,例如它的名称或 ID,似乎确实提供了可靠的检查。

两个警告:

  • 在 Deborah Pate 的原始代码中,她将先前的事件处理程序(如果有)保存到 OleVariant 以便以后可以恢复。
  • 如果你想连续连接几个Html页面的事件,你应该释放其间的EventObject。

[从 MSHTML.Pas 中提取]

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

【讨论】:

  • 如果您只想为单个元素(而不是整个文档)处理事件,您可以为给定的元素事件(由@987654327 提供)分配事件接收器(此处为TEventObject 对象) @ 界面)。 this chapter 中描述的所有内容。附言我不会亲自将 sink 对象存储在 OleVariant 变量中。我会直接使用IDispatch。 [+1ed]
  • @TLama:确实(关于单个事件)。感谢您的圣人 cmets/建议。正如您可能已经猜到的那样,我决定今天是尝试掌握自动化事件的日子(我仍然很惊讶 Bihn Ly 在 D2/D3 时代解决了所有这些问题)。我同意绕过 OleVariant,但是很多提问者(这里不是指 OP)似乎使用后期绑定并尽可能避免使用接口。
  • 我再次更新了答案,将重点放在了 ConnectionPoint 的使用上,顺便说一下,它更短了。
  • +1。也许向TEventObject.Create 添加一个标志以指示您是要处理所有事件(通过IConnectionPoint)还是每个DispID 事件?我还将Params... 等传递给回调方法(在这种情况下不确定LocaleIDFlags)。另见here
  • @kobik:谢谢和很好的建议,我会在进入抛光阶段时添加。同时,我仍在尝试查看是否有一种方法可以可靠地捕获活动 HTML 元素的变化,这就是我认为 q 的意思 - 正如我当前版本 Doc2.ActiveElement 中提到的那样,连续报告了几个不同的值输入相同的 INPUT 元素,这可能会破坏 OP 使用计时器检查它的最初想法。
猜你喜欢
  • 1970-01-01
  • 2020-10-02
  • 2021-07-20
  • 2020-07-19
  • 2012-10-15
  • 2016-01-30
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
相关资源
最近更新 更多