TWebBrowserとTDBGrid

解決


久美子  2013-06-27 19:43:41  No: 44753

一つのフォームにWebBrowserとDBGridを配置して表示すると、Formを開いた当初は、DBGridをクリックすると、カーソルがその行に行くのに、一度WebBrowserをマウスで操作した後は、DBGridをクリックしても、カーソが移動しなくなりました。(マウスホイールによる行移動は可能です。)
いくつかのプログラムで試しをしましたが、同様になります。
WebBrowserとDBGridの相性なのでしょうか。
プロパティーの変更等で対応できるのでしょうかおわかり方がいらっしゃいましたら教えてください。
環境:Windows7 64bit  Delphi XE2です。


Harry  2013-06-30 11:06:15  No: 44754

TWebBrowserとTStringGridの組み合わせで再現を確認しました。
(Delphi6 Personal、WindowsXP、IE8)

>プロパティーの変更等で対応できるのでしょうか
何かの設定で直せるものではないと思います。
私の調べた限りでは、下記の「TWebBrowserがActiveControlにならない」という事象に起因する
不具合だと思います。

How to make a TWebBrowser become the active control when clicked - DelphiDabbler.com
http://www.delphidabbler.com/articles?article=19

例示されてるコードやダウンロードできるデモプロジェクトの通りにすれば修正できると思います。
でもこのサンプルは、タイマーでポーリングしてTApplicationEventsのOnMessageで処理、しかも
TWebBrowserは1個に限定!という、かなりアレな感じですけど。

TUIWebBrowserとかTEmbeddedWBとかの利用を検討するのはどうでしょうか。
ただし、これらでIEコンポーネントの不具合がどれだけ解消しているのか知りません。
TChromium(DCEF3)という先進的なものもあります。これは割と良い感触でした。


Harry  2013-06-30 11:16:31  No: 44755

TWebBrowserの既知の不具合を手軽に修正する方法を研究してましたので、この機会に
今回のT****Gridと絡んだ不具合の修正も加えて完成させてみました。
ただ、、、まだあんまりテストしてません。Windows7や64bitOS環境も未チェックです。
もしよろしければテストしてみてください。

・ActiveControlに関しては、前述のサイトの情報を元に再構築しました。
・textareaとファイル名変更に関しては、古来より伝わるコード?を整頓して使ってます。
※1998年の時点ですでに原型が見られます。
GetClassName function - Experts Exchange(1998/07/28)
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_10067496.html

-------------------------------------------------------------------------------
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Grids, OleCtrls, SHDocVw_TLB; // ←環境によりSHDocVwになる

type
  TForm1 = class(TForm)
    WebBrowser1: TWebBrowser;
    WebBrowser2: TWebBrowser;
    StringGrid1: TStringGrid;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  Unit2,   // TWebBrowserFix
  ActiveX; // OleInitialize, OleUninitialize

procedure TForm1.FormCreate(Sender: TObject);
begin
  TWebBrowserFix.Create(WebBrowser1); // Navigateの直前でFixを生成しておく
  WebBrowser1.Navigate('https://www.petitmonte.com/bbs/answers?question_id=8026');

  WebBrowser2.Navigate('https://www.petitmonte.com/bbs/answers?question_id=973');
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TWebBrowserFix.Create(WebBrowser2); // 後から任意の時点でFixを生成してもOk
end;

initialization
  OleInitialize(nil);

finalization
  OleUninitialize;

end.

-------------------------------------------------------------------------------
unit Unit2;

interface

uses
  Windows, Messages, Classes, SHDocVw_TLB; // ←環境によりSHDocVwにする

type
  TWebBrowserFix = class(TComponent)
  private
    FIEServerWin: HWND;
    FDefOnNavigateComplete2: TWebBrowserNavigateComplete2;
    function MakeFixProc(): Boolean;
    function GetIEServerWindow(EmbedWin: HWND): HWND;
    procedure FirstTimeNaviComp2(Sender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure AppEventsMessage(var Msg: TMsg; var Handled: Boolean);
  public
    constructor Create(AWebBrowser: TWebBrowser); reintroduce;
  end;

implementation

uses
  AppEvnts, // TApplicationEvents
  ActiveX,  // IOleInPlaceActiveObject
  Forms;    // GetParentForm(TControl)

constructor TWebBrowserFix.Create(AWebBrowser: TWebBrowser);
begin
  inherited Create(AWebBrowser);

  if MakeFixProc() then Exit;

  FDefOnNavigateComplete2:=TWebBrowser(Self.Owner).OnNavigateComplete2;
  TWebBrowser(Self.Owner).OnNavigateComplete2:=FirstTimeNaviComp2;
end;

function TWebBrowserFix.MakeFixProc(): Boolean;
begin
  Result:=False;
  FIEServerWin:=GetIEServerWindow(TWebBrowser(Self.Owner).Handle);
  if FIEServerWin=0 then Exit;

  Result:=True;
  TApplicationEvents.Create(Self).OnMessage:=AppEventsMessage;
end;

function TWebBrowserFix.GetIEServerWindow(EmbedWin: HWND): HWND;
var
  DocObjWin: HWND;
begin
  Result:=0;
  if EmbedWin=0 then Exit;

  DocObjWin:=FindWindowEx(EmbedWin, 0, 'Shell DocObject View', nil);
  if DocObjWin<>0 then
    Result:=FindWindowEx(DocObjWin, 0, 'Internet Explorer_Server', nil);
end;

procedure TWebBrowserFix.FirstTimeNaviComp2(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  if Assigned(FDefOnNavigateComplete2) then FDefOnNavigateComplete2(Sender, pDisp, URL);

  if not MakeFixProc() then Exit;

  TWebBrowser(Self.Owner).OnNavigateComplete2:=FDefOnNavigateComplete2;
end;

procedure TWebBrowserFix.AppEventsMessage(var Msg: TMsg; var Handled: Boolean);
var
  Dispatch: IDispatch;
  iOIPAO:   IOleInPlaceActiveObject;
begin
  if Msg.hwnd<>FIEServerWin then Exit;

  // ・TWebBrowserがActiveControlにならないため、TStringGrid等で矢印キー移動できない
  // …という不具合の修正
  case Msg.message of
    WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN: begin
      GetParentForm(TWebBrowser(Self.Owner)).ActiveControl:=TWebBrowser(Self.Owner);
      Exit;
    end;
  end;

  // ・Web表示モード時にtextareaで改行が入力できない
  // ・Web表示モード時にtextareaで矢印キー押下でアプリ内の他コントロールに移動してしまう
  // ・フォルダ表示モード(Explorerモード?)時にファイル名を変更できない
  // …という不具合の修正
  Handled:=IsDialogMessage(TWebBrowser(Self.Owner).Handle, Msg);
  if Handled and not (TWebBrowser(Self.Owner).Busy or
     (((Msg.message=WM_KEYDOWN) or (Msg.message=WM_KEYUP)) and
      (Msg.wParam in [VK_BACK, VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN]))) then begin
    Dispatch:=TWebBrowser(Self.Owner).Application;
    if Dispatch<>nil then begin
      Dispatch.QueryInterface(IOleInPlaceActiveObject, iOIPAO);
      if iOIPAO<>nil then iOIPAO.TranslateAccelerator(Msg);
    end;
    Dispatch._Release;
  end;
end;

end.


久美子  2013-07-01 18:49:14  No: 44756

Harryさん、ありがとうございました。
ご提示いただいたソースを、自分なりに理解しながら、試してみたいと思います。
まだ、ご報告しますが、しばらくお時間をください。


久美子  2013-07-04 00:38:16  No: 44757

Harryさんのソースで、試しをしてみました。
Unit2で
procedure FirstTimeNaviComp2(Sender: TObject;
      const pDisp: IDispatch; var URL: OleVariant);
    procedure AppEventsMessage(var Msg: TMsg; 
に対して
constructor TWebBrowserFix.Create(AWebBrowser: TWebBrowser)

  TWebBrowser(Self.Owner).OnNavigateComplete2:=FirstTimeNaviComp2;
としているため、パラメータが足りないとエラーが出ます。
どのようにしたらいいのでしょうか。


Harry  2013-07-04 21:22:33  No: 44758

本題に入る前にひとつ。
先のソースではCreateの引数をチェックしてませんでしたが、そこくらいは押さえておきたいので、
implementation 〜 コンストラクタまでのコードを以下のように変更します。

-----------------------------------------------------------------------------------------
implementation

uses
  SysUtils, // class(Exception);
  AppEvnts, // TApplicationEvents
  ActiveX,  // IOleInPlaceActiveObject
  Forms;    // GetParentForm(TControl)

type
  EArgumentNilException = class(Exception);

constructor TWebBrowserFix.Create(AWebBrowser: TWebBrowser);
begin
  if AWebBrowser=nil then
    raise EArgumentNilException.Create('Error TWebBrowser not Assigned.');

  inherited Create(AWebBrowser);

  if MakeFixProc() then Exit;

  FDefOnNavigateComplete2:=TWebBrowser(Self.Owner).OnNavigateComplete2;
  TWebBrowser(Self.Owner).OnNavigateComplete2:=FirstTimeNaviComp2;
end;
-----------------------------------------------------------------------------------------


Harry  2013-07-04 21:56:43  No: 44759

テストしていただき、ありがとうございます。 結論から言いますと、
    Delphi XE2以降の場合、FirstTimeNaviComp2メソッドのパラメータ、var URL: OleVariant の部分を、
    const URL: OleVariant のように、var を const に書き換えてください。

※なお、いま試案中の方法ではOnNavigateComplete2を使わず、このメソッドも不要になる…はず。

以下はチラシの裏です。
-----------------------------------------------------------------------------------------
>パラメータが足りないとエラーが出ます。
悩みました。考えても分からないので、XE4 トライアル版をインストールしてみました \(^o^)/
すると…コンパイルエラーが1個発生。
>[dcc32 エラー] Unit2.pas(50): E2009 型に互換性がありません : パラメータリストが異なります

よくよくOnNavigateComplete2のイベントハンドラ(TWebBrowserNavigateComplete2)を見比べますと…
■Delphi 6 Personal(SHDocVw_TLB.pas)
procedure TForm1.WebBrowser1NavigateComplete2(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
■Delphi XE4(SHDocVw.dcu)
procedure TForm1.WebBrowser1NavigateComplete2(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant);

パラメータが微妙に、var → const に変わってる。 ひどすぎるよ、こんなのってないよ!
そこで、いつから変わったのか調べました。

SHDocVw.TWebBrowserNavigateComplete2 - XE オンラインヘルプ
http://docwiki.embarcadero.com/VCL/XE/ja/SHDocVw.TWebBrowserNavigateComplete2
>C++ typedef void __fastcall (__closure *TWebBrowserNavigateComplete2)(System::TObject* ASender, const _di_IDispatch pDisp, System::OleVariant &URL);

SHDocVw.TWebBrowserNavigateComplete2 - XE2 オンラインヘルプ
http://docwiki.embarcadero.com/Libraries/XE2/ja/SHDocVw.TWebBrowserNavigateComplete2
>C++ typedef void __fastcall (__closure *TWebBrowserNavigateComplete2)(System::TObject* ASender, const _di_IDispatch pDisp, const System::OleVariant &URL)

※Delphiのコードはなぜか記載なし。
C++のコードはほとんど理解してませんが、XE2から const が付いたのでそこから変更ではないかと。
D2009以前はどれも var であることを確認、D2010はヘルプが変なので不明ですが、やはり var でしょう。
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SHDocVw_TWebBrowserNavigateComplete2.html
http://docwiki.embarcadero.com/VCL/2010/ja/SHDocVw.TWebBrowserNavigateComplete2

もしや…と思ってOnBeforeNavigate2も調べると、やはり const に変わってる。
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  Headers: OleVariant; var Cancel: WordBool);

これじゃURLが書き換えられない…リダイレクトさせたい場合はどうすれば…?
ヘルプの記述「この値を変更すると…」とも矛盾するし、一体全体どうなっているんでしょうね??


久美子  2013-07-05 00:41:24  No: 44760

Harryさんありがとうございました。
「const URL: OleVariant」 と、varをconstに書き換えてエラーが発生しなくなりました。
ただ、 マウスをWebBrowser1から移動して最初のDBGrid1のクリックときに、クリックした行からまた、その直前の行に戻ってしまいます。
そこで、DBGridのonMouseEnterを次のように設定して、とりあえず思うような動きが実現しました。
procedure TForm1.DBGrid1MouseEnter(Sender: TObject);
begin
  DBGrid1.SetFocus;
end;

開発中のプログラムは、
氏名,住所 のCSVを読み込んで、GoogleMap APIで緯度、経度を取り込み、DBGrid1に氏名、住所、緯度、経度を表示して、
WebBrowserにGoogleMapを表示して、マーカー設定。
DBGrid1をクリックしてInfoWindowを表示
でした。

Harryさんご教授のソースを理解しきった訳ではありませんが、とりあえず期待に近い動きを実現できました。
ソースについては、をもう一度じっくり読んでみたいと思います。
「※なお、いま試案中の方法ではOnNavigateComplete2を使わず、このメソッドも不要になる…はず。」にも興味があります。
とりあえずは解決としますが、また教えてください。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加