SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

デスクトップ上のウィンドウをグリッドに沿って合わせるユーティリティ

デスクトップユーティリティ作成のヒント


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

親アプリケーションの作成

 それから、親アプリケーションの方を見ていきましょう。新規プロジェクトを作成し、「test2.dpr」の名前で保存しました。こちらは、先ほど作ったものと大して違いがないので、部分的に抜粋して紹介します。

 以下は、先ほど作った「mhook.dll」からフックインストール用の関数を静的にインポートする部分です。

フックDLLから関数のインポートと利用
const hook_dll = 'mhook.dll';
function HookInstall(Parent:THandle):
  BOOL; stdcall; external hook_dll;
function HookUninstall: BOOL; stdcall; external hook_dll;

procedure TfrmTest2.btnInstallClick(Sender: TObject);
begin
  if HookInstall(Self.Handle) = False then ShowMessage('失敗');
end;
procedure TfrmTest2.btnUninstallClick(Sender: TObject);
begin
  HookUninstall;
end;

 ここでは、ボタン[btnInstall]をクリックした時に、DLLのフックインストール関数を呼ぶようにしています。

 次に、フック関数から投げられたメッセージを受け取る関数onMouseAction()です。ここでは、フックされたメッセージや、どの部分をクリックしたという情報をTMemoに表示するだけです。

フックされたメッセージを表示する
type
  // フォームの宣言
  TfrmTest2 = class(TForm)
    Memo1: TMemo;
    btnInstall: TButton;
    btnUninstall: TButton;
    procedure btnInstallClick(Sender: TObject);
    procedure btnUninstallClick(Sender: TObject);
  private
    procedure onMouseAction(var msg: TMessage);
    message WM_USER_MOUSE_ACTION;
  end;

//...省略...

procedure TfrmTest2.onMouseAction(var msg: TMessage);
var
  s, t: string;
begin
  // どのメッセージがフックされたのか?
  case msg.WParam of
    WM_NCMOUSEMOVE:   s := 'WM_NCMOUSEMOVE';
    WM_NCLBUTTONDOWN: s := 'WM_NCLBUTTONDOWN';
    WM_NCLBUTTONUP:   s := 'WM_NCLBUTTONUP';
    WM_MOUSEMOVE:     s := 'WM_MOUSEMOVE';
    WM_LBUTTONDOWN:   s := 'WM_LBUTTONDOWN';
    WM_LBUTTONUP:     s := 'WM_LBUTTONUP';
    else              s := 'msg=' + IntToStr(msg.WParam);
  end;
  // どこをクリックしたのか?
  case msg.LParam of
    HTCLIENT:         t := 'HTCLIENT';
    HTCAPTION:        t := 'HTCAPTION';
    HTLEFT:           t := 'HTLEFT';
    HTRIGHT:          t := 'HTRIGHT';
    HTTOP:            t := 'HTTOP';
    HTTOPLEFT:        t := 'HTTOPLEFT';
    HTTOPRIGHT:       t := 'HTTOPRIGHT';
    HTBOTTOM:         t := 'HTBOTTOM';
    HTBOTTOMLEFT:     t := 'HTBOTTOMLEFT';
    HTBOTTOMRIGHT:    t := 'HTBOTTOMRIGHT';
    HTBORDER:         t := 'HTBORDER';
    else              t := 'ht=' + IntToStr(msg.LParam);
  end;
  // メモに表示
  Memo1.Lines.Add(s + #9 + t);
end;

 プログラムを実行してみると、フックしたメッセージと、どの部分をクリックしたのかが表示されるようになります。また、自身のウィンドウだけでなく、メモ帳など別のウィンドウのイベントをフックできていることを確認してみてください。

実行してみたところ
実行してみたところ

ウィンドウ同士をぴったりくっつけるソフト

 ここまで分かれば、フックされたメッセージを調べて、親アプリケーションの方でウィンドウのリサイズ処理を行えば、ウィンドウ同士をぴったりくっつけるツールが完成します。

 ですが、ここでいくつか問題があります。

 まず、先のフック関数では、マウスイベントのすべてを親ウィンドウに投げていたので、あまり効率がよくありません。そこで、必要なイベントだけを選んで投げることにすれば、パフォーマンスが良くなります。

 次に、ウィンドウのリサイズの際には、どんなメッセージが送られているのかを調べて、どんな風にリサイズを検出すればよいのかを考えます。

 そこで、先ほど作ったマウスイベントを表示するテストアプリケーションを使って、ウィンドウのリサイズの時にどんなメッセージが送られてきているのかを調べてみてください。私が試したところ、次のようなメッセージが得られました。

ウィンドウのリサイズを行った時
WM_NCMOUSEMOVE          HTBOTTOM
WM_NCLBUTTONDOWN        HTBOTTOM
WM_MOUSEMOVE            HTCLIENT
WM_MOUSEMOVE            HTCLIENT
WM_MOUSEMOVE            HTCLIENT
WM_MOUSEMOVE            HTCLIENT
WM_LBUTTONUP            HTCLIENT
WM_NCMOUSEMOVE          HTBOTTOM

 つまり、ウィンドウのリサイズを行う際には、「WM_NCLBUTTONDOWN」→「WM_MOUSEMOVE」→「WM_LBUTTONUP」という順番でメッセージが送られていることが分かります。また、ヒットテストの結果を見ても、「WM_NCLBUTTONDOWN」の状態でテストすれば、どこをクリックしたのかが判別できそうです。

 そこで、共有変数の構造体にマウスの状態を記憶するメンバ変数「Status」を追加して、マウスの状態によって、ユーザーがウィンドウのリサイズをしたかどうかを判別できるようにしました。

マウス状態を調べる機能を追加したフック関数
function HookProc(code: Integer; wp:WParam; lp:LParam):
  LRESULT; stdcall;
var
  pms: PMouseHookStruct;
begin
  // Codeが0以下なら即座に戻らなくてはならない
  if code < 0 then
  begin
    Result := CallNextHookEx(ShareData^.hHook, code, wp, lp);
    Exit;
  end;
  // マウス情報を持つ構造体にキャスト
  pms := PMouseHookStruct(lp);
  // 任意のメッセージを拾って処理する
  case wp of
    WM_NCLBUTTONDOWN:
      begin
        case pms^.wHitTestCode of // どこをクリックしたか確認
          HTCAPTION,
          HTLEFT, HTTOP,HTRIGHT,HTBOTTOM,
          HTBOTTOMRIGHT, HTBOTTOMLEFT, HTTOPRIGHT, HTTOPLEFT:
          begin
            ShareData^.Status := msDown;
          end;
        end;
      end;
    WM_MOUSEMOVE:
      begin
        if ShareData^.Status = msDown then
        begin
          ShareData^.Status := msMove;
        end;
      end;
    WM_LBUTTONUP:
      begin
        if ShareData^.Status = msMove then
        begin
          PostMessage(ShareData^.hParent, WM_USER_MOUSE_ACTION,
            pms.hwnd, 0);
          ShareData^.Status := msNone;
        end;
      end;
  end;
  Result := CallNextHookEx(ShareData^.hHook, code, wp, lp);
end;

 そして、以下が親ウィンドウ側のソースです。DLL側で投げられたメッセージを受け取り、その情報を元に、ウィンドウをリサイズします。この関数以外は、テストのプログラムと同じです。

親ウィンドウ側のウィンドウリサイズの処理
procedure TfrmGrid.onMouseAction(var msg: TMessage);
var
  handle: HWND;
  rect: TRect;
  size: Integer;
begin
  size := 24; // グリッドのサイズ
  handle := msg.WParam;
  // 既存の大きさを取得
  GetWindowRect(handle, rect);
  // グリッド適用後の位置を計算
  rect.Left   := (rect.Left div size) * size;
  rect.Top    := (rect.Top div size) * size;
  rect.Right  := (rect.Right div size) * size;
  rect.Bottom := (rect.Bottom div size) * size;
  // ウィンドウを移動する
  MoveWindow(handle, rect.Left, rect.Top,
    rect.Right - rect.Left, rect.Bottom- rect.Top, True);
end;

 ここでは、グリッド適用後のサイズを、切り捨てで計算してますが、もうちょっと工夫すると、ベストな位置にリサイズできると思います。

さらなる改造のヒント

 こうして、デスクトップにあるウィンドウをグリッドぴったりに合わせるツールが完成しました。ところが、不満点もたくさんあります。

 ここでは、グリッドサイズが24に固定されていますが、設定で変更したいとか、せっかくグリッドに合わせるなら、グリッドの線も画面に描画したいという要望もあるでしょう。

 また、まれに移動させたくないウィンドウが移動してしまうことがあります。そこで、ウィンドウのクラス名やウィンドウを指定して、除外リストなるものを用意するとさらに利便性が高まるでしょう。

 その他のアイデアとして、冒頭で紹介した「窓グリッド」では、タイトルバーを右クリックした時に、ウィンドウをマック風に折りたたんだり、スクロールボタンクリックで、ウィンドウを透明にするという機能を持たせています。これもすべて、フックDLLのマウスイベント内で処理しています。

 注意点として、フックDLLを作る時は用心深くコードを書いてください。すべてのイベントを横取りすることになるので、マウス操作などができなくなり、OSを再起動する事態に陥ってしまいます。

 デスクトップユーティリティの作成は、Windowsを裏から操っているような気分を味わうことができ、公開するとユーザーからの反応もそこそこありますので、作り甲斐がある分野のソフトだと思います。

 普段、デスクトップの操作感に不満がある方は、ぜひ、本稿を参考にして便利なユーティリティを作成してみてください。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

クジラ飛行机(クジラヒコウヅクエ)

ソフト企画「くじらはんど」にて、多数のフリーソフトを公開しています。日本語プログラミング言語「なでしこ」、テキスト音楽「サクラ」、日本語Wiki記法が特徴の「KonaWiki」などを公開しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/369 2007/12/10 13:50

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング