如何在 TWebBrowser IDispatchEvent 中获取自定义事件参数

Mat*_*att 4 delphi mshtml delphi-2010 twebbrowser

我试图在 Javascript 和我的TWebBrowser. 在我的第一次迭代中,我能够注册属性更改事件,以便当标记更改时,Delphi 会获取更改,然后读取触发更改的标记的值。这样我们就可以让 Javascript 设置一个隐藏标签,Delphi 将接收更改,然后 Delphi 将读取隐藏标签以获取值。这工作正常,但感觉有点hacky。

在第二次迭代中,我尝试触发 Javascript 中引发的自定义事件。我能够使其正常工作,但我找不到一种方法来获取传递给自定义事件的参数。这是我创建的代码IDispatch

constructor TWebBrowserEvent.Create(const OnEvent: TCallback);
begin
   inherited Create;
   FOnEvent := OnEvent;
end;

function TWebBrowserEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
   Result := E_NOTIMPL;
end;

function TWebBrowserEvent.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
   Result := E_NOTIMPL;
end;

function TWebBrowserEvent.GetTypeInfoCount(out Count: Integer): HResult;
begin
   Result := E_NOTIMPL;
end;

function TWebBrowserEvent.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
var
   Parameters : TDispParams;
begin
   if (Dispid = DISPID_VALUE) then begin
      if Assigned(FOnEvent) then begin
         FOnEvent;
         Result := S_OK;
      end;
   end else begin
      Result := E_NOTIMPL;
   end;
end;
Run Code Online (Sandbox Code Playgroud)

然后为了注册我的自定义事件,我应用了以下代码:

procedure TWebBrowserWrapper.RegisterCustomEvent(EventName : String; CallbackFunction : TCallback);
var
   Target : IEventTarget;
begin
   Target := WebBrowser.Document as IEventTarget;
   Target.addEventListener(EventName, TWebBrowserEvent.Create(CallbackFunction) as IDispatch, true);
end;
Run Code Online (Sandbox Code Playgroud)

本质上我只是获取文档,将其转换为 aIEventTarget然后调用addEventListener. 我传递第一个参数的事件名称,然后IDispatch使用回调函数创建一个。我可以成功触发此事件,但我不知道如何获取传递到事件中的参数。这是我在 javascript 中触发的事件: 事件

我正在尝试details在 Delphi 中获取此事件的部分。我想也许调用函数可以将其参数转换为 a TDispParams,但当我尝试转换时,此类中的参数为空。

任何提示或答案将不胜感激。

Pet*_*olf 5

要在 Delphi 代码中访问detailof CustomEvent,您需要:

  1. IDOMEvent在您的事件侦听器中获取参考,
  2. IDOMCustomEvent从中得到参考,
  3. 使用后期绑定来浏览detail属性。

显然你在第一步失败了。您可以通过一种好方法实现事件侦听器IDispatch并将其作为 的第二个参数传递IEventTarget.addEventListener。此时,您希望在将事件分派到侦听器时根据文档接收一些参数:

listener[在]

类型:IDispatch

与事件关联的事件处理函数。请注意,事件处理函数本身需要两个参数 - 第一个是事件目标(即调用事件处理函数的对象),第二个是对象IDOMEvent

Params注册事件侦听器并在 HTML 中引发事件后,您发现方法中的参数没有收到任何值Invoke

TDispParams(Params) 的调试检查器

您不是第一个面临此问题的人,并且寻找根本原因只会产生很少的结果:

基于此,您的侦听器需要实现IDispatchEx,文档中没有提及。你只需要实现它的InvokeEx方法并忽略其余的。这是示例实现(我敢于将类重命名为TWebBrowserEventListener以更好地表达其目的):

uses
  System.SysUtils, Winapi.Windows, Winapi.ActiveX, MSHTML;

type
  THandleEvent = procedure(const Target: IDispatch; const DOMEvent: IDOMEvent) of object;

  TWebBrowserEventListener = class(TInterfacedObject, IDispatchEx)
  private
    FOnHandleEvent: THandleEvent;
    { IDispatch }
    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;
    { IDispatchEx }
    function GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
       out id: TDispID): HResult; stdcall;
    function InvokeEx(const id: TDispID; const lcid: LCID; const wflags:
       WORD; const pdp: PDispParams; out varRes: OleVariant; out pei:
       TExcepInfo; const pspCaller: PServiceProvider): HResult; stdcall;
    function DeleteMemberByName(const bstr: TBSTR;
       const grfdex: DWORD): HResult; stdcall;
    function DeleteMemberByDispID(const id: TDispID): HResult; stdcall;
    function GetMemberProperties(const id: TDispID; const grfdexFetch:
       DWORD; out grfdex: DWORD): HResult; stdcall;
    function GetMemberName(const id: TDispID; out bstrName: TBSTR):
       HResult; stdcall;
    function GetNextDispID(const grfdex: DWORD; const id: TDispID;
       out nid: TDispID): HResult; stdcall;
    function GetNameSpaceParent(out unk: IUnknown): HResult; stdcall;
  protected
    procedure HandleEvent(const Target: IDispatch; const DOMEvent: IDOMEvent); virtual;
  public
    constructor Create(AOnHandleEvent: THandleEvent);
  end;

constructor TWebBrowserEventListener.Create(AOnHandleEvent: THandleEvent);
begin
  inherited Create;
  FOnHandleEvent := AOnHandleEvent;
end;

function TWebBrowserEventListener.DeleteMemberByDispID(const id: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.DeleteMemberByName(const bstr: TBSTR;
  const grfdex: DWORD): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
  out id: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetMemberName(const id: TDispID;
  out bstrName: TBSTR): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetMemberProperties(const id: TDispID;
  const grfdexFetch: DWORD; out grfdex: DWORD): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetNameSpaceParent(out unk: IInterface): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetNextDispID(const grfdex: DWORD; const id: TDispID;
  out nid: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetTypeInfo(Index, LocaleID: Integer;
  out TypeInfo): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Result := E_NOTIMPL;
end;

procedure TWebBrowserEventListener.HandleEvent(const Target: IDispatch;
  const DOMEvent: IDOMEvent);
begin
  if Assigned(FOnHandleEvent) then
    FOnHandleEvent(Target, DOMEvent);
end;

function TWebBrowserEventListener.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.InvokeEx(const id: TDispID; const lcid: LCID;
  const wflags: WORD; const pdp: PDispParams; out varRes: OleVariant;
  out pei: TExcepInfo; const pspCaller: PServiceProvider): HResult;
var
  DOMEvent: IDOMEvent;
begin
  if (id = DISPID_VALUE) and (pdp^.cArgs = 2) and (pdp^.rgvarg^[0].vt = varDispatch) and
     (pdp^.rgvarg^[1].vt = varDispatch) and Supports(IDispatch(pdp^.rgvarg^[1].dispVal), IDOMEvent, DOMEvent) then
  begin
    HandleEvent(IDispatch(pdp^.rgvarg^[0].dispVal), DOMEvent);
    Result := S_OK;
  end
  else
    Result := E_NOTIMPL;
end;
Run Code Online (Sandbox Code Playgroud)

为了测试我是否将此 HTML 加载到 Web 浏览器控件中:

uses
  System.SysUtils, Winapi.Windows, Winapi.ActiveX, MSHTML;

type
  THandleEvent = procedure(const Target: IDispatch; const DOMEvent: IDOMEvent) of object;

  TWebBrowserEventListener = class(TInterfacedObject, IDispatchEx)
  private
    FOnHandleEvent: THandleEvent;
    { IDispatch }
    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;
    { IDispatchEx }
    function GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
       out id: TDispID): HResult; stdcall;
    function InvokeEx(const id: TDispID; const lcid: LCID; const wflags:
       WORD; const pdp: PDispParams; out varRes: OleVariant; out pei:
       TExcepInfo; const pspCaller: PServiceProvider): HResult; stdcall;
    function DeleteMemberByName(const bstr: TBSTR;
       const grfdex: DWORD): HResult; stdcall;
    function DeleteMemberByDispID(const id: TDispID): HResult; stdcall;
    function GetMemberProperties(const id: TDispID; const grfdexFetch:
       DWORD; out grfdex: DWORD): HResult; stdcall;
    function GetMemberName(const id: TDispID; out bstrName: TBSTR):
       HResult; stdcall;
    function GetNextDispID(const grfdex: DWORD; const id: TDispID;
       out nid: TDispID): HResult; stdcall;
    function GetNameSpaceParent(out unk: IUnknown): HResult; stdcall;
  protected
    procedure HandleEvent(const Target: IDispatch; const DOMEvent: IDOMEvent); virtual;
  public
    constructor Create(AOnHandleEvent: THandleEvent);
  end;

constructor TWebBrowserEventListener.Create(AOnHandleEvent: THandleEvent);
begin
  inherited Create;
  FOnHandleEvent := AOnHandleEvent;
end;

function TWebBrowserEventListener.DeleteMemberByDispID(const id: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.DeleteMemberByName(const bstr: TBSTR;
  const grfdex: DWORD): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetDispID(const bstrName: TBSTR; const grfdex: DWORD;
  out id: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetMemberName(const id: TDispID;
  out bstrName: TBSTR): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetMemberProperties(const id: TDispID;
  const grfdexFetch: DWORD; out grfdex: DWORD): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetNameSpaceParent(out unk: IInterface): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetNextDispID(const grfdex: DWORD; const id: TDispID;
  out nid: TDispID): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetTypeInfo(Index, LocaleID: Integer;
  out TypeInfo): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Result := E_NOTIMPL;
end;

procedure TWebBrowserEventListener.HandleEvent(const Target: IDispatch;
  const DOMEvent: IDOMEvent);
begin
  if Assigned(FOnHandleEvent) then
    FOnHandleEvent(Target, DOMEvent);
end;

function TWebBrowserEventListener.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
begin
  Result := E_NOTIMPL;
end;

function TWebBrowserEventListener.InvokeEx(const id: TDispID; const lcid: LCID;
  const wflags: WORD; const pdp: PDispParams; out varRes: OleVariant;
  out pei: TExcepInfo; const pspCaller: PServiceProvider): HResult;
var
  DOMEvent: IDOMEvent;
begin
  if (id = DISPID_VALUE) and (pdp^.cArgs = 2) and (pdp^.rgvarg^[0].vt = varDispatch) and
     (pdp^.rgvarg^[1].vt = varDispatch) and Supports(IDispatch(pdp^.rgvarg^[1].dispVal), IDOMEvent, DOMEvent) then
  begin
    HandleEvent(IDispatch(pdp^.rgvarg^[0].dispVal), DOMEvent);
    Result := S_OK;
  end
  else
    Result := E_NOTIMPL;
end;
Run Code Online (Sandbox Code Playgroud)

这就是我注册监听器的方式:

procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject;
  const pDisp: IDispatch; const URL: OleVariant);
var
  Target : IEventTarget;
  Listener: IDispatchEx;
begin
  Target := WebBrowser1.Document as IEventTarget;
  Listener := TWebBrowserEventListener.Create(WebBrowserEvent);
  Target.addEventListener('change', Listener, True);
  Target.addEventListener('pmweTest', Listener, True);
end;

procedure TForm1.WebBrowserEvent(const Target: IDispatch;
  const DOMEvent: IDOMEvent);
var
  EventInfo: string;
  DOMCustomEvent: IDOMCustomEvent;
begin
  EventInfo := 'Type: ' + DOMEvent.type_ + #13#10'SrcElement: ';
  if Assigned(DOMEvent.srcElement) then
  begin
    EventInfo := EventInfo + DOMEvent.srcElement.tagName;
    if DOMEvent.srcElement.id <> '' then
      EventInfo := EventInfo + '#' + DOMEvent.srcElement.id;
  end
  else
    EventInfo := EventInfo + '#document';
  if (DOMEvent.type_ = 'pmweTest') and Supports(DOMEvent, IDOMCustomEvent, DOMCustomEvent) then
    EventInfo := EventInfo + #13#10'detail.dataPackage: ' + VarToStr(DOMCustomEvent.detail.dataPackage);
  ShowMessage(EventInfo);
end;
Run Code Online (Sandbox Code Playgroud)

上面的代码侦听元素change上的事件<input>以及pmweTest通过单击按钮触发的自定义事件。两种类型使用相同的侦听器。

当您更改文本字段的值并将焦点移出该字段时,它会显示:

类型:更改
SrcElement:INPUT#input-text

当您单击复选框时:

类型:更改
SrcElement:INPUT#input-checkbox

当您单击按钮时:

类型:pmweTest
SrcElement:#
documentdetail.dataPackage:你好马特