我知道我可以检查计时器中的活动元素,但如果可能的话我宁愿避免这种情况.
解决方法
(对于通过类似问题到达此处的未来读者:
>假设您有自动/ Com服务器的类型库导入单元,如SHDocVw,MSHTML或MS Word.有时,Delphi的类型库导入器为它生成的Delphi TObject-descendant包装器添加事件支持,比如TWebBrowser,OnNavigateComplete等的事件.有时它不能或不会生成Delphi包装类,但你仍然可以附加通过多种方法之一对服务器对象事件,例如通过创建如下所示的EventObject,它连接服务器对象的事件和Delphi代码中的事件处理程序.
>处理接口事件基本上涉及定义Delphi类,该类实现IDispatch接口,然后将该接口连接到要通知其事件的Ole或COM对象.然后,当Ole / COM“后面”接口发生事件时,它会调用您的IDispatch,就像调用它一样.您对事件通知的处理完全取决于您;下面的代码将它们传递给TForm1的方法.
)
下面的EventObject紧密基于2003年11月由TeamB的Deborah Pate在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,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,%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.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;