Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2012年02月03日] [更新 2020年05月20日] Delphi サンプルプログラム集
   
943_IE オブジェクトのイベントシンク
動作確認等 indows 7 U64(SP1) + Delphi XE Pro + IE 8.0 (一部 Delphi 10.3.3 Community) 
TEmbeddedWB Version 14.69.1 - 23.11.2009
943_IEObject_EventsSink.zip [5,819 KB] 2020年05月20日版 (EXE 同梱)







IE オブジェクトのイベントを捕える


ブラウザで表示しているウェブページのクリック動作等を捕える,検出するサンプルプログラムです.ブラウザと言っても,対象は IE (InternetExplorer) オブジェクトだけです.

[941_ウェブページのテキスト入力とボタンクリック等] では,ウェブページのボタンクリック等をコードで実行するテストをしましたが,このページのサンプルはこの逆となります.ボタン等がクリックされたのを検出します.
[941_ウェブページのテキスト入力とボタンクリック等] はフレームに対応していませんが,本ページには,フレームに対する操作の参考となるコードがあります.

ウェブページのイベントを捕える仕組みをイベントシンクと呼んでいます.イベントシンクを実装するには,各種のインターフェイスを駆使しなければなりません.そこで,ここでは [942_TEmbeddedWB コンポーネント] で紹介した TEmbeddedWB コンポーネントをインストールすると,その中に含まれる 2 つのコンポーネントを利用してみました.これらのコンポーネントは,TEmbeddedWB コンポーネントとは独立して利用可能となっています.本ページのサンプル,テストプログラムのプロジェクトでは, が必要です.TShell を使用しないで IE を起動するコードは,[940_TWebBrowser と IE] を参考にしてください.

イベントシンクの対象にできるオブジェクトについては,MSHTML Event DispInterfaces を,また各々のオブジェクトで使用可能なイベントは,例えば HTMLDocumentEvents Dispinterface を参考にしてください.このページのサンプルでは,イベントシンクの対象として,HTMLDocumentEvents だけを扱っています.

本ページには,ボタンクリックイベント処理の具体的なサンプルはありませんが,クリックした場所の HTML 要素のタグ名や name,Id 等の属性で,そのクリック位置の要素がボタンであるかを判定することができます.

以下のサンプルは,Delphi 側でイベントを検出するものです.開発者自身が HTML コードを作成するのであれば,JavaScript を使用してイベントの処理を行うウェブページを作成することができます.





01_MSHTMLEvents を使用 - InternetExplorer の場合


uses 部に  MSHTMLEvents を追加して,実行時に生成して使用します.
[IE 起動]ボタンをクリックすると,IE を新規起動します.その IE でマウスボタンをクリックするとその位置のエレメントのタグ名とテキスト,文字け列を表示します.もし,その文字列にリンクが貼ってあればそのリンク先の URL も表示します.

このサンプルコードは.フレームのページには対応していません.

リスト1
MSHTMLEvents.pas の内容
イベントの対象オブジェクトごとのクラスがある
ここでは TMSHTMLHTMLDocumentEvents だけを使用
{ *****************************************************************************
  WARNING: This component file was generated using the EventSinkImp utility.
           The contents of this file will be overwritten everytime EventSinkImp
           is asked to regenerate this sink component.

  NOTE:    When using this component at the same time with the XXX_TLB.pas in
           your Delphi projects, make sure you always put the XXX_TLB unit name
           AFTER this component unit name in the USES clause of the interface
           section of your unit; otherwise you may get interface conflict
           errors from the Delphi compiler.

           EventSinkImp is written by Binh Ly (bly@techvanguards.com)
  *****************************************************************************
  //Sink Classes//
  TMSHTMLHTMLElementEvents
  TMSHTMLHTMLElementEvents2
  TMSHTMLHTMLLinkElementEvents
  TMSHTMLHTMLLinkElementEvents2
  TMSHTMLHTMLFormElementEvents
  TMSHTMLHTMLFormElementEvents2
  TMSHTMLHTMLImgEvents
  TMSHTMLHTMLImgEvents2
  TMSHTMLHTMLTextContainerEvents
  TMSHTMLHTMLTextContainerEvents2
  TMSHTMLHTMLAnchorEvents
  TMSHTMLHTMLAnchorEvents2
  TMSHTMLHTMLLabelEvents
  TMSHTMLHTMLLabelEvents2
  TMSHTMLHTMLSelectElementEvents
  TMSHTMLHTMLSelectElementEvents2
  TMSHTMLHTMLInputTextElementEvents
  TMSHTMLHTMLInputTextElementEvents2
  TMSHTMLHTMLOptionButtonElementEvents
  TMSHTMLHTMLButtonElementEvents
  TMSHTMLHTMLButtonElementEvents2
  TMSHTMLHTMLMarqueeElementEvents
  TMSHTMLHTMLMarqueeElementEvents2
  TMSHTMLHTMLWindowEvents
  TMSHTMLHTMLWindowEvents2
  TMSHTMLHTMLDocumentEvents
  TMSHTMLHTMLDocumentEvents2
  TMSHTMLDWebBridgeEvents
  TMSHTMLHTMLControlElementEvents
  TMSHTMLHTMLControlElementEvents2
  TMSHTMLHTMLMapEvents
  TMSHTMLHTMLMapEvents2
  TMSHTMLHTMLAreaEvents
  TMSHTMLHTMLAreaEvents2
  TMSHTMLHTMLTableEvents
  TMSHTMLHTMLTableEvents2
  TMSHTMLHTMLScriptEvents
  TMSHTMLHTMLScriptEvents2
  TMSHTMLHTMLObjectElementEvents
  TMSHTMLHTMLObjectElementEvents2
  TMSHTMLHTMLFrameSiteEvents
  TMSHTMLHTMLFrameSiteEvents2
  TMSHTMLHTMLStyleElementEvents
  TMSHTMLHTMLStyleElementEvents2
  TMSHTMLHTMLNamespaceEvents
  TMSHTMLHTMLInputFileElementEvents
  TMSHTMLHTMLInputImageEvents
}

{$IFDEF VER100}
{$DEFINE D3}
{$ENDIF}

{$IFDEF CONDITIONALEXPRESSIONS}
{$WARN SYMBOL_PLATFORM OFF}
{$ENDIF}

//SinkUnitName//
unit MSHTMLEvents;

interface

uses
  Windows, ActiveX, Classes, ComObj, OleCtrls
  //SinkUses//
  , StdVCL
  , MSHTML_EWB
  ;


このテストプログラムでは,uses 部に,MSHTMLEvents, MSHTML_EWB, SHDocVw_EWB が必要ですが,注意があります.それは,MSHTMLEvents を必ず MSHTML_EWB, SHDocVw_EWB よりも前に書くことです.


  • 図1
  • MSHTMLEvents を,必ず MSHTML_EWB と SHDocVw_EWB よりも前に書く

図2
設計時画面
図3
実行時画面
  • [IE 起動]ボタンをクリック

下図は,ブラウザでクリックしたテキストを表示したところです.


図4
クリック例

リスト2
SHTMLEvents を使用したイベントの検出サンプル
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, MSHTMLEvents, MSHTML_EWB, SHDocVw_EWB,
  OleServer, Shell32_TLB;

type
  TForm1 = class(TForm)
    Shell1: TShell;
    Button1: TButton;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    Memo2: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FSinkComponent : TMSHTMLHTMLDocumentEvents;
  public
    { Public 宣言 }
    function DocOnClick(Sender: TObject): WordBool;
    function DocSelectStart(Sender: TObject): WordBool;
  end;

var
  Form1: TForm1;

implementation

uses ShellAPI, ActiveX;

{$R *.dfm}

//=============================================================================
//  [IE起動]ボタン
//  ウェブページを表示して,イベントシンク用コンポーネント生成
//  IEは新規起動
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AppStr    : String;
  AppCmd    : String;
  ShWindows : ShellWindows;
  IECount   : Integer;
  IEObj     : InternetExplorer;
  iDoc2     : IHTMLDocument2;
begin
  //シェルウインドウのオブジェクトを取得したら,現在のウィンドウ数を取得しておく
  //この数が1つ増加したら,新規起動完了とみなすため
  ShWindows := Shell1.Windows as ShellWindows;
  IECount   := ShWindows.Count;

  //IE起動.IEは関連付けのあるアプリなのでフルパスでなくても起動可能
  AppStr := 'IEXPLORE.EXE';
  AppCmd := 'http://mrxray.on.coocan.jp/';
  ShellExecute(Handle, 'open', PChar(AppStr), PChar(AppCmd), nil, SW_SHOW);

  //起動中のシェルウィンドウの数が増加したら新規起動完了とみなす
  while ShWindows.Count <= IECount do begin
    Application.ProcessMessages;
  end;

  //そのウィンドウの内,最後に起動したIEのオブジェクトを取得
  IEObj := ShWindows.Item(ShWindows.Count - 1) as InternetExplorer;
  //表示完了まで待つ
  while IEObj.ReadyState < READYSTATE_INTERACTIVE do begin
    Application.ProcessMessages;
  end;

  //------ IE 起動終了 --------------------------------------------------------

  //イベントシンクコンポーネントを生成
  //ここでは,TMSHTMLHTMLDocumentEventsをテストする
  FSinkComponent := TMSHTMLHTMLDocumentEvents.Create(Self);

  //OnSelectStartとOnClickイベントを定義
  FSinkComponent.onselectstart := DocSelectStart;
  FSinkComponent.OnClick       := DocOnClick;

  //イベントシンク接続
  //一応QueryInterfaceでチェック
  iDoc2 := IEObj.Document as IHTMLDocument2;
  if Succeeded(IEObj.Document.QueryInterface(IID_IHTMLDocument2, iDoc2)) then begin
    FSinkComponent.Connect(iDoc2);
  end;
end;

//=============================================================================
//  OnClickイベント処理用の関数
//=============================================================================
function TForm1.DocOnClick(Sender: TObject): WordBool;
var
  iElement : IHTMLElement;
  iDoc2    : IHTMLDocument2;
begin
  Result := False;

  LabeledEdit1.Text := '';
  LabeledEdit2.Text := '';
  Memo2.Lines.Text  := '';

  //SenderはTMSHTMLHTMLDocumentEventsコンポ
  iDoc2 := TMSHTMLHTMLDocumentEvents(Sender).Source as IHTMLDocument2;

  if iDoc2 <> nil then begin
    iElement := iDoc2.activeElement;

    //リンクを設定してあるエレメントはそのリンクも検出するために親とする
    if iElement.parentElement.tagName = 'A' then begin
      iElement := iElement.parentElement;
    end;

    //tag属性を表示
    LabeledEdit1.Text := iElement.tagName;

    //href属性を表示
    if iElement.getAttribute( 'href', 0) <> null then begin
      LabeledEdit2.Text := iElement.getAttribute( 'href', 0);
    end;

    //エレメントのソースを表示
    Memo2.Text := iElement.innerText;
    Result := True;
  end;
end;

//=============================================================================
//  OnSelectStartイベント処理用の関数
//=============================================================================
function TForm1.DocSelectStart(Sender: TObject): WordBool;
var
  iDoc2 : IHTMLDocument2;
begin
  Result := True;

  //SenderはTMSHTMLHTMLDocumentEventsコンポ
  iDoc2 :=  TMSHTMLHTMLDocumentEvents(Sender).Source as IHTMLDocument2;

  if iDoc2 <> nil then begin
    Result := False;
    Memo2.Lines.Text := '選択操作を禁止しています.';
  end;
end;


end.




02_THtmlListener コンポーネント  -  InternetExplorer の場合


THtmlListener (HtmlListener HTML リスナー)コンポーネントを利用したサンプルです.前のサンプルではフレームで構成されたページには対応していませんが,こちらは対応コードを追加しています.

IE を新規起動して,そのページのイベントを検出します.表示ページが変更となった時の検出はしていいません.したがって,ページが変更となったら[リスナー接続] ボタンをクリックして,イベントシンクを再構成します.


図5
設計時画面

THtmlListener コンポーネントの設定方法です.


図6
まず SinkKind を選択
  • ここでは,skDocument とした

オブジェクトインスペクタの [Handers] をダブルクリックして,編集ダイアログを表示します.
ここで,追加ボタンで項目を追加します.これらの項目のプロパティの EventID から,検出するイベントを選択します.


図7
項目のイベントを選択
  • このサンプルでは以下を追加
    OnMouseMove
    OnClick

項目のプロパティの [イベント] タブで,イベントハンドラを作成します.


図8
イベントハンドラ作成
  • 他のコンポーネント同様,ダブルクリックでデフォルトのメソッド名が作成できる

以上の設定で実行した結果です.
[IE 起動] をクリックして,IE が起動したら,[リスナー接続] ボタンをクリックします.


図9
IE を起動
  • マウスカーソル位置のエレメントの情報を表示

ブラウザで表示しているページのテキスト,文字列を選択して,[選択テキスト]  ボタンをクリックすると,その文字列をダイアログで表示します.


図10
選択テキスト

リスト3
THtmlListener コンポーネントを使用したイベントの検出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, Buttons, EwbEvents, MSHTML_EWB,
  SHDocVw_EWB, OleServer, Shell32_TLB, EwbEventsComp;

type
  TForm1 = class(TForm)
    HtmlListener1: THtmlListener;
    Shell1: TShell;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    LabeledEdit3: TLabeledEdit;
    Memo2: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure HtmlListener1HandlersOnMouseMoveHandle(Sender: TObject;
      Event: IHTMLEventObj);
    procedure HtmlListener1HandlersOnClickHandle(Sender: TObject;
      Event: IHTMLEventObj);
  private
    { Private 宣言 }
    HubLinksList : TInterfaceList;
    HubLink      : IHubLink;
    IEObj        : InternetExplorer;
    procedure ConnectHtmlListeners;
    procedure ConnectHtmlListener(ASender: TObject; Doc: IHTMLDocument2);
    procedure DisconnectHtmlListeners;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ShellAPI, ActiveX;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  TEmbededdWBコンポのGetFrameメソッドの代用関数
//  元のIHTMLDocument2からIWebbrowser2オブジェクトを取得
//
//  usesにMSHTML_EWB, SHDocVw_EWB, ActiveXが必要
//  TEmbededdWBコンポを使用しない場合は,MSHTML, SHDocVw, ActiveXでもよい
//-----------------------------------------------------------------------------
function GetFrameFromID(ADoc2: IHTMLDocument2; FrameNo: Word): IWebbrowser2;
var
  OleContainer : IOleContainer;
  Enum         : ActiveX.IEnumUnknown;
  Unk          : IUnknown;
  Fetched      : PLongint;
begin
  Result := nil;
  if Assigned(ADoc2) then begin
    Fetched := nil;
    OleContainer := ADoc2 as IOleContainer;
    OleContainer.EnumObjects(OLECONTF_EMBEDDINGS, Enum);
    Enum.Skip(FrameNo);
    Enum.Next(1, Unk, Fetched);
    if Supports(Unk, IWebBrowser2, Result) then begin
      Result := Unk as IWebbrowser2;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  TEmbededdWBコンポのGetActiveFrameメソッドの代用関数
//  アクティブなフレームのIHTMLDocument2オブジェクトを求める
//
//  usesにMSHTML_EWB, SHDocVw_EWB, ActiveXが必要
//  TEmbededdWBコンポを使用しない場合は,MSHTML, SHDocVw, ActiveXでもよい
//-----------------------------------------------------------------------------
function GetActiveFrameDoc2(Doc: IHTMLDocument2): IHTMLDocument2;

  //---------------------------------------------------------------------------
  //  実際の処理
  //  フレームはネストしている場合がほとんどなので再帰処理
  //---------------------------------------------------------------------------
  function GetActiveFrame(Doc: IHTMLDocument2): IHTMLDocument2;
  var
    Element      : IHTMLElement;
    Frame        : IHTMLFrameElement;
    WB           : IWebBrowser2;
    HTMLDocument : IHTMLDocument2;
    HtmlDoc2     : IHTMLDocument2;
  begin
    Result := nil;
    if Supports(Doc, IHTMLDocument2, HtmlDoc2) then begin
      Element := Doc.activeElement;
      if Assigned(Element) then begin
        if Element.QueryInterface(IHTMLFrameElement, Frame) = S_OK then begin
          if Frame.QueryInterface(IID_IWebBrowser2, WB) = S_OK then begin
            if Assigned(WB.Document) then begin
              if WB.Document.QueryInterface(IID_IHTMLDocument2,
                                            HTMLDocument) = S_OK then begin
                Result := GetActiveFrame(HTMLDocument);
              end;
            end;
          end;
        end;
      end;
    end;
    if Result = nil then Result := doc;
  end;
  //---------------------------------------------------------------------------

var
  HtmlDoc2: IHTMLDocument2;
begin
  if Supports(Doc, IHTMLDocument2, HtmlDoc2) then
    Result := GetActiveFrame(HtmlDoc2);
end;

//=============================================================================
//  フォームを生成したら,リスナー管理用のリストを作成しておく
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  HubLinksList := TInterfaceList.Create;
end;

//=============================================================================
//  リスナーの解放処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  DisconnectHtmlListeners;
  HubLinksList.Free;
end;

//=============================================================================
//  [IE起動]ボタン
//  ウェブページを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AppStr    : String;
  AppCmd    : String;
  ShWindows : ShellWindows;
  IECount   : Integer;
begin
  //シェルウインドウのオブジェクトを取得したら,現在のウィンドウ数を取得しておく
  //この数が1つ増加したら,新規起動完了とみなすため
  ShWindows := Shell1.Windows as ShellWindows;
  IECount   := ShWindows.Count;

  //IE起動.IEは関連付けのあるアプリなのでフルパスでなくても起動可能
  AppStr := 'IEXPLORE.EXE';
  AppCmd := 'http://ht-deko.com/Delphi/';
  ShellExecute(Handle, 'open', PChar(AppStr), PChar(AppCmd), nil, SW_SHOW);

  //起動中のシェルウィンドウの数が増加したら新規起動完了とみなす
  while ShWindows.Count <= IECount do begin
    Application.ProcessMessages;
  end;

  //そのウィンドウの内,最後に起動したIEのオブジェクトを取得
  IEObj := ShWindows.Item(ShWindows.Count - 1) as InternetExplorer;
end;

//=============================================================================
//  [選択テキスト]ボタン
//  クリックすると,現在選択中のテキストの文字列を表示
//  このコードはイベントシンク(リスナー)を登録しなくても動作する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  iDoc2   : IHTMLDocument2;
  HTMLDoc : IHTMLDocument2;
  StrText : String;
begin
  iDoc2   := IEObj.Document as IHTMLDocument2;
  HTMLDoc := GetActiveFrameDoc2(iDoc2);

  if iDoc2 <> nil then begin
    StrText := (HTMLDoc.selection.createRange as IHTMLTxtRange).Text;
    MessageBox(Handle, PChar(StrText), '選択テキスト', MB_OK);
 end;
end;

//=============================================================================
//  [リスナー接続]ボタン
//  表示ウェブページを変更したらこのボタンをクリックして,リスナーを再登録する
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
begin
  DisconnectHtmlListeners;
  ConnectHtmlListeners;
end;

//=============================================================================
//  THtmlListener(リスナー)の接続用関数
//  表示フレーム分の接続処理をしてリスト(TInterfaceList)に追加しておく
//  フレームはネストしている場合がほとんどなので再帰処理としている
//
//  表示ページを変更した時はこの関数を実行してリスナーを再登録する
//
//  HubLink(IHubLink)の使用にはusesにEwbEventsが必要
//=============================================================================
procedure TForm1.ConnectHtmlListeners;

  //---------------------------------------------------------------------------
  //  フレームの接続の実際の処理(再帰処理)
  //---------------------------------------------------------------------------
  procedure SetHTMListnerAllFrames(ADoc: IHTMLDocument2);
  var
    i    : Integer;
    WB   : IWebbrowser2;
    iDoc : IHTMLDocument2;
  begin
    for i := 0 to ADoc.frames.length - 1 do  begin
      WB := GetFrameFromID(ADoc, i);
      if Assigned(WB) then begin
        if Succeeded(WB.Document.QueryInterface(IHTMLDocument2, iDoc)) then begin
          //このiDoc(IHTMLDocument2オブジェクト)が実際のフレームのドキュメント
          //リスナーの接続処理.このフレームで発生したイベントが取得可能となる
          //iDoc.body.innerHTML;でこのフレームのinnerHTMLのソースが取得できる
          ConnectHtmlListener(nil, iDoc);
          //ネストしたフレーム用に再帰処理
          SetHTMListnerAllFrames(iDoc);
        end;
      end;
    end;
  end;
  //---------------------------------------------------------------------------

var
  iDoc2 : IHTMLDocument2;
begin
  if not Assigned(IEObj) then exit;

  iDoc2 := IEObj.Document as IHTMLDocument2;
  //フレームページではないメインのページの接続
  ConnectHtmlListener(nil, iDoc2);
  //フレームの接続処理
  SetHTMListnerAllFrames(iDoc2);
end;

//=============================================================================
//  THtmlListener(リスナー)の接続用関数
//  イベントシンクは,フレームページ(IHTMLDocument2オブジェクト)ごとに必要なの
//  で,表示フレーム数の分だけこの関数を実行して,リスト(TInterfaceList)に追加
//  しておく
//
//  TEmbeddedWBコンポであれば,フレーム表示のたびにOnDocumentCompleteイベント
//  が発生するので,そのイベント内で処理可能
//=============================================================================
procedure TForm1.ConnectHtmlListener(ASender: TObject; Doc: IHTMLDocument2);
var
  CPC : IConnectionPointContainer;
begin
  if not Assigned(Doc) then exit;

  Doc.QueryInterface(IConnectionPointContainer, CPC);
  if Assigned(CPC) then begin
    HubLink := HtmlListener1.Connect2(CPC);
    HubLinksList.Add(HubLink);
  end;
end;

//=============================================================================
//  THtmlListener(リスナー)の切断処理用の関数
//=============================================================================
procedure TForm1.DisconnectHtmlListeners;
var
  i: Integer;
begin
  for i := HubLinksList.Count - 1 downto 0 do begin
    (HubLinksList[i] as IHubLink).Disconnect;
    HubLinksList.Remove(HubLinksList[i]);
  end;
end;

//=============================================================================
//  OnMouseMoveイベント処理
//  マウスカーソル下のエレメント(要素)の情報を表示
//
//  THtmlListnerのHandlesプロパティのEventID=eiOnMouseMoveのOnHadleイベント
//  THtmlListnerのSinkKindプロパティはskDocument
//=============================================================================
procedure TForm1.HtmlListener1HandlersOnMouseMoveHandle(Sender: TObject;
  Event: IHTMLEventObj);
var
  iElement : IHTMLElement;
begin
  iElement := Event.srcElement;

  LabeledEdit1.Text := '';
  LabeledEdit2.Text := '';
  Memo2.Lines.Text  := '';

  Memo2.Lines.Text := iElement.innerHTML;

  if iElement.getAttribute( 'src', 0) <> null then begin
    LabeledEdit1.Text := iElement.getAttribute( 'src', 0);
  end;

  //リンクを設定してあるエレメントはそのリンクも検出するために親とする
  if iElement.parentElement.tagName = 'A' then begin
    iElement := iElement.parentElement;
  end;

  if iElement.getAttribute( 'href', 0) <> null then begin
    LabeledEdit2.Text := iElement.getAttribute( 'href', 0);
  end;
end;

//=============================================================================
//  OnClickイベント処理
//  マウスクリックしたクライアント座標値を表示
//
//  THtmlListnerのHandlesプロパティのEventID=eiOnClickのOnHadleイベント
//  THtmlListnerのSinkKindプロパティはskDocument
//=============================================================================
procedure TForm1.HtmlListener1HandlersOnClickHandle(Sender: TObject;
  Event: IHTMLEventObj);
begin
  LabeledEdit3.Text := 'X: '  + IntToStr(Event.x) + '  ' +
                       'Y: '  + IntToStr(Event.y);
end;

end.




03_THtmlListener コンポーネント  -  TEmbeddedWB の場合


今度は,IE ではなく,TEmbeddedWB コンポーネントにウェブページを表示してのテストです.
動作内容は前のサンプルと同じですが,TEmbeddedWB コンポーネントはドキュメント表示前と読み込む際のイベントがあります.このイベントハンドラでリスナーの登録を自動的に行えます.

なお,このサンプルプログラムは,リンク先は全てこのウィンドウに表示します.ポップアップメニューで,[新しいタブで開く] または [新しいウィンドウで開く] を選択しても無効としています.また,ページ移動のボタンは実装していませんので,[Back Space] あるいは [Shift] + [back Space] を使用してください.

TEmbeddedWB コンポーネントの [08 - Elements_Under_Mouse_Demo] と [15 - THTMLListener_Demo] のデモプログラムも参考にしてください (下図のハイライト部のフォルダ).


図11
TEmbeddedWB デモプログラム

  • 図12
  • 設計時画面

  • 図13
  • 実行画面
  • マウスカーソル位置の情報を表示
  • [click point] はマウスクリックの座標値の表示用


TEmbeddedWB コンポーネントは,Delphi に実装されている TWebBrowser に比べてかなり多くの機能が追加されています.例えば,選択中のテキストは,GetSelectedText (旧バージョンでは GetSelText) で簡単に取得できます.
もちろんフレームで構成されているページにも対応しています.


リスト4
選択中のテキスト (文字列) を取得する
procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := EmbeddedWB1.GetSelectedText;
  MessageBox(Handle, PChar(StrText), '選択テキスト', MB_OK);
end;


  • 図14
  • 選択中のテキスト (文字列) を表示

リスト5
THtmlListener コンポーネントを使用したイベントの検出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, Buttons, EwbEvents, MSHTML_EWB,
  SHDocVw_EWB, EwbCore, EmbeddedWB, EwbEventsComp;

type

  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    LabeledEdit1: TLabeledEdit;
    LabeledEdit2: TLabeledEdit;
    LabeledEdit3: TLabeledEdit;
    Memo2: TMemo;
    EmbeddedWB1: TEmbeddedWB;
    HtmlListener1: THtmlListener;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure HtmlListener1HandlersOnMouseMoveHandle(Sender: TObject;
      Event: IHTMLEventObj);
    procedure EmbeddedWB1BeforeNavigate2(ASender: TObject;
      const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
      Headers: OleVariant; var Cancel: WordBool);
    procedure EmbeddedWB1DocumentComplete(ASender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure HtmlListener1HandlersOnClickHandle(Sender: TObject;
      Event: IHTMLEventObj);
    procedure EmbeddedWB1NewWindow3(ASender: TObject; var ppDisp: IDispatch;
      var Cancel: WordBool; dwFlags: Cardinal; const bstrUrlContext,
      bstrUrl: WideString);
  private
    { Private 宣言 }
    HubLinksList: TInterfaceList; //イベントのトラップに必要
    HubLink: IHubLink;
    procedure ConnectHtmlListener(ASender: TObject; Doc: IHTMLDocument2);
    procedure DisconnectHtmlListeners;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ActiveX;

{$R *.dfm}

//=============================================================================
//  フォームを生成したら,リスナー管理用のリストを作成しておく
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  HubLinksList := TInterfaceList.Create;
end;

//=============================================================================
//  リスナーの解放処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  DisconnectHtmlListeners;
  HubLinksList.Free;
end;

//=============================================================================
//  [ページ表示]ボタン
//  ウェブページを表示
//
//  [注意]
//  TEmbeddedWBでMSHtMLの機能を使用する場合は,MSHTML_EWBを利用する(usesに追加)
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL : String;
begin
  //指定のURLを表示
  AURL := 'http://ht-deko.com/Delphi/';
  EmbeddedWB1.Navigate(AURL);
end;

//=============================================================================
//  [選択テキスト]ボタン
//  クリックすると,現在選択中のテキストの文字列を表示
//  このコードはイベントシンク(リスナー)を登録しなくても動作する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  StrText : String;
begin
  StrText := EmbeddedWB1.GetSelectedText;
  MessageBox(Handle, PChar(StrText), '選択テキスト', MB_OK);
end;

//=============================================================================
//  TEmbeddedWBコンポのOnBeforeNavigate2イベント
//  THtmlListener(リスナー)の接続を切断
//=============================================================================
procedure TForm1.EmbeddedWB1BeforeNavigate2(ASender: TObject;
  const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  Headers: OleVariant; var Cancel: WordBool);
begin
  if pDisp = EmbeddedWB1.DefaultInterface then begin
    DisconnectHtmlListeners;
  end;
end;

//=============================================================================
//  TEmbeddedWBコンポのOnDocumentCompleteイベント
//  表示フレーム数だけここを通過するのでリスナーの接続処理をその分実行
//=============================================================================
procedure TForm1.EmbeddedWB1DocumentComplete(ASender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var
  WB      : IWebbrowser;
  HTMLDoc : IHTMLDocument2;
begin
  WB := (pDisp as IWebBrowser);
  if Assigned(WB) then begin
    if Succeeded(WB.Document.QueryInterface(IHTMLDocument2, HTMLDoc)) then begin
      ConnectHtmlListener(ASender, HTMLDoc);
    end;
  end;
end;

//=============================================================================
//  TEmbeddedWBコンポのOnNewWindow3イベント
//  テストなので,リンク先のページは別ウィンドウで開かずに,EmbeddedWB1で表示
//=============================================================================
procedure TForm1.EmbeddedWB1NewWindow3(ASender: TObject; var ppDisp: IDispatch;
  var Cancel: WordBool; dwFlags: Cardinal; const bstrUrlContext,
  bstrUrl: WideString);
begin
  Cancel := True;
  EmbeddedWB1.Navigate(bstrUrl);
end;

//=============================================================================
//  THtmlListener(リスナー)の接続用関数
//=============================================================================
procedure TForm1.ConnectHtmlListener(ASender: TObject; Doc: IHTMLDocument2);
var
  CPC : IConnectionPointContainer;
begin
  if Assigned(Doc) then begin
    Doc.QueryInterface(IConnectionPointContainer, CPC);
    if Assigned(CPC) then begin
      HubLink := HtmlListener1.Connect2(CPC);
      HubLinksList.Add(HubLink);
    end;
  end;
end;

//=============================================================================
//  THtmlListener(リスナー)の切断処理用の関数
//=============================================================================
procedure TForm1.DisconnectHtmlListeners;
var
  i : Integer;
begin
  for i := HubLinksList.Count - 1 downto 0 do begin
    (HubLinksList[i] as IHubLink).Disconnect;
    HubLinksList.Remove(HubLinksList[i]);
  end;
end;

//=============================================================================
//  OnMouseMoveイベント処理
//  マウスカーソル下のエレメント(要素)の情報を表示
//
//  THtmlListnerのHandlesプロパティのEventID=eiOnMouseMoveのOnHadleイベント
//  THtmlListnerのSinkKindプロパティはskDocument
//=============================================================================
procedure TForm1.HtmlListener1HandlersOnMouseMoveHandle(Sender: TObject;
  Event: IHTMLEventObj);
var
  iElement : IHTMLElement;
begin
  iElement := Event.srcElement;

  LabeledEdit1.Text := '';
  LabeledEdit2.Text := '';
  Memo2.Lines.Text  := '';

  Memo2.Lines.Text := iElement.innerHTML;

  if iElement.getAttribute( 'src', 0) <> null then begin
    LabeledEdit1.Text := iElement.getAttribute( 'src', 0);
  end;

  //リンクを設定してあるエレメントはそのリンクも検出するために親とする
  if iElement.parentElement.tagName = 'A' then begin
    iElement := iElement.parentElement;
  end;

  if iElement.getAttribute( 'href', 0) <> null then begin
    LabeledEdit2.Text := iElement.getAttribute( 'href', 0);
  end;
end;

//=============================================================================
//  OnClickイベント処理
//  マウスクリックしたクライアント座標値を表示
//
//  THtmlListnerのHandlesプロパティのEventID=eiOnClickのOnHadleイベント
//  THtmlListnerのSinkKindプロパティはskDocument
//=============================================================================
procedure TForm1.HtmlListener1HandlersOnClickHandle(Sender: TObject;
  Event: IHTMLEventObj);
begin
  LabeledEdit3.Text := 'X: '  + IntToStr(Event.x) + '  ' +
                       'Y: '  + IntToStr(Event.y);
end;

end.




04_クリックイベント - TEmbeddedWB 専用


TEmbeddedWB コンポーネントは多くのイベントを実装しています.
この内,OnClick イベントのテストです.このサンプルもリンク先のページを,この TEmbeddesWB で表示するようにしています.
本サンプルは,フレームのページには対応していません.

マウスボタンの操作は,TApplicationEvents を使用して検出することができます.TApplicationEvents であれば,TWebBrowser でも利用可能です.[942] [02_TEmbeddedWB コンポーネントの簡単な使用例] に具体的なコードがあります.



図15
設計時画面
図16
実行画面
  • クリックした位置のテキスト (文字列) を表示するだけ

リスト6
OnClick イベントのテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, Buttons, SHDocVw_EWB, EwbCore,
  EmbeddedWB;

type

  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Memo2: TMemo;
    EmbeddedWB1: TEmbeddedWB;
    procedure Button1Click(Sender: TObject);
    procedure EmbeddedWB1NewWindow3(ASender: TObject; var ppDisp: IDispatch;
      var Cancel: WordBool; dwFlags: Cardinal; const bstrUrlContext,
      bstrUrl: WideString);
    procedure EmbeddedWB1Click(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses MSHTML_EWB;

{$R *.dfm}

//=============================================================================
//  ウェブページを表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AURL : String;
begin
  //指定のURLを表示
  AURL := 'http://mrxray.on.coocan.jp/';
  EmbeddedWB1.Navigate(AURL);
end;

//=============================================================================
//  TEmbeddedWBコンポのOnNewWindow3イベント
//  テストなので,リンク先のページは別ウィンドウで開かずに,EmbeddedWB1で表示
//=============================================================================
procedure TForm1.EmbeddedWB1NewWindow3(ASender: TObject; var ppDisp: IDispatch;
  var Cancel: WordBool; dwFlags: Cardinal; const bstrUrlContext,
  bstrUrl: WideString);
begin
  Cancel := True;
  EmbeddedWB1.Navigate(bstrUrl);
end;

//=============================================================================
//  OnClickイベント
//  TEmbeddedWBコンポのOnClickイベントハンドラ
//=============================================================================
procedure TForm1.EmbeddedWB1Click(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  iElement : IHTMLElement;
begin
  //マウス位置のエレメントを取得
  iElement  := EmbeddedWB1.Doc2.elementFromPoint(X, Y);

  //エレメントのソースを表示
  if iElement <> nil then begin
    Memo2.Lines.Text := iElement.innerHTML;
  end else begin
    Memo2.Lines.Text := '';
  end;
end;

end.




05_window.external を使用して JavaScript の実行結果を取得


JavaScript の window オブジェクトの external を使用すると,HTML コード内から Delphi のメソッドを実行する機能を実装できます.TEmbeddedWB コンポーネントには,window.external で Delphi のメソッドを実行するための機能が実装されています.
本サンプルは,TEmbeddedWB コンポーネントを使用して,JavaScript の関数の実行結果を Delphi 側で取得する例です.JavaScript の関数の実行をイベントとして捕捉する例と言えます.

これまでのサンプルで使用している THtmlListener コンポーネントのリスナーを使用する方法とは違い,実行結果を取得するJavaScritp にコードを追加する必要があります.したがって,本サンプルの方法は,開発者が HTML コードを変更できる場合に限ります.

本サンプルは,以下のリンクの記事を元にして作成しました.



テストに使用している HTML コードです.ブラウザに表示した input タグのボタンをクリックすると JavaScript の関数を実行します.JavaScript の関数では,window.external.GetResult で Delphi の GetResult 関数を実行します.
GetResult は,TObjectDispatch のメソッド (関数) として実装しています.



リスト7
テスト用の HTML コード.ボタンクリックで JavaScript の関数 testScript を実行する
JavaScript の関数は Delphi のメソッドを実行るようになっている
<!DOCTYPE html>
<html>
<head>
<script>
  function testScript(msg) {
    IntVal = 1230;
    window.external.GetResult(msg, IntVal);
  }
</script>
</head>

<body
<form action="" method="post" enctype="" name="delact">
  <input type="button" name="test1" value="Script 実行" onclick="testScript('TEST')">
</form>
</body>
</html>


window.extrnal を実行することによって結果を受け取るプログラムのコードです.
JavaScript 内で window.external.GetResult を実行すると.結果を受け取る Delphi 側の関数が実行されるので,実質的にイベントと同じ機能になります.GetResult が Delphi 側の関数名です.

JavaScript の結果を受け取るイベントとしての関数は,クラスを宣言してそのクラスのメソッドとして実装します. クラス名は任意です.このクラスのインスタンスは,TEmbeddedWB.OnGetExternal イベント内で TObjectDispatch.Create 関数の引数として生成します.関数名も,関数の引数の数も型も任意です.



図17
実行結果
  • ブラウザに表示したボタンをクリック
  • JavaScript が実行される
  • ダイアログは JavaScript の実行結果

リスト8
JavaScript の結果を イベントとして取得する機能を実装したサンプル
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, Buttons, SHDocVw_EWB, EwbCore, EmbeddedWB;

type
{$METHODINFO ON}
  TJavascriptReceiver = class
    function GetResult(msg: string; AValue: Integer): string;
  end;
{$METHODINFO OFF}


  TForm1 = class(TForm)
    Panel1: TPanel;
    EmbeddedWB1: TEmbeddedWB;
    procedure FormCreate(Sender: TObject);
    procedure EmbeddedWB1GetExternal(Sender: TCustomEmbeddedWB;
      var ppDispatch: IDispatch);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ObjComAuto;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用の HTML コードを読み込んで表示
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LURL : string;
begin
  LURL := ExtractFilePath(Application.ExeName) + 'JaveScriptTest.html';
  EmbeddedWB1.Navigate(LURL);
end;

//=============================================================================
//  EmbeddedWB1 の OnGetExternal イベント
//  JavaScript で window.external を使用可能 (機能するよう) にする
//
//  TObjectDispatch の使用には uses に ObjComAuto が必要
//=============================================================================
procedure TForm1.EmbeddedWB1GetExternal(Sender: TCustomEmbeddedWB;
  var ppDispatch: IDispatch);
begin
  ppDispatch := TObjectDispatch.Create(TJavascriptReceiver.Create);
end;

{ TJavascriptReceiver }

//-----------------------------------------------------------------------------
//  JavaScript 内から window.external で呼び出す関数
//  この関数の場合は window.external.GetResult(引数...) とする
//
//  関数は必要に応じて追加する
//  関数名と引数の数と型は任意
//-----------------------------------------------------------------------------
function TJavascriptReceiver.GetResult(msg: string; AValue: Integer): string;
var
  LText : string;
begin
  Result := '';

  LText := msg + sLineBreak + IntToStr(AValue);
  MessageBox(Form1.Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.


JavaScript の実行結果を Delphi 側で取得した後,取得した値を処理して JavaScrip 側で取得することもできます.関数の戻り値が JavaScript が受け取る値となります.



図18
Delphi での処理結果を JavaScrip 側で受け取った例

リスト9
Delphi から値を取得する JavaScript の関数の例
  function testScript(msg) {
    IntVal = 1230;
    ValfromDelphi = window.external.GetResult(msg, IntVal);
    alert(ValfromDelphi);
  }


リスト10
関数の実行結果である Result の値を JavaScript 側で取得できる
function TJavascriptReceiver.GetResult(msg: string; AValue: Integer): string;
begin
  Result := 'テスト文字列';
end;


JavaScript の配列の値を取得するには,配列の引数の型を OleVariant 型にします.
配列要素の各要素の値は GetDispatchPropValue 関数で取得できます.GetDispatchPropValue 関数を使用するには uses に ComObj が必要です.



図19
JavaScript の配列の値を Delphi 側で取得

リスト11
配列を使用した JavaScript の関数の例
  function testScript(msg) {
    var testarray = [123, 'ABC'];
    window.external.GetResult(msg, testarray);
  }


リスト12
JavaScript 側の配列の値を取得
function TJavascriptReceiver.GetResult(msg: string; AArray: OleVariant): string;
var
  LText  : string;
begin
  Result := '';

  // JavaScript の配列の値を取得
  // 要素番号の指定は文字列を使用する
  // GetDispatchPropValue の使用には uese に ComObj が必要
  LText := '要素数 : ' + IntToStr(AArray.length) + sLineBreak
         + IntToStr(GetDispatchPropValue(AArray, '0')) + sLineBreak
         + GetDispatchPropValue(AArray, '1');

  MessageBox(Form1.Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


JavaScript の配列はオブジェクト型です.オブジェクト型の引数は参照渡しになります.したがって,Delphi 側から JavaScript 側に値を返すことができます.その例です.
引数の型は OleVariant 型にしておきます.SetDispatchPropValue 手続きで配列要素に値をセットできます.SetDispatchPropValue を使用するには uses に ComObj が必要です. 



図20
Delphi で変更した配列の値を JavaScript 側で表示

リスト13
Delphi 側で変更した配列の値を表示する JavaScript の関数例
  function testScript(msg) {
    var testarray = [123, 'ABC'];
    window.external.GetResult(msg, testarray);

    for (var i = 0; i < testarray.length; i++){
      alert(testarray[i]);    
    }
  }


リスト14
JavaScript 側から受け取った配列の要素の値を変更.JavaScript 側の配列要素の値も変更される
function TJavascriptReceiver.GetResult(msg: string; AArray: OleVariant): OleVariant;
var
  LText  : string;
begin
  Result := '';

  // JavaScript に配列の値を返す
  // 要素番号の指定は文字列を使用する
  // SetDispatchPropValue の使用には uese に ComObj が必要
  SetDispatchPropValue(AArray, '0', 5200);
  SetDispatchPropValue(AArray, '1', 'テスト文字列');
end;




06_TWebBrowser で window.external を使用可能にする方法


JavaScript の window オブジェクトの external を使用すると,HTML コード内から Delphi のメソッドを実行する機能を実装できます.
本項の記事は,Delphi 10 Seattle 以降の TWebBrowser で,window.external で Delphi のメソッドを実行する機能を実装する方法です.

TWebBrowser で Delphi のメソッドを実行する機能を使用可能にするには IDocHostUIHandle インターフェイスを実装しなければなりません.このインターフェイスは,Delphi (Delphi 10 Seattle 以降には実装されていますが,window.external で Delphi のメソッドを実行する機能が無効になっています.このインターフェイスのコードを変更して window.external で Delphi のメソッドを使用できるようにする方法です.
今回は Delphi 10.3.3 Community を使用して動作確認しました.


Delphi のインストール先の [source] [internet] フォルダ内の SHDocVw.pas ファイルを,プロジェクトファイルがあるフォルダ内にコピーします.
現在のプロジェクトの TWebBrowser の部分を [Ctrl] + クリックで SHDocVw.pas 内の TWebBrowser の部分にジャンプします.private 部に次のプロパティ用変数を追加します.


  private
    FExternalObject: IDispatch;


public 部にプロパティ ExternalObject を追加します.


    property ExternalObject: IDispatch read FExternalObject write FExternalObject;


GetExternal 関数の実装コードを以下のように変更します.
これで SHDocVw.pas の変更は終了です.


function TWebBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT;
begin
  ppDispatch := nil;
  Result := E_FAIL;
  if Assigned(FExternalObject) then begin
    try
      ppDispatch := FExternalObject;
      Result := S_OK;
    except
    end;
  end;

//  ppDispatch := nil;
//  Result := E_FAIL;
end;


変更した SHDocVw.pas を使用したサンプルコードと実行結果です.
HTML コートは,前項の最初のテストで使用した JaveScriptTest.html を使用しています.



図21
実行結果

リスト15
JavaScript 内で Delphi のメソッド実行を可能にした TWebBrowser のテスト
Delphi 10 Seattle 以降用
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.OleCtrls, SHDocVw,
  Vcl.ExtCtrls;

type
{$METHODINFO ON}
  TJavascriptReceiver = class
   function GetResult(msg: string; AValue: Integer): string;
  end;
{$METHODINFO OFF}


  TForm1 = class(TForm)
    Panel1: TPanel;
    WebBrowser1: TWebBrowser;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ObjComAuto,
  ComObj;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用の HTML コードを読み込んで表示
//
//  uses に ObjComAuto, ComObj が必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LURL : string;
begin
  // TWebBrowser.ExternalObject オブジェクトの生成
  // JavaScript で window.external を使用可能 (機能するよう) にする
  WebBrowser1.ExternalObject := TObjectDispatch.Create(TJavascriptReceiver.Create);

  LURL := ExtractFilePath(Application.ExeName) + 'JaveScriptTest.html';
  WebBrowser1.Navigate(LURL);
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  WebBrowser1.ExternalObject := Unassigned;
end;



//-----------------------------------------------------------------------------
//  JavaScript 内から window.external で呼び出す関数
//  この関数の場合は window.external.GetResult(引数...) とする
//
//  関数は必要に応じて追加する
//  関数名と引数の数と型は任意
//-----------------------------------------------------------------------------
function TJavascriptReceiver.GetResult(msg: string; AValue: Integer): string;
var
  LText : string;
begin
  Result := '';

  LText := msg + sLineBreak + IntToStr(AValue);
  MessageBox(Form1.Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.

end.




07_window.external を使用可能にする IDocHostUIHandle の実装


JavaScript の window オブジェクトの external を使用すると,HTML コード内から Delphi のメソッドを実行する機能を実装できます.
Delphi XE8 以前の Delphi の TWebBrowser コンポーネントには IDocHostUIHandle インターフェイスが組み込まれていません,そのため,window.external で Delphi のメソッドを実行する機能を利用することができません.


// Delphi XE8 以前の TWebBrowser
TWebBrowser = class(TOleControl)

// Delphi 10 Seattle 以降の TWebBrowser
TWebBrowser = class(TOleControl, IDocHostUIHandler, IDocHostShowUI, IOleCommandTarget)

このようなンインターフェイスは,[ファイル] [新規作成] [その他] のダイアログから [ActiveX] [タイプライブラリ] を選択して作成しますが,マイクロソフトの資料との格闘が予想されます.以下のリンクのページから作成済みユニットファイルを入手することができます.本サンプルは,このユニット類を利用したサンプルです.
[Part 6] のページから demo code をダウンロードします.



ダウンロードしたらブロックを解除して解凍展開します.解凍展開したら,[Phase1] フォルダ内にある以下の 3 つのユニットファイルをライブラリバスの通ったフォルダ内にコピーします.


// 以下のファイルをライブラリパスの通ったフォルダ内に置く
IntfDocHostUIHandler.pas
UContainer.pas    // 要編集ユニット(uses して使用するユニット)
UNulContainer.pas


TWebBrowser を配置したフォームのユニットの uses 部に UContainer を追加します.追加したら,このユニットファイルを開きます.TWBContainer 宣言部に,ExternalObject プロパティと GetExternal メソッドを追加します.

IDocHostUIHandle を実装した (組み込んだ) TWebBrowser として VCL のコードを変更することはできません.そこで,IDocHostUIHandle を実装した TWBContainer というコンテナクラスを宣言して,実行時にこのクラスのインスタンスを,TWebBrowser を親にして生成して使用します.


  TWBContainer = class(TNulWBContainer, IDocHostUIHandler, IOleClientSite)
  private
    //
    FExternalObject: IDispatch;
  protected
    //
  public
    //
    function GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
    //
    property ExternalObject: IDispatch read FExternalObject write FExternalObject;
  end;


GetExternal 関数の実装コードを,以下のように,前項の SHDocVw.pas の場合と同じにします..
これで UContainer.pas の変更は終了です.


function TWBContainer.GetExternal(out ppDispatch: IDispatch): HRESULT;
begin
  ppDispatch := nil;
  Result := E_FAIL;
  if Assigned(FExternalObject) then begin
    try
      ppDispatch := FExternalObject;
      Result := S_OK;
    except
    end;
  end;
end;


IDocHostUIHandle を実装して,window.external で Delphi のメソッドを実行可能にしたTWebBrowser の使用例です.
HTML コートは,前項のテストでも使用した JaveScriptTest.html を使用しています.



図22
実行結果

リスト16
JavaScript 内で Delphi のメソッド実行を可能にした TWebBrowser のテスト
Delphi XE8 以前用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, ExtCtrls, UContainer;

type
{$METHODINFO ON}
  TJavascriptReceiver = class
   function GetResult(msg: string; AValue: Integer): string;
  end;
{$METHODINFO OFF}

  TForm1 = class(TForm)
    Panel1: TPanel;
    WebBrowser1: TWebBrowser;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    FWBContainer : TWBContainer;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ObjComAuto,
  ComObj;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//
//  (1) TWBContainer (UContainer.pas) のインスタンスを生成
//  (2) TWBContainer.ExternalObject を生成
//  (3) テスト用の HTML コードを読み込んで表示
//
//  uses に UContainer, ObjComAuto, ComObj が必要
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LURL : string;
begin
 FWBContainer := TWBContainer.Create(WebBrowser1);

  // TWBContainer.ExternalObject オブジェクトの生成
  // JavaScript で window.external を使用可能 (機能するよう) にする
  FWBContainer.ExternalObject := TObjectDispatch.Create(TJavascriptReceiver.Create);

  LURL := ExtractFilePath(Application.ExeName) + 'JaveScriptTest.html';
  FWBContainer.HostedBrowser.Navigate(LURL);
  // 以下でもよい
  // WebBrowser1.Navigate(LURL);
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FWBContainer.ExternalObject := Unassigned;
  FWBContainer.Free;
end;

//-----------------------------------------------------------------------------
//  JavaScript 内から window.external で呼び出す関数
//  この関数の場合は window.external.GetResult(引数...) とする
//
//  関数は必要に応じて追加する
//  関数名と引数の数と型は任意
//-----------------------------------------------------------------------------
function TJavascriptReceiver.GetResult(msg: string; AValue: Integer): string;
var
  LText : string;
begin
  Result := '';

  LText := msg + sLineBreak + IntToStr(AValue);
  MessageBox(Form1.Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;

end.




08_window.external を使用したマウスイベントの捕捉


JavaScript の window オブジェクトの external を使用すると,HTML コード内から Delphi のメソッドを実行する機能を実装できます.
上のいくつかのサンプルで,この window.external で Delphi のメソッドを実行する機能を実装する方法を確認してきました. 本サンプルは,window.external で Delphi のメソッドを実行する機能を使用してマウスイベントを捕捉する例です.

次の HTML では,マウスの移動またはクリックイベントが発生したら JavaScript の関数を実行するよにしています.JavaScript の関数は Delphi のメソッドを実行して,Delphi のプログラムにマウスイベントを知らせます.



リスト17
テストで使用した HTML コード.マウスイベントで JavaScript の関数を実行
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
  function mousemove(event){
    window.external.HtmlMouseMove(event);
  }
  function mouseclick(event){
    window.external.HtmlClick(event);
  }
</script>
</head>     

<body
  <div id="element1" onmousemove="mousemove(event);"
                     onclick="mouseclick(event);"
    > 
    マウスイベントテスト用<br>
    マウスカーソル移動とクリック<br>
    要素上の移動と要素上のクリックを検出
  </div>
</body>
</html>


上の HTML コードを TEmbeddedWB ブラウザコンポーネントで表示して,マウスイベントを受け取る側のコード例です.
TWebBrowser を使用する場合は,前項までの window.external 関係記事を参考にしてください.



図23
マウスイベントの捕捉
  • クリックするとダイアログを表示
  • フォーム上部の TPanel にマウスカーソルの位置を表示
  • 下部のテキストを表示していない部分ではイベントは発生しない

リスト18
window.external によってマウスイベントの通知を受け取る例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, OleCtrls, Buttons, SHDocVw_EWB, EwbCore, EmbeddedWB;

type
{$METHODINFO ON}
  TJavascriptReceiver = class
    procedure HtmlMouseMove(Aevent: OleVariant);
    procedure HtmlClick(Aevent: OleVariant);
  end;
{$METHODINFO OFF}


  TForm1 = class(TForm)
    Panel1: TPanel;
    EmbeddedWB1: TEmbeddedWB;
    Panel2: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure EmbeddedWB1GetExternal(Sender: TCustomEmbeddedWB;
      var ppDispatch: IDispatch);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ObjComAuto;

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//  テスト用の HTML コードを読み込んで表示
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LURL : string;
begin
  LURL := ExtractFilePath(Application.ExeName) + 'EventtTest.html';
  EmbeddedWB1.Navigate(LURL);
end;

//=============================================================================
//  EmbeddedWB1 の OnGetExternal イベント
//  JavaScript で window.external を使用可能 (機能するよう) にする
//
//  TObjectDispatch の使用には uses に ObjComAuto が必要
//=============================================================================
procedure TForm1.EmbeddedWB1GetExternal(Sender: TCustomEmbeddedWB;
  var ppDispatch: IDispatch);
begin
  ppDispatch := TObjectDispatch.Create(TJavascriptReceiver.Create);
end;

{ TJavascriptReceiver }

//-----------------------------------------------------------------------------
//  JavaScript 内から window.external で呼び出すメソッド
//  メソッド名と引数の数と型は任意
//-----------------------------------------------------------------------------
procedure TJavascriptReceiver.HtmlMouseMove(Aevent: OleVariant);
var
  LText : string;
begin
  LText := IntToStr(Aevent.clientX) + ', ' + IntToStr(Aevent.clientY);
  Form1.Panel2.Caption := LText;
end;

//-----------------------------------------------------------------------------
//  JavaScript 内から window.external で呼び出すメソッド
//  メソッド名と引数の数と型は任意
//-----------------------------------------------------------------------------
procedure TJavascriptReceiver.HtmlClick(Aevent: OleVariant);
begin
  MessageBox(Form1.Handle, 'クリック', '情報', MB_ICONINFORMATION);
end;

end.


上のサンプルで使用した HTML では,マウスイベントの発生で JavaScript の関数を実行しています.
JavaScript の関数を実行すると各種の処理が可能になって便利ですが,以下は,マウスイベントが発生したら直接 Delphi のメソッドを実行する HTML コードの例です.
window.external の window は省略できるので省略しています.これまでのサンプルても window は省略できます.



リスト19
<!DOCTYPE html>
<html>
<body>
  <div id="element1" onmousemove="external.HtmlMouseMove(event);"
                     onclick="external.HtmlClick(event);"
    > 
    マウスイベントテスト用<br>
    マウスカーソル移動とクリック<br>
    要素上の移動と要素上のクリックを検出
  </div>
</body>
</html>


マウスイベントを BODY タグ内で捕捉する HTML のコード例です.



リスト20
<!DOCTYPE html>
<html>
<body onmousemove="external.HtmlMouseMove(event);"
      onclick="external.HtmlClick(event);"
      > 
    マウスイベントテスト用<br>
    マウスカーソル移動とクリック<br>
    要素上の移動と要素上のクリックを検出
</body>
</html>


ブラウザコントロールで表示している画像をクリックしたら,Delphi のメソッドを実行する例です.メソッドの引数の名前,引数の数と型は任意ですから,この例のように,Delphi 側に追加の情報を渡すこともできます.
"" は,単に空白であることを明示的にするために使用しているだけです.



図24
画像をクリック

リスト21
<!DOCTYPE html>
<html>
<body>
  <img id="img01" src="testimage.gif"
   "" onclick="external.HtmlClick(event, id);"
   > 
</body>
</html>


リスト22
procedure TJavascriptReceiver.HtmlClick(Aevent: OleVariant; AID: string);
begin
  MessageBox(0, PChar(AID), '情報', MB_ICONINFORMATION);
end;


[備考]
マウスイベントの引数は省略できます.引数を指定すると,最初の引数は event オブジェクトとなります.引数の名前は任意です.