Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年03月02日] [更新 2018年05月20日] Delphi サンプルプログラム集
   
934_Google Chrome ブラウザコントロール CEF4Delphi
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
CEF4Delphi 3.3359.1768
934_GoogleChrome_CEF4Delphi.zip [786 KB] 2018年05月20日版 (EXEの添付なし)




  • 2018年05月05日
  • CEF4Delphi 3.3359.1768 を使用した内容の記事に変更,修正




HTML5 対応 Chrome ブラウザコントロール CEF4Delphi


本ページは Google Chrom ブラウザコンポーネント CEF4Delphi の紹介記事です.
使用した CEF4Delphi のバージョン (リビジョン) は 3.3359.1768 です.Chromium 66.0.3359.117 相当となっています.FireMonkey でも使用できます.今回は Delphi XE(UP1) Pro にインストールしてテストしています.
ちなみに筆者が使用している Google Chromlum のバージョンは 66.0.3359.139 です.


[備考 1]
本記事を掲載した 2017年03月02日の時の CEF4Delphi のリビジョンは 3.2924.1569 でした.添付のデモプログラムも 4 つしかありませんでしたが,約 1 年後の 2018年05月01日の更新時のリビジョンは 3.3359.1768 となっていて,多くのデモプログラムが添付されています.

[備考 2]
2018年05月01日の時点では,CEF4Delphi は CEF ライブラリの更新に伴い頻繁に更新れています.頻繁に更新されているということは仕様変更の可能性もあると言えます.
本ページに掲載しているサンプルコードは,ページの先頭に記載しいるリビジョンでの動作確認であることに注意してください.

[備考 3]
EXE の実行には,EXE を作成した時と同じリビジョンの CEF のライブラリが必要となってしまいます.そのため,本ページに掲載しているサンプルには EXE ファイルは添付していません.


以下のページに Microsoft Edge 相当の機能Eを実装するためのコンポーネントの記事を掲載しています.





01_CEF4Delphi と CEF ライブラリのダウンロード


CEF4Delphi コンポーネントを利用するには,CEF4Delphi コンポーネントと CEF ライブラリの両方をダウンロードします.
共に以下のページからダウンロードできます.


図1
CEF4Delphi のダウンロード

CEF4Delphi をダウンロードしたら,同じページにある下図ようなリンクをクリックして CEF のライブラリもダウンロードします.32 ビットの EXE で使用する場合は 32 ビット用のを,64 ビットの EXE で使用する場合は 64 ビット用のライブラリをダウンロードします.


図2
CEF ライブラリのダウンロード
  • CEF4Delphi とペアでダウンロードする

ダウンロードしたら,両方の圧縮ファイルを解凍展開します.
CEF のライブラリの解凍展開は少し時間がかかります.




02_CEF4Delphi と同じバージョンの CEF のライブラリが必要


必要な CEF のライブラリは,CEF4Delphi.md に記述してあるリンクからダウンロードできますが,必要であれば以下のリンクから古いバージョンがダウンロードできます.[Standard Distribution] をダウンロードします.
ライブラリのバージョンは,CEF4Delphi が使用している CEF のバージョンと同じものが必要です.異なる CEF のバージョンのライブラリを使用すると正常に動作しません.
2018年05月05日現在,入手可能な最も古いリビジョンは 05/26/2016 - CEF 3.2704.1414.g185cd6c / Chromium 51.0.2704.47 となっています.






03_CEF4Delphi のインストール


まず,[source] フォルダにライブラリのパスを通します.
次に [source] フォルダにある CEF4Delphi.dproj を IDE で開き,[インストール] して,[上書き保存] します.これでインストールは終了です.コンポーネントパレットの [Chrominum] タブにコンポーネントが登録されます.

不要になった場合は,CEF4Delphi.dproj を IDE で開き,[アンインストール] を実行し,[クリーンアップ] を実行します.CEF4Delphi をバージョンアップする時も,古いバージョンの CEF4Delphi.dproj を を IDE で開き,同じようにアンインストールしてから新しいバージョンの CEF4Delphi をインストールします.
[クリーンアップ] を実行してもファイルは残ったままです.必要であけば削除します.



図3
インストール
  • [インストール] をクリック
  • [上書き保存] をクリック
図4
インストール終了




04_CEF4Delphi における DLL 等のパスと言語とロケールの設定


ダウンロードした CEF ライブラリを解凍展開すると,下図のような構成となっています.
この内,[Release] と [Resources] フォルダ内の cef_sandbox.lib と libcef.lib を除く全てのフォルダとファイルが開発時にも実行時にも必要です.これらのファイルとフォルダはシステム的にパスが通った場所に必要ですが,これらのファイル類がある場所を設定して利用することができます.

今回は %LOCALAPPDATA% フォルダ直下に CEF4Delphi という名前のフォルダを作成して,全てそこにコピーして利用するようにしました.



図5
CEF ライブラリの構成
  • [Release] と [Resources] 内のファイルとフォルダが必要
  • それ以外は全て不要
図6
%LOCALAPPDATA% フォルダ直下の CEF4Delphi フォルダにコピーしたファイル類
  • cef_sandbox.lib と libcef.lib は不要

これらのファイル類を実行時に使用するために,プロジェクト (DPR) のソースコードを以下のようにして,CEF ライブラリのファイルがある場所を指定しました.また,言語とロケールを ja (日本語) に設定しています.
本ページのサンプルは全てこの設定でテストしています.CEF4Delphi に添付のデモプログラムも全てのこの設定にしてテストしました.


リスト1
プロジェクトファイル (DPR ファイル) での設定
uses 部で指定しているユニット類にも注意
program Project1;

{$I cef.inc}

uses
  Forms,
  Windows,
  SysUtils,
  Dialogs,
  uCEFApplication,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE}

var
  LLibDir  : string;
  LDirChar : array [0..MAX_PATH - 1] of Char;
  LRunFlag : Boolean;
begin
  GlobalCEFApp := TCefApplication.Create;


  //---------------------------------------------------------------------------
  //  DLL等のライブラリ等のパスの設定と言語とロケールの設定
  //  usesにSysUtils,Dialogsが必要
  //---------------------------------------------------------------------------
  LRunFlag := True;
  try
    LLibDir := '%LOCALAPPDATA%\CEF4Delphi';
    FillChar(LDirChar, SizeOf(LDirChar), #0);
    ExpandEnvironmentStrings(PChar(LLibDir), LDirChar, MAX_PATH);
    LLibDir := LDirChar;
    if not (DirectoryExists(LLibDir)) then begin
      MessageBox(0, 'ライブラリのフォルダが存在しません', '起動失敗', MB_ICONERROR);
      LRunFlag := False;
    end else begin
      GlobalCEFApp.FrameworkDirPath   := LLibDir;
      GlobalCEFApp.LocalesDirPath     := LLibDir + '\locales';
      GlobalCEFApp.ResourcesDirPath   := LLibDir;
      GlobalCEFApp.AcceptLanguageList := 'ja';
      GlobalCEFApp.LocalesRequired    := 'ja';
      GlobalCEFApp.Locale := 'ja';
    end;
  except
    LRunFlag := False;
  end;
  if not LRunFlag then begin
    GlobalCEFApp.Free;
    Exit;
  end;
  //---------------------------------------------------------------------------

  if GlobalCEFApp.StartMainProcess then begin
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TForm1, Form1);
    Application.Run;
  end;
  GlobalCEFApp.Free;
end.


ウェブサイトによっては,メニューの表示が多国語に対応していることがあります.言語の設定を ja にしておけば日本語で表示するようになります.
下図は Yootube で再生している動画上で,マウスの右ボタンで現れるメニューの例です.


図7
言語の設定が ja の場合
図8
言語の設定がない場合




05_アプリ開始時と終了時の処理コード


CEF4Delphi のブラウザコントロールは,フォームの OnCreate イベント発生時点ではウェブページが表示可能な状態になっていません.このことについて以下の説明があります.

  • (1) TChromium.CreateBrowser で,指定した TCEFWindowParent を TChromiun のブラウザコントロールとして生成する
  • (2) TChromium.OnAfterCreated イベントを作成し,ブラウザコントロールが生成されたらフォームに生成完了のメッセージを送信する
  • (3) ブラウザ生成終了メッセージ受信専用のメソッドを作成し,メッセージを受信したら指定のウェブページを TChromium.LoadURL メソッドで表示する

また,終了時の処理についても,添付のデモプログラムのほとんど全てに次の説明が書かれています.

  • (1) OnFormCloseQuery イベントで CanClose を False にして TChromium.CloseBrowser を実行
    すると TChromium.Close イベントが発生する
  • (2) そのイベントで WindowParent を破棄する CEFBROWSER_DESTROY メッセージを送信する
    すると TChromium.OnBeforeClose イベントが発生する
  • (3) そのイベント内でフォームに WM_CLOSE を送信する

そこで,本ページに掲載しているサンプルもこの方針に従うことにしました.




06_CEF4Delphi のデモの [ MiniBrowser ] をコンパイルして実行


以上で準備が整いました.下図は CEF4Delphi に添付されているデモプログラム [ MiniBrowser ] をコンパイルして実行した結果です.
この [MiniBrowser] のデモプログラムは,DOM の操作,マウスイベントのリスナー等の参考コードとなっています.また,メニューの表示制御の参考にもなります.デベロッパーツールを表示することもできます.
CEF4Delphi にはこの他にも多くのデモプログラムが添付されています.



[備考 1]
CEF4Delphi に添付のデモプログラムをコンパイルすると,DCU ファイルはデモプログラムのフォルダ階層下の [Win32] [Debug] 内に生成されます.プロジェクト関係ファイルの構成を最小限にしてコンパイルすると,DCU ファイルはコンポーネントの [Source] フォルダ内に生成されて,コンパイルの速度も速くなります.
この方法は Win32, Win64, Debug, Release 等のフォルダを作成しなくなります.必要であれば [プロジェクト] [オプション] で出力ディレクトリを設定します.


図9
実行結果




07_CEF4Delphi における VisitDomProc 等を使用した DOM 操作


EF4Delphi には ICefDomDocument という DOM 操作用のインターフェイスが実装されています.これを利用して DOM の操作をすることができます.
ただし,このインターフェイスには,ブラウザを表示しているプロセスからは直接アクセスできません.このインターフェイスにアクセスするには,プロジェクトのソースコードで以下のようにイベントを設定します.
このイベントハンドラを,メインフォームのユニットで定義して実装します.

メインフォーム側でレンダラにメッセージを送信すると,そのイベントがメッセージを受信します.そのイベント内で DOM の操作が可能になります.


  GlobalCEFApp := TCefApplication.Create;
  GlobalCEFApp.OnProcessMessageReceived := GlobalCEFApp_OnProcessMessageReceived;


本サンプルは,この DOM の操作の例です.表示中のウェブページのタイトルの取得と,選択中のテキストの取得だけの簡単な例です.
フレームを処理するコードはありません.


[備考]
当サイトには dcef-r306 を使用した記事を掲載していますが,dcef-r306 であれば同じスレッド内での DOM 操作が可能です.dcef-306 は CEF1 相当のライブラリを使用します.
CEF1 はシングルスレッド用のライブラリでした.CEF3 はマルチスレッド用のライブラリとなっています.CEF2 というのはありません.



図10
設計時の画面
図11
ウェブページのタイトルを取得
図12
選択中のテキストを取得

リスト2
CEF4Delphi における VisitDomProc 等を使用した DOM 操作の例
unit Unit1;

{$I cef.inc}

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ExtCtrls, uCEFChromium, ComCtrls, uCEFWindowParent,
  uCEFInterfaces, uCEFTypes, uCEFConstants, uCEFProcessMessage, uCEFDomVisitor;

type
  TForm1 = class(TForm)
    CEFWindowParent1: TCEFWindowParent;
    Panel1: TPanel;
    SpeedButton1: TSpeedButton;
    Edit1: TEdit;
    Button1: TButton;
    Chromium1: TChromium;
    Timer1: TTimer;

    Button2: TButton;    procedure FormCreate(Sender: TObject);
    procedure Chromium1AfterCreated(Sender: TObject;
      const browser: ICefBrowser);
    procedure FormShow(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure Chromium1Close(Sender: TObject; const browser: ICefBrowser;
      out Result: Boolean);
    procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Chromium1AddressChange(Sender: TObject;
      const browser: ICefBrowser; const frame: ICefFrame; const url: ustring);
    procedure SpeedButton1Click(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Chromium1ProcessMessageReceived(Sender: TObject;
      const browser: ICefBrowser; sourceProcess: TCefProcessId;
      const message: ICefProcessMessage; out Result: Boolean);
  private
    { Private 宣言 }
    FCanClose   : boolean;
    FClosing    : boolean;
    procedure BrowserCreatedMsg(var aMessage : TMessage); message CEF_AFTERCREATED;
    procedure BrowserDestroyMsg(var aMessage : TMessage); message CEF_DESTROY;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

procedure GlobalCEFApp_OnProcessMessageReceived(const browser: ICefBrowser;
  sourceProcess: TCefProcessId; const message: ICefProcessMessage;
  var aHandled: boolean);

implementation

{$R *.dfm}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Edit1.Text := 'http://mrxray.on.coocan.jp/index.htm';

  Chromium1.CreateBrowser(CEFWindowParent1, '');
  FCanClose := False;
  FClosing  := False;
end;

//-----------------------------------------------------------------------------
//  以下,フォームの生成時から終了までの関係処理
//  CEF4Delphi 3.3359.1768.g8e7c5d6 現在用
//
//  TTimerのプロパティは以下の値にしている
//  TTimer.Enabled := False;
//  TTimer.Interval := 300;
//
//  ICefBrowser等のインターフェイスの使用にはusesにuCEFInterfacesが必要
//  定数名の使用にはusesにuCEFConstantsが必要
//-----------------------------------------------------------------------------
procedure TForm1.Chromium1AfterCreated(Sender: TObject;
  const browser: ICefBrowser);
begin
  PostMessage(Handle, CEF_AFTERCREATED, 0, 0)
end;

//-----------------------------------------------------------------------------
procedure TForm1.BrowserCreatedMsg(var aMessage: TMessage);
begin
  CEFWindowParent1.UpdateSize;
  Chromium1.LoadURL(Trim(Edit1.Text));
end;

//-----------------------------------------------------------------------------
procedure TForm1.FormShow(Sender: TObject);
begin
  //('初期化中です');

  //  WebRTCのIPリーク回避
  //  詳細は https://www.browserleaks.com/webrtc 参照
  Chromium1.WebRTCIPHandlingPolicy := hpDisableNonProxiedUDP;
  Chromium1.WebRTCMultipleRoutes   := STATE_DISABLED;
  Chromium1.WebRTCNonproxiedUDP    := STATE_DISABLED;

  //初期化されていない場合はタイマーを使用して後でブラウザを生成する
  if not(Chromium1.CreateBrowser(CEFWindowParent1, '')) then Timer1.Enabled := True;
end;

//-----------------------------------------------------------------------------
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := FCanClose;

  if not(FClosing) then begin
    FClosing := True;
    Visible  := False;
    Chromium1.CloseBrowser(True);
  end;
end;

//-----------------------------------------------------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
//
end;

//-----------------------------------------------------------------------------
procedure TForm1.Chromium1BeforeClose(Sender: TObject;
  const browser: ICefBrowser);
begin
  if (Chromium1.BrowserId = 0) then begin
    FCanClose := True;
    PostMessage(Handle, WM_CLOSE, 0, 0);
  end;
end;

//-----------------------------------------------------------------------------
procedure TForm1.Chromium1Close(Sender: TObject; const browser: ICefBrowser;
  out Result: Boolean);
begin
  if (browser <> nil) and (Chromium1.BrowserId = browser.Identifier) then begin
    PostMessage(Handle, CEF_DESTROY, 0, 0);
    Result := True;
  end else begin
    Result := False;
  end;
end;

//-----------------------------------------------------------------------------
procedure TForm1.BrowserDestroyMsg(var aMessage: TMessage);
begin
  CEFWindowParent1.Free;
end;

//-----------------------------------------------------------------------------
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  if not(Chromium1.CreateBrowser(CEFWindowParent1, ''))
    and not(Chromium1.Initialized) then begin
    Timer1.Enabled := True;
  end;
end;
//-----------------------------------------------------------------------------
//  フォーム生成時と終了時の処理関係コード.ここまで
//-----------------------------------------------------------------------------


//=============================================================================
//  Chromium1のOnAddressChangeイベント処理
//  変更されたウェブページのアドレスをEdit1に表示
//=============================================================================
procedure TForm1.Chromium1AddressChange(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame; const url: ustring);
begin
  Edit1.Text := url;
end;

//=============================================================================
//  Edit1のOnKeyDownイベント
//  [Enter]キーを押下したらEdit1のURLを表示
//=============================================================================
procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    Chromium1.LoadURL(Trim(Edit1.Text));
  end;
end;

//=============================================================================
//  前のページに移動
//=============================================================================
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  Chromium1.GoBack;
end;

//=============================================================================
//  DOM操作用のメッセージを生成して送信
//
//  プロジェクトのコード(DPRファイル)の
//  GlobalCEFApp.OnProcessMessageReceived
//  に設定したプロシージャーがメッセージを受信する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LMsg : ICefProcessMessage;
begin
  LMsg := TCefProcessMessageRef.New('get_doctitle');
  Chromium1.SendProcessMessage(PID_RENDERER, LMsg);
end;

//=============================================================================
//  同上
//  選択テキストを取得する
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LMsg : ICefProcessMessage;
begin
  LMsg := TCefProcessMessageRef.New('get_selecttext');
  Chromium1.SendProcessMessage(PID_RENDERER, LMsg);
end;

//=============================================================================
//  Chromium1のOnProcessMessageReceivedイベント処理
//  レンダラのプロセスからのメッセージを受信する
//  メッセージを受け取る必要がなければこのイベント処理は不要
//=============================================================================
procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
  const browser: ICefBrowser; sourceProcess: TCefProcessId;
  const message: ICefProcessMessage; out Result: Boolean);
var
  LText : ustring;
begin
  Result := False;
  if (message = nil) or (message.ArgumentList = nil) then Exit;

  if (message.Name = 'get_doctitle') then begin
    LText := message.ArgumentList.GetString(0);
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);

    Result := True;
  end else
  if (message.Name = 'get_selecttext') then begin
    LText := message.ArgumentList.GetString(0);
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);

    Result := True;
  end;
end;

//-----------------------------------------------------------------------------
//  TCefApplicationのOnProcessMessageReceivedの処理
//  メインプロセスから受信したメッセージを処理するレンダラ
//
//  プロジェクトファイル(DPR)でこのプロシージャーを指定しておく
//
//  レンダラは別プロセスで実行されるので,この部分にメインスレッドにある
//  コントロール等の操作を行わないこと
//
//  usesにuCEFDomVisitorが必要
//-----------------------------------------------------------------------------
procedure GlobalCEFApp_OnProcessMessageReceived(const browser: ICefBrowser;
  sourceProcess: TCefProcessId; const message: ICefProcessMessage;
  var aHandled: boolean);
var
  LIFrame : ICefFrame;
begin
  aHandled := False;
  if browser = nil then Exit;
  LIFrame := browser.MainFrame;
  if (LIFrame = nil) then Exit;

  if (message.Name = 'get_doctitle') then begin
    LIFrame.VisitDomProc(
    procedure(const doc: ICefDomDocument)
    var
      LMsg  : ICefProcessMessage;
      LText : ustring;
    begin
      //メインプロセスに送信するメッセージの文字列
      //ここでは受け取った値と同じにしている
      LMsg := TCefProcessMessageRef.New(message.Name);

      LText := doc.Title;
      //送信内容のセット(ここでは文字列)
      LMsg.ArgumentList.SetString(0, LText);
      //送信(メインプロセスで受け取る必要がなければ不要)
      browser.SendProcessMessage(PID_BROWSER, LMsg);
    end
    );
    aHandled := True;
  end else
  if (message.Name = 'get_selecttext') then begin
    LIFrame.VisitDomProc(
    procedure(const doc: ICefDomDocument)
    var
      LMsg  : ICefProcessMessage;
      LText : ustring;
    begin
      LMsg := TCefProcessMessageRef.New(message.Name);

      LText := doc.SelectionAsText;
      LMsg.ArgumentList.SetString(0, LText);
      browser.SendProcessMessage(PID_BROWSER, LMsg);
    end
    );
    aHandled := True;
  end;
end;

end.




08_CEF4Delphi におけるマウスのイベントリスナー


マウスのイベントの捕捉は JavaScript でマウスイベントリスナーを登録しますが,マウスイベントもブラウザを表示しているプロセス内では取得できません.
まず,プロジェクトのソースコードで以下のようにイベントを設定します. このイベントハンドラを,メインフォームのユニットで定義して実装します.

  GlobalCEFApp := TCefApplication.Create;
  GlobalCEFApp.OnWebKitInitialized := GlobalCEFApp_OnWebKitInitialized;

メインフォームのユニットでは以下のハンドラ登録用のクラスを定義します.このクラスを上で実装したイベント内で登録します.すると,この mouseover メソッドがマウスの操作を検出します.そのプロセスからメインプロセスへ,取得したメッセージを送信します.
メインプロセス側では OnProcessMessageReceived イベント内でこのメッセージを受信することができるようになります.

フォーム生成時と終了時のコードは上のサンプルと同じですので,以下のコードでは省略しています.このサンプルもフレームには未対応のコードです.


type
  TTestExtension = class
    class procedure mouseover(const data: string);
  end;


[備考]
CEF4Delphi が使用している CEF3 のライブラリには AddEventListenerProc はありません.
CEF3 で AddEventListenerProc が使用できないのは,最近の Google Chromium WebKit API ではこのメソッドが削除されているからです.


図13
マウスカーソル位置のタグ名を表示
図14
タグが A の時はリンク先を表示

リスト3
CEF4Delphi におけるマウスイベントリスナー
//-----------------------------------------------------------------------------
//  TTestExtensionクラスの登録
//  プロジェクトのコード(DPR)で以下のレンダラの設定をしておくと
//  TTestExtensionがメッセージをメインプロセスに送信できるようになる
//  GlobalCEFApp.OnWebKitInitialized := GlobalCEFApp_OnWebKitInitialized;
//
//  usesにuCEFv8Handlerが必要
//-----------------------------------------------------------------------------
procedure GlobalCEFApp_OnWebKitInitialized;
begin
  TCefRTTIExtension.Register('myextension', TTestExtension);
end;

//=============================================================================
//  マウスのイベントリスナーをJavaScriptで登録する
//  マウスカーソル位置のタグ名またはリンク先を取得
//  フレームには未対応のコード
//  実行結果はTChromium.OnProcessMessageReceivedイベントで受け取る
//  表示ページが変わったら再度登録が必要
//
//  自動的に登録するには,例えばTChromium.OnLoadEndイベント等で実行する
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LScriptText : string;
begin
  if (Chromium1.Browser = nil) or (Chromium1.Browser.MainFrame = nil) then exit;

  LScriptText := 'document.body.addEventListener("mouseover", function(evt){'
               + 'function getpath(n){'
               + '  if (n.nodeName === "A") {'
               + '    return n.href }'
               + '    else {'
               + '    return "<" + n.nodeName + ">"'
               + '  };'
               +'};'
               + 'myextension.mouseover(getpath(evt.target))'
               +'}'
               + ')';

  //JavaScriptの実行
  Chromium1.Browser.MainFrame.ExecuteJavaScript(LScriptText, 'about:blank', 0);
end;

//=============================================================================
//  Chromium1のOnProcessMessageReceivedイベント処理
//  レンダラからメッセージを受け取る
//=============================================================================
procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
  const browser: ICefBrowser; sourceProcess: TCefProcessId;
  const message: ICefProcessMessage; out Result: Boolean);
begin
  Result := False;
  if (message = nil) or (message.ArgumentList = nil) then Exit;

  if (message.Name = 'mouseover') then begin
    Label1.Caption := message.ArgumentList.GetString(0);
    Result := True;
  end;
end;

{ TTestExtension }

//-----------------------------------------------------------------------------
//  レンダラのメッセージをメインプロセスに送信する
//  TCefv8ContextRef.CurrentはJavascriptを実行しているフレームのv8コンテキスト
//
//  usesにuCEFv8Context,uCEFProcessMessageが必要
//  定数名の使用にはusesにuCEFConstantsが必要
//-----------------------------------------------------------------------------
class procedure TTestExtension.mouseover(const data: string);
var
  LMsg: ICefProcessMessage;
begin
  LMsg := TCefProcessMessageRef.New('mouseover');
  LMsg.ArgumentList.SetString(0, data);

  TCefv8ContextRef.Current.Browser.SendProcessMessage(PID_BROWSER, LMsg);
end;




09_GetSourceProc メソッドを使用した HTML のソースコード取得


表示中のウェブページの HTML ソースコードは,次のように GetSourceProc メソッドを使用して取得できます.このメソッドの引数はプロシージャー型 1 つで,そのプロシージャーの引数が HTML のソースコードの文字列となっています.


図15
GetSourceProc を使用した HTML コードの取得例
  • 保存したテキストをテキストエディタで表示

リスト4
表示中のウェブページの HTML ソースコードを GetSourceProc を使用して取得
//=============================================================================
//  GetSourceProcを使用したHTMLソースコードの取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LStrList : TStringList;
begin
  Chromium1.Browser.MainFrame.GetSourceProc(
    procedure(const str: ustring)
    begin
      LStrlist := TStringList.Create;
      try
        LStrList.Text := str;
        LStrList.SaveToFile(ExpandFileName('.\000.html'));
      finally
        FreeAndNil(LStrList);
      end;
    end
  );
end;


CEF4Delphi を使用したブラウザでは下図のようなコンテキストメニューが実装されていて,[ページのソースを表示] でメインフレームの HTML のソースコードを表示できます.この HTML のソースコードの表示には ICefFrame.ViewSource というメソッドが使用されています.
ただし,現状では日本語が文字化けすることがあるようです.

以下のコードはその現象の対策用のサンプルです.上のコードと同じ方法を使用しています.
このコードでは拡張子の関連付けのプログラムで HTML コードを表示するので,保存の時に拡張子を .txt にしています.拡張子を .html にして保存した場合,テキストエディタで表示するには,ShellExecute 関数の第 3 引数に起動するテキストエディタのフルパスを指定して,第 4 引数にテキストファイルを指定します.



図16
[ページのソースを表示] で表示すると日本語が文字化けする

リスト5
コンテキストメニューで実行するコマンドを変更して HTML のソースコードを表示
//=============================================================================
//  Chromium1のOnContextMenuCommandイベント処理
//  コンテキストメニューで[ページのソースを表示]を選択した時の動作を変更
//=============================================================================
procedure TForm1.Chromium1ContextMenuCommand(Sender: TObject;
  const browser: ICefBrowser; const frame: ICefFrame;
  const params: ICefContextMenuParams; commandId: Integer; eventFlags: Cardinal;
  out Result: Boolean);
var
  LStrList      : TStringList;
  LSaveFilePath : string;
begin
  Result := False;

  if commandId = MENU_ID_VIEW_SOURCE then begin
    Chromium1.Browser.MainFrame.GetSourceProc(
      procedure(const str: ustring)
      begin
        LSaveFilePath := ExpandFileName('.\000.txt');
        LStrlist := TStringList.Create;
        try
          LStrList.Text := str;

          //HTMLのソースコードを取得したら保存
          //保存したテキストファイルを既定のテキストエディタで表示
          //CEF3では%TEMP%の階層下にファイルを保存して表示している
          LStrList.SaveToFile(LSaveFilePath);
          ShellExecute(Handle, '', PChar(LSaveFilePath), nil, nil, SW_SHOWNA);
        finally
          FreeAndNil(LStrList);
        end;
      end
    );
    //コマンドを処理したことにする
    //本来のコマンドを実行させない
    Result := True;
  end;
end;


HTML のソースコードの取得には,CEF4Delphi のデモプログラム [MiniBrowser] で使用されている次のような方法もあります.TChromium.RetrieveHTML メソッドと OnTextResultAvailable イベントを使用する方法です.



リスト6
HTML のソースコードを取得
TChromium.RetrieveHTML メソッドと OnTextResultAvailable イベントを使用する方法
//=============================================================================
//  RetrieveHTMLを使用したHTMLソースコードの取得
//  RetrieveHTMLの引数を省略するとメインフレームのHTMLソースコードの取得となる
//  結果はOnTextResultAvailableイベントで受け取る
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Chromium1.RetrieveHTML;
end;

//=============================================================================
//  Chromium1のOnTextResultAvailableイベント処理
//  TChromium.RetrieveHTMLメソッドの結果を受け取る
//
//  usesにClipbrdが必要
//=============================================================================
procedure TForm1.Chromium1TextResultAvailable(Sender: TObject;
  const aText: string);
begin
  //aTextがHTMLのソースコードの文字列
  clipboard.AsText := aText;
end;




10_CEF4Delphi でダウンロードのリンクをクリックした時の処理


CEF4Delphi のブラウザでは,ダウンロードのリンクをクリックしても,デフォルトの状態ではダウンロードを開始しません.
実際にダウンロードを開始させるには OnBeforeDownload イベントハンドラを実装して,そのイベント内で callback.cont メソッドを実行します.ダウンロードは別スレッドで行われます.以下が具体的な例ですが,このサンプルも,フォーム生成時と終了時のコードは最初のサンプルと同じですので省略しています.


[備考]
TChromium.startDownload というファイルのダウンロード用のメソッドがありますが,このメソッドは引数のファイルが存在しないと OnBeforeDownload イベントが発生しません.そのためダウンロードの失敗を検出できません.エラーも発生しません.


図17
保存ダイアログを表示
図18
ダウンロードの進捗状況を表示

リスト7
ダウンロードさせるには OnBeforeDownload イベントを実装する
//=============================================================================
//  Chromium1のOnBeforeDownloadイベント処理
//  このイベント内でコールバックのcallback.Contを実行しないとダウンロードしない
//
//  第2引数をTrueにすると保存ダイアログを表示する
//  第2引数をFalseにすると第1引数の場所に自動保存
//  保存先フォルダか存在しない時は保存しない
//
//  このイベントの使用にはusesにuCEFInterfaces, uCEFTypes, uCEFConstantsが必要
//=============================================================================
procedure TForm1.Chromium1BeforeDownload(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const suggestedName: ustring;
  const callback: ICefBeforeDownloadCallback);
var
  LSaveDir      : string;
  LSaveFullPath : string;
  LTempName     : string;
begin
  if not(Chromium1.IsSameBrowser(browser))
    or (downloadItem = nil) or not(downloadItem.IsValid) then Exit;

  LSaveDir := ExtractFileDir(Application.ExeName);

  if (Length(suggestedName) > 0) then begin
    LTempName := suggestedName;
  end else begin
    LTempName := 'DownloadedFile';
  end;

  if (length(LSaveDir) > 0) then begin
    LSaveFullPath := IncludeTrailingPathDelimiter(LSaveDir) + LTempName;
  end else begin
    LSaveFullPath := LTempName;
  end;

  //ダウンロード用のコールバック実行
  callback.cont(LSaveFullPath, True);
end;

//=============================================================================
//  Chromium1のOnDownloadUpdatedイベント処理
//  ダウンロード用のコールバック関数内で発生するイベント
//  ダウンロードの進捗状況の表示に使用できる
//
//  このイベントの使用にはusesにuCEFInterfaces, uCEFTypes, uCEFConstantsが必要
//=============================================================================
procedure TForm1.Chromium1DownloadUpdated(Sender: TObject;
  const browser: ICefBrowser; const downloadItem: ICefDownloadItem;
  const callback: ICefDownloadItemCallback);
var
  LTempString : string;
begin
  if not(Chromium1.IsSameBrowser(browser)) then Exit;

  if downloadItem.IsComplete then begin
    StatusBar1.Panels[0].Text := downloadItem.FullPath + ' completed';
  end else
  if downloadItem.IsCanceled then begin
    StatusBar1.Panels[0].Text := downloadItem.FullPath + ' canceled';
  end else
  if downloadItem.IsInProgress then begin
    if (downloadItem.PercentComplete >= 0) then begin
      LTempString := downloadItem.FullPath
                   + ' : ' + inttostr(downloadItem.PercentComplete) + '%'
    end else begin
      LTempString := downloadItem.FullPath + ' : '
                   + inttostr(downloadItem.ReceivedBytes) + ' bytes received';
    end;
    StatusBar1.Panels[0].Text := LTempString;
  end;
end;




11_JavaScript の実行と JavaScript の関数の戻り値取得


JavaScript は ExecuteJavaScript メソッドで実行できます.CEF4Delphi にはコンソールの出力を取得するためのイベント OnConsoleMessage があります.このイベントで JavaScript の関数の実行結果を取得することができます.
本サンプルでは表示中の HTML コード内の JavaScript の関数を実行するのではなくコードを追加して実行しますが, HTML 内に <script> タグで追加するのではなく 'about:blank' として追加します.
'about:blank' で追加しても HTML の document の操作は可能です.
フォーム生成時と終了時のコードは最初のサンプルと同じですので省略しています.


図19
実行結果

リスト8
JavaScript の実行と JavaScript の関数の戻り値取得
//=============================================================================
//  JavaScriptの実行
//  FJSFuncNameはグローバル変数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LScriptText : string;
  LJsFunc     : string;
  LParam1     : string;
  LParam2     : string;
begin
  LScriptText := 'javascript: '
               + ''
               + 'function add_values(a, b) {'
               + '  var ret = "計算結果 : " + String(a + b);'
               + '  return ret;'
               +'}';

  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LScriptText, 'about:blank');

  //実行する関数名
  //OnConsoleMessageイベント内ではこの値で処理を分岐する
  FJSFuncName := 'add_values';

  //JavaScript内の関数を実行
  //戻り値の出力先はconsole.log
  LParam1 := IntToStr(100);
  LParam2 := IntToStr(350);
  LJsFunc := 'console.log(' + FJSFuncName + ' ( ' + LParam1 + ',' + LParam2 + '))';
  Chromium1.Browser.MainFrame.ExecuteJavaScript(LJsFunc, 'about:blank', 0);
end;

//=============================================================================
//  Chromium1のOnConsoleMessageイベント処理
//  JavaScript内のの関数の実行結果をこのイベントで受け取る
//=============================================================================
procedure TForm1.Chromium1ConsoleMessage(Sender: TObject;
  const browser: ICefBrowser; level: Cardinal; const message, source: ustring;
  line: Integer; out Result: Boolean);
var
  LRetText : string;
begin
  if source = 'about:blank' then begin
    if FJSFuncName = 'add_values' then begin
      LRetText := message;
      MessageBox(Handle, PChar(LRetText), '結果', MB_ICONINFORMATION);
    end;
  end;

  FJSFuncName := '';
end;


引数がない JavaScript の関数を実行する場合は,下に示すコードのように,ExecuteJavaScript で実行する時に引数を "" のように空文字にします.このコードの場合は期待通りに動作しますが,処理の内容によっては,引数の値を空文字にしないと正常に処理されないことがあります.


図20
取得して保存した HTML コードをテキストエディタで表示

リスト9
引数がない JavaScript 関数の実行例
表示中のウェブページの HTML コードを取得
// =============================================================================
// JavaScriptの実行
// FJSFuncNameはグローバル変数
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LScriptText: string;
  LJsFunc    : string;
begin
  LScriptText := 'javascript: '
               + ''
               + 'function get_htmlcode() {'
               + '  return document.getElementsByTagName("html")[0].innerHTML;'
               + '}';

  // JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LScriptText, 'about:blank');

  // 実行する関数名
  // OnConsoleMessageイベント内ではこの値で処理を分岐する
  FJSFuncName := 'get_htmlcode';

  // JavaScript内の関数を実行
  LJsFunc := 'console.log(' + FJSFuncName + ' (""))';
  Chromium1.browser.MainFrame.ExecuteJavaScript(LJsFunc, 'about:blank', 0);
end;

// =============================================================================
// Chromium1のOnConsoleMessageイベント処理
// JavaScript内のの関数の実行結果をこのイベントで受け取る
// =============================================================================
procedure TForm1.Chromium1ConsoleMessage(Sender: TObject;
  const browser: ICefBrowser; level: Cardinal; const message, source: ustring;
  line: Integer; out Result: Boolean);
var
  LStrList: TStringList;
begin
  if source = 'about:blank' then begin
    if FJSFuncName = 'get_htmlcode' then begin
      LStrList := TStringList.Create;
      try
        LStrList.Text := message;
        LStrList.SaveToFile('000.html');
      finally
        FreeAndNil(LStrList);
      end;
    end;
  end;

  FJSFuncName := '';
end;




12_ウェブページの画像の URL 列挙と JavaScript の配列値の取得


前述の DOM の操作のサンプルで使用している ICefDomDocument には getElementsByTagName 等がありませんが,タグ要素とその属性を調べることで操作可能です.
また,本項のサンプルのように,JavaScript を使用して DOM 操作する手もあります.

本サンプルでは表示中のウェブページの IMG 要素の src 属性の値を JavaScript を使用して列挙します.つまり,ウェブページで表示中の画像の URL の値を取得します.JavaScript で URL のリストを配列に格納し,その配列を JavaScript の関数の実行結果として取得して保存します.
JavaScript の配列は,配列の各要素がカンマで区切られた文字列として取得できます.それを TStringList に格納していきます.

ネット上には JavaScript のコードが比較的豊富にありますから,JavaScript による DOM の操作はある意味で簡便と言えるかも知れません.コーディングの面で言えば ICefDomDocument を使用する方が便利と言えます.



[備考]
例えばタグの属性名とその属性値のペアで複数のデータを取得したい場合は,JavaScript 側で JSON 形式のデータにしてしまう方法も考えられます.1 組だけのペアであれば要素数が 2 つの配列を使用すれば十分です.


図21
保存した URL のリストをテキストエディタで表示

リスト10
ウェブページの画像の URL を列挙
JavaScript の配列要素を TStringList に格納している
//=============================================================================
//  JavaScriptの実行
//  FJSFuncNameはグローバル変数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LScriptText : string;
  LJsFunc     : string;
begin
  LScriptText := 'javascript: '
               + ''
               + 'function get_imglist () {'
               + '  var list = new Array();'
               + '  var doccollection = document.getElementsByTagName("img");'
               + '  for (var i = 0; i < doccollection.length; ++i){'
               + '    list.push(doccollection[i].src);'
               + '  }'
               + ' return list;'
               +'}';

  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LScriptText, 'about:blank');

  //実行する関数名
  //OnConsoleMessageイベント内ではこの値で処理を分岐する
  FJSFuncName := 'get_imglist';

  //JavaScript内の関数を実行
  //戻り値の出力先はconsole.log
  LJsFunc := 'console.log(' + FJSFuncName + ' (""))';
  Chromium1.Browser.MainFrame.ExecuteJavaScript(LJsFunc, 'about:blank', 0);
end;

//=============================================================================
//  Chromium1のOnConsoleMessageイベント処理
//  JavaScript内の関数の実行結果をこのイベントで受け取る
//=============================================================================
procedure TForm1.Chromium1ConsoleMessage(Sender: TObject;
  const browser: ICefBrowser; level: Cardinal; const message, source: ustring;
  line: Integer; out Result: Boolean);
var
  LStrList : TStringList;
begin
  if source = 'about:blank' then begin
    if FJSFuncName = 'get_imglist' then begin
      LStrList := TStringList.Create;
      try
        LStrList.Delimiter := ',';
        LStrList.StrictDelimiter := True;
        LStrList.DelimitedText := message;

        //リストを保存
        LStrList.SaveToFile(ExpandFileName('.\ImagesrcList.txt'));
      finally
        FreeAndNil(LStrList);
      end;
    end;
  end;

  FJSFuncName := '';
end;


上の例のように,実行する JavaScript の関数に引数がなくて,しかも 1 回だけであれば以下のように,戻り値を console.log にセットすこともできます.
引数が必要であれば,引数に相当する値を変数 var で与えることもできます.

もし,アプリケーションで JavaScript を 1 回しか実行しないのであればそのままでいいのですが,複数回実行することを考慮すると,何を実行したかをグローバル変数等で区別する必要があります.あるいは consol.log の出力に判定用の文字列を追加しておく方法も考えられます.



リスト11
引数が必要ない処理を 1 回だけ実行するのであれば JavaScript 内での関数の定義は不要
//=============================================================================
//  JavaScriptの実行
//  FJSFuncNameはグローバル変数
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LScriptText : string;
begin
  FJSFuncName := 'get_imglist';
  LScriptText := 'javascript: '
               + ''
               + 'var list = new Array();'
               + 'var doccollection = document.getElementsByTagName("img");'
               + 'for (var i = 0; i < doccollection.length; ++i){'
               + '  list.push(doccollection[i].src);'
               + '}'
               + 'console.log(list)';

  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LScriptText, 'about:blank');
end;

//=============================================================================
//  Chromium1のOnConsoleMessageイベント処理
//  JavaScript内の関数の実行結果をこのイベントで受け取る
//=============================================================================
procedure TForm1.Chromium1ConsoleMessage(Sender: TObject;
  const browser: ICefBrowser; level: Cardinal; const message, source: ustring;
  line: Integer; out Result: Boolean);
var
  LStrList : TStringList;
begin
  if source = 'about:blank' then begin
    if FJSFuncName = 'get_imglist' then begin
      LStrList := TStringList.Create;
      try
        LStrList.Delimiter := ',';
        LStrList.StrictDelimiter := True;
        LStrList.DelimitedText := message;

        //リストを保存
        LStrList.SaveToFile(ExpandFileName('.\ImagesrcList.txt'));
      finally
        FreeAndNil(LStrList);
      end;
    end;
  end;

  FJSFuncName := '';

end;




13_JavaScript コード内のシングルとダブルクォーテーション


前項の例のような URL の文字列にはカンマが含まれることはありませんが,以下の JavaScript のコードは 1 つの要素の文字列の中にカンマが含まれる例です.
この例のように,カンマ区切りの文字列 (CSV) を分割する際,カンマを含む文字列は " (ダブルクォーテーション) で括らないと複数の文字列に分割されてしまいます.

このテストでは,下に示している JavaScript のコードをテキストファイルとして保存し,実行時に読み込んでセットしています.



図22
カンマを含む文字列の分割結果

javascript: 

function get_array () {
  var list = new Array();
  list.push("東京都");
  list.push("大,阪,府");
  list.push("京都府");
  return list;
}

JavaScript で " (ダブルクォーテーション) を文字して認識させるには,\" と記述します.また ' (シングルクォーテーション) も同じ様に \' と記述します.
下の JavaScript のコードはその例です.


図23
カンマを含む文字列を 1 つの文字列として扱う

javascript: 

function get_array () {
  var list = new Array();
  list.push("\"東京都\"");
  list.push("\"大,阪,府\"");
  list.push("\'京都府\'");
  return list;
}


上のテストでは JavaScript コードをテキストファイルに保存してそれを実行時に読み出しています.
コード内に JavaScript のコードを直接書く場合は以下のように書けます.Delphi では ' (シングルクォーテーション) は文字列の定義文の囲み文字ですからそのままでは ' として認識できません.そこでこのコードでは Chr 関数を使用して ASCII コードの値で設定しています.


var
  LCode    : string;
  LJsFunc  : string;
begin
  LCode := 'javascript:'
         + ''
         + 'function get_array () {'
         + 'var list = new Array();'
         + 'list.push(' + Chr(39) + '東京都' + Chr(39) + ');'
         + 'list.push("\"大,阪,府\"");'
         + 'list.push("\' + Chr(39) + '京都府\' + Chr(39) +'");'
         + 'return list;'
         + '}';
  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LCode, 'about:blank');


Chr(39) は,以下のように #39 としても同じです.


var
  LCode    : string;
  LJsFunc  : string;
begin
  LCode := 'javascript:'
         + ''
         + 'function get_array () {'
         + 'var list = new Array();'
         + 'list.push(' + Chr(39) + '東京都' + Chr(39) + ');'
         + 'list.push("\"大,阪,府\"");'
         + 'list.push("\' + #39 + '京都府\' + #39 +'");'
         + 'return list;'
         + '}';
  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LCode, 'about:blank');


Delphi では,文字列の中に ' (シングルクォーテーション) を 2 つ書くとシングルクォーテーションとして認識してくれます.したがって,上のコードは次のように書くことができます.


var
  LCode    : string;
  LJsFunc  : string;
begin
  LCode := 'javascript:'
         + ''
         + 'function get_array () {'
         + 'var list = new Array();'
         + 'list.push(''東京都'');'
         + 'list.push("\"大,阪,府\"");'
         + 'list.push("''京都府''");'
         + 'return list;'
         + '}';
  //JavaScriptのコードをセット
  Chromium1.ExecuteJavaScript(LCode, 'about:blank');




14_Find メソッドによるテキストの逐次検索とキーイベント処理


ページのテキストの逐次検索のテストサンプルです.Delphi の IDE のコードエディタの検索のスタイルを真似てみました.TChromium.Find メソッドを使用しています.大文字と小文字の区別なしの検索としています.

[Ctrl] + [F3] でフォームの左下に検索用のコントロールを表示します.[F3] で下方向に,[Shift] + [F3] で上方向に逐次検索します.
キーの押下は OnKeyEvent イベントで検出していますが,このイベント内でメインプロセスのフォームに配置しているコントロール類の操作をすると動作不正になります.
そこで本サンプルでは,OnKeyEvent イベントでのキー押下情報を PostMessage でメインプロセスのフォームに送信しています.メインフォーム側では TApplicationEvents.OnMessage イベントでメッセージを受け取って処理するようにしています.

このサンプルでは,メインフォームのキー押下と区別するために CHROMIUM_KEYDOWN というメッセージ用の定数を定義して使用していますが,メッセージとしてのキーの押下は,ブラウザにフォーカスがない時も検出できるようにしています.



[備考]
TChromium のブラウザコントロールのキー操作は,TApplicationEvents.OnMessage イベントでは補足できません.


図24
検索の実行中
  • フレーム対応
  • 検索操作用として TPanel に以下を配置
    TEdit を 1 つ
    TSpeedButton を 3 つ
    TLabel を 1 つ

リスト12
Find メソッドによるテキストの逐次検索とキーイベント処理
//=============================================================================
//  Chromium1のOnKeyEventイベント処理
//
//  このイベント内ででメインプロセスのコントロールに対して操作しないこと
//  メインプロセスのコントロールに対して操作するとハングアップする
//=============================================================================
procedure TForm1.Chromium1KeyEvent(Sender: TObject; const browser: ICefBrowser;
  const event: PCefKeyEvent; osEvent: PMsg; out Result: Boolean);
var
  LMsg : TMsg;
begin
  Result := False;

  if not(Chromium1.IsSameBrowser(browser)) then exit;
  if (event = nil) or (osEvent = nil) then Exit;

  if osEvent.Message = WM_KEYDOWN then begin
    LMsg := osEvent^;
    //メインのプロセスにメッセージを送信
    //本サンプルではこのメッセージをTApplicationEvents.OnMessageで受け取る
    PostMessage(Handle, CHROMIUM_KEYDOWN, LMsg.wParam, LMsg.lParam);

    Result := True;
  end;
end;

//=============================================================================
//  ApplicationEvents1のOnMessageイベント処理
//  Chromium1のOnKeyEventイベントからのメッセージを受け取って処理する
//=============================================================================
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  LKeyState : TKeyboardState;
  LIsCtrl   : Boolean;
  LIsShift  : Boolean;
begin
  if (Msg.message = CHROMIUM_KEYDOWN) or (Msg.message = WM_KEYDOWN) then begin
    LIsCtrl := False;
    if GetKeyboardState(LKeyState) then begin
      LIsCtrl  := LKeyState[VK_CONTROL] and $80 <> 0;
      LIsShift := LKeyState[VK_SHIFT] and $80 <> 0;
    end;
    //[Ctrl]キーが含まれていたら検索操作用のTPanelを表示して検索開始
    if (Msg.wParam = Ord('F')) and LIsCtrl then begin
      Panel2.Visible := True;
      SpeedButton3Click(nil);
      Edit2.SetFocus;
    end else
    if (Msg.wParam = VK_F3) and (Panel2.Visible) and LIsShift then begin
      SpeedButton2Click(nil);
    end else
    if (Msg.wParam = VK_F3) and (Panel2.Visible) then begin
      SpeedButton3Click(nil);
    end;
  end;
end;

//=============================================================================
//  検索用の文字列を入力するTEditのOnKeyDownイベント処理
//  [Enter]キーで検索開始
//=============================================================================
procedure TForm1.Edit2KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_RETURN then begin
    SpeedButton3Click(nil);
  end;
end;

//=============================================================================
//  前(上方向に)検索
//=============================================================================
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
  Chromium1.Find(1, Edit2.Text, False, False, True);
end;

//=============================================================================
//  次(下方向に)検索
//=============================================================================
procedure TForm1.SpeedButton3Click(Sender: TObject);
begin
  if Trim(Edit2.Text) = '' then begin
    Label1.Caption := '';
    SpeedButton2.Enabled := False;
    SpeedButton3.Enabled := False;
  end else begin
    Chromium1.Find(1, Edit2.Text, True, False, False);
  end;
end;

//=============================================================================
//  検索操作終了
//=============================================================================
procedure TForm1.SpeedButton4Click(Sender: TObject);
begin
  Chromium1.StopFinding(True);
  //検索操作用のTPanelを非表示にする
  Panel2.Visible := False;
end;

//=============================================================================
//  Chromium1のOnFindResultイベント処理
//  検索の結果,ヒットした件数等を表示
//=============================================================================
procedure TForm1.Chromium1FindResult(Sender: TObject;
  const browser: ICefBrowser; identifier, count: Integer;
  const selectionRect: PCefRect; activeMatchOrdinal: Integer;
  finalUpdate: Boolean);
begin
  Label1.Caption := IntToStr(count) + ' 件中 ' + IntToStr(activeMatchOrdinal) + ' 件目';
  SpeedButton2.Enabled := count >= 1;
  SpeedButton3.Enabled := count >= 1;
end;




15_TChromium のブラウザを編集モードにする


TChromium のブラウザを編集モードにするサンプルです.これは IE オブジェクトの HTMLDocument2 の designMode プロパティに相当する機能です.
TChromium にはこれに相当するメソッドやプロパティが実装されていませんが,JavaScript を使用すれば実現できます.


図25
通常の状態で画像を選択した時のコンテキストメニュー
図26
編集モードにして画像を選択
  • 違うコンテキストメニューになる
  • 削除は [Del] キーでも可能
  • 画像の貼り付けは,ローカルな画像ファイルを表示している場合のみ可能
  • テキストの編集も可能
図27
選択した画像を削除した状態

リスト13
document.designMode を使用してブラウザを編集モードにする
//=============================================================================
//  designMode開始
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  Chromium1.ExecuteJavaScript('document.designMode = "on";', 'about:blank');
end;

//=============================================================================
//  designMode終了
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  Chromium1.ExecuteJavaScript('document.designMode = "off";', 'about:blank');
end;