ウィンドウハンドルを大捏造(1999/08/18)
新着情報 トップメニュー ボートメニュー 掲示板 お手紙はここ!

え? 何のことかって? 要するにアレよ。例えば非ビジュアルコンポーネントでMessageBox APIを使いたくなったと思いねェ。ところがどっこい、ウィンドウハンドルはTWinColtrolクラスを継承していないと使えないんで、困ったりするんですな。

いろいろ調べたですよ……尽きるところTWinControlクラスでやってることをそのままやれば良いんですが、結構煩雑でして。
それはそれとして、多分このような目的のためにあるかのようなシンプルな回答とするなら、実にFormsユニットAllocateHWnd()メソッドを使うだけでOKだったんです……。
あちこちの非ビジュアルコンポーネントで呼ばれておりますですよ。

ちなみに作ったハンドルは当然、自分で開放しましょう。DeallocateHWndメソッドを呼びます。



AllocateHWnd()の使い方

AllocateHWnd()メソッドは引数に「ウィンドウプロシージャ」というメソッドポインタを必要とします。早い話がハンドラを登録するわけ。Windowsは、メッセージの配達先を見極めて、そのハンドラめがけて投函してくれるわけです。

このメッセージと言うのは何も必要なものだけが飛んでくるとは限りません。「マウスがウィンドウ上を通過中です」と言うメッセージなんかは使わなくてもバンバン飛んでくるわけ……なので、ウィンドウプロシージャの処理が重たいと、飛んでくるメッセージの数だけ実行されるわけですから、OSの処理能力によってはパフォーマンスの低下を招きます

したがって、ウィンドウプロシージャの処理は「いかに不必要な処理を実行しないか」に掛かっていたりします。まぁ、そんな神経質にならなくてもいいんですけど、心には留めておきましょう。

大抵は、自分が必要としているメッセージだけを拾うので、いやがうえにもシンプルになるでしょう。
 TMyClass.WndProc( var Msg:TMessage );
begin
    if Msg.Msg = WM_XXX then
        try
            if Assigned(FOnEvening) then FOnEvening(Self);
        except
            Application.HandleException(Self);
        end
    else
        Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
WM_XXXがメッセージ名です。WM_TIMERWM_HOTKEYなど、いろいろあります。Win32 SDKのヘルプを見ればいろいろありますんで面白げに読んでみましょう

DefWindowProc()は要するに、「私ゃ知らんから後はいつも通りで」とWindowsに仕事を押し付けてしまうわけですな。逆にこれがないと、下手すればアプリがハングアップ、いやOSだってわからないくらいで……



実際の使用例は、拙作TWaitHotKeyのソースコードを参照してみてください……このコンポーネントではWM_HOTKEYを捕まえてイベントを発生させていますので。



しかしナンですな。ウインドウハンドルを持ったくらいでドラッグ&ドロップとか受け付けられるかと言うと、それは世の中そんなに甘くないです。何故かと言うと、こういうイベントメッセージてぇのは要するに画面に範囲(広義のウィンドウ[*1])を持ってないと駄目なんで……

じゃあ不可能かと言うと、そこはそれ、蛇の道は蛇。「やらぬなら・やらせて見せようプロシージャ」とばかり、先のウィンドウプロシージャをドロップを受け付けてくれるコントロールに強引に押し付けて、イベントを処理させちゃうわけですな。こう言うのをサブクラス化と言うらしいですが。

具体的には、プロシージャをオブジェクトとして確保しておいて、APIを使って相手側のプロシージャ参照ポインタに上書きしちゃうわけ。VCLのイベントハンドラを実行時に切り替えるみたいなもん……ですが、最後にちゃんと書き戻してあげないと大変なことになるかも まぁ、一つのアプリ内(プロセス内)ならほぼ同時に消えるってんで大丈夫かと存じますが、やっぱし作った順に消すとか、使った順に返すとか、そう言う美しさは大事だと思いますよ。えぇ今日日でも。

サンプル:フォームのダブルクリックを横取りするコンポーネント!
こいつはフォームにコンポーネントを貼り付けるだけでダブルクリックに反応します……
EnabledプロパティをON/OFFする仕組みを搭載して、フォームをダブルクリックしてみましょう。
unit vclMouseHook;

interface
uses
  classes, windows, messages, controls, shellapi, forms, sysutils;

  type
  TMouseHook = class(TComponent)
    private
      FOldWndProc: Pointer;
      FNewWndProc: Pointer;
      FWndHandle : HWND;
      FEnabled   : Boolean;

      procedure WndProc( var Msg:TMessage ); virtual;

    public
      constructor Create(AOwner:TComponent); override;
      destructor Destroy; override;
      procedure DoubleClick( KeyFlag:Integer; X,Y:LongInt);

    published
      property Enabled:Boolean read FEnabled write FEnabled default True;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('魔僧神仙',[TMouseHook]);
end;

{ TMouseHook }

constructor TMouseHook.Create(AOwner: TComponent);
begin
  inherited;
  FEnabled := True;

  FWndHandle  := TWinControl(AOwner).Handle;
  FNewWndProc := MakeObjectInstance(WndProc);
  FOldWndProc := Pointer(SetWindowLong(FWndHandle,GWL_WNDPROC,LongInt(FNewWndProc)));
end;

destructor TMouseHook.Destroy;
begin
  SetWindowLong(FWndHandle,GWL_WNDPROC,LongInt(FOldWndProc));

  inherited;
end;

procedure TMouseHook.DoubleClick(KeyFlag: Integer; X,Y:LongInt);
var
  Str: String;
begin
  Str := '('+IntToStr(X)+','+IntToStr(Y)+')';
  Application.MessageBox(PChar(Str),'',MB_ICONINFORMATION);
end;

procedure TMouseHook.WndProc( var Msg:TMessage );
begin
  with Msg do
  begin
    if (Msg=WM_LBUTTONDBLCLK)and(Enabled) then
    begin
      try
        DoubleClick(wParam,Loword(lParam),HiWord(lParam));
      except
        Application.HandleException(Self);
      end;
    end else
      Result := CallWindowProc(FOldWndProc,FWndHandle,Msg,wParam,lParam);
  end;
end;

end.



[*1] まぁ、Windowsプログラミングでは、ウィンドウに広義もなにもありませんが……TFormもTEditもTPanelも、ぜ〜んぶウィンドウ。VCL的に言えば、TWinControlの子孫たちは全員「ウィンドウ」なのれす。Got Me?


新着情報 トップメニュー ボートメニュー 掲示板 お手紙はここ!