[BlueLeaf1336]> PROGRAM>

CaptionButtonクラス - キャプションバーにボタンを置いてみるテスト

historyTOP

2007/11/27:作成

overviewTOP

タイトルにあるとおりです。自分で作ったプログラムのキャプションバー(タイトルバー)の部分に、ボタンをつけてクリックイベントを拾ってみました。

もちろん自分ではほとんどやらずに、参考サイトからパクってきたソースコードをチョロっとイジっただけです。って実際にはかなり時間かけてしまってますが...

やっぱり飽きないためには、すでに動くものを少しずつ修正していくのが一番です。こういう好奇心優先のものの場合は、特にそうです。いきなり C++ のソースから翻訳し始めると、終盤にならないと動かないので翻訳中に飽きてしまう可能性がかなり高いです。なので、今回は Delphi ですでに完成されているのを下敷きにしました。もうその時点で好奇心もへったくれもないわけですが、そこはそれ。

参考サイトTOP

いつも興味深いサンプルが見つかる Code Project のは、C++ でかなりがっちり書かれていて、Delphi で書き直す気になれませんでした。

もうひとつの方(サイトの名前失念です)は、もろの回答です。で、こっちです。ただ、そのままはさすがにアレなので、2個以上のボタンを作れるようにしました。

ダウンロードTOP

20071127CaptionButton.zip(199,370Bytes)
テストに使ったソースコードと実行ファイルです。

スクリーンショットTOP

起動時です。次回自分で見たときの覚書を思いっきり書いてあります。
それはそうとキャプションバーに必要以上にボタンが並んでいるのが見えます。
右側の3つは標準のボタンですが、それ以外は全部今回のテストで表示しています。

何個目かの作ったボタンを押しているところです。押し心地はイマイチです。

とりあえずバージョン情報を表示してみました。ちゃんとクリックを取れているように見えます。見た感じいい感じです。

使い方TOP

上のサンプルは、後述するクラスを使って結構あっさりと実現できています。といってみる。

クラスを作って、キャプションを表示したいフォームを引数に渡します。描画したいフォントを伝えて、クリックイベントを接続して、後はボタンを追加(AddButton())して、ついでにキャプションを設定するだけです。邪魔くさいか。

そうすると、クリックされたときに、作った順番に対応した Index 付き(ゼロベース)でクリックイベントが発生するので、いろいろやってみると少し楽しいです。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, CaptionButton, StdCtrls;

type
  TForm1 = class(TForm)
    (略)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FCapBtns: TCaptionButtons;
    procedure OnCapBtnClick(const Index: Integer);
  public
  end;

var
  Form1: TForm1;

implementation

uses
    Info;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  起動時
procedure TForm1.FormCreate(Sender: TObject);
begin
    //  ボタン管理クラス作成
    FCapBtns := TCaptionButtons.Create(Self);
    FCapBtns.Font.Assign(LblFont.Font);
    FCapBtns.OnClick := OnCapBtnClick;

    //  ボタン追加
    FCapBtns.AddButton().Caption := '1';
    FCapBtns.AddButton().Caption := '2';
    FCapBtns.AddButton().Caption := '3';
    FCapBtns.AddButton().Caption := '4';
    FCapBtns.AddButton().Caption := '5';
    FCapBtns.AddButton().Caption := '6';
    FCapBtns.AddButton().Caption := '7';
    FCapBtns.AddButton().Caption := '8';
    FCapBtns.AddButton().Caption := '9';
end;

//-----------------------------------------------------------------------------
//  終了時
procedure TForm1.FormDestroy(Sender: TObject);
begin
    FCapBtns.Free();
end;

//-----------------------------------------------------------------------------
//  キャプションボタンクリック時
procedure TForm1.OnCapBtnClick(const Index: Integer);
begin
    //  作った順番と処理の対応は自分でやる。残念。
    case Index of

    0:  OpenFilePropertyDialog(ParamStr(0));
    1:  OpenFilePropertyDialog(ExtractFilePath(ParamStr(0)));
    2:  AppMessageBox(GetOsInfo(), MB_OK or MB_ICONINFORMATION);
    else
        OpenInfoForm();
    end;
end;

end.

ソースコードTOP

そんなに書くこともないので、キャプションバーにボタンを置いてクリックを判定するクラスのソースコードを掲載します。

他人の成果を勝手に公開しているようなものなので、言い訳代わりに参考にしたプログラムと違う点を挙げてみます。

//-----------------------------------------------------------------------------
//  タイトルバーにボタンを配置してクリックに反応するためのクラス
//  BlueLeaf1336    http://www.geocities.jp/fjtkt/

//-----------------------------------------------------------------------------
//  2007.11.26  DrawNewButton を元にクラスにまとめた
//              複数のボタンに対応
//  2007.11.27  TO DO ■Caption or Glyph
//  2007.11.27  TO DO ■OnClick()
//              TO DO □トグル形式のボタン

//-----------------------------------------------------------------------------
//  参考プログラム

//1----------------------------------------------------------------------------
//	class CCaptionButton
//		Add push buttons to the caption bar
//
//	Author:		Yao Zhifeng
//	Contact:	yaozhifeng@hotmail.com

//2----------------------------------------------------------------------------
//  ダウンロードサイト失念
//  NewButton.lzh
//  unit DrawNewButton

unit CaptionButton;

interface

uses
    Windows, Messages, Classes, SysUtils, Contnrs, Forms, Graphics;

type
    TOnCaptionButtonClick = procedure (const Index: Integer) of object;

    TCaptionButtons = class;

    TCaptionButton = class
    protected
        Pushed: Boolean;
        Parent: TCaptionButtons;
    public
        Caption: String;
        procedure Draw(const AHDC: HDC; const ARect: TRect);
        constructor Create();
        destructor Destroy(); override;
    end;


    TCaptionButtons = class
    protected
        FTarget :TForm;
        FOriginalWndProc: Pointer;
        FHookWndProc :Pointer;
        FWndHandle :HWND;
        procedure WndProc(var Msg: TMessage); virtual;
    protected
        FButtons: TObjectList;
        FFont: TFont;
        FOnClick: TOnCaptionButtonClick;
        function CaptionHeight(): Integer;
        function CaptionBarRect(): TRect;
        function DefaultButtonCount(): Integer;
        function LeftEndDefaultButtonRect(): TRect;
    protected
        procedure DrawButtons();
        function LeftButtonDown(const APoint: TPoint): Boolean;
        function LeftButtonUp(const APoint: TPoint): Boolean;
    public
        property Font: TFont read FFont;
        property OnClick: TOnCaptionButtonClick read FOnClick write FOnClick;
    public
        constructor Create(ATarget: TForm);
        destructor Destroy(); override;
        function AddButton(): TCaptionButton;
    end;

implementation

//-----------------------------------------------------------------------------
//■  TCaptionButton

//-----------------------------------------------------------------------------
//  コンストラクタ
constructor TCaptionButton.Create();
begin
    inherited Create();
    Pushed := False;
end;

//-----------------------------------------------------------------------------
//  デストラクタ
destructor TCaptionButton.Destroy();
begin
    inherited Destroy();
end;

//-----------------------------------------------------------------------------
//  描画
procedure TCaptionButton.Draw(const AHDC: HDC; const ARect: TRect);
var
    LRect: TRect;
    LState: Cardinal;
    LFormat: Cardinal;
    LParams: TDrawTextParams;
    LOldMode: Integer;
begin
    //  ボタンを描画する
    LState := DFCS_BUTTONPUSH;
    if (Pushed) then LState := LState or DFCS_PUSHED;
    DrawFrameControl(AHDC, ARect, DFC_BUTTON, LState);

    //  キャプションを描画するための設定
    LRect := ARect;
    OffsetRect(LRect, -1, -1);  //  実験値。しかもイマイチ。
    LFormat := DT_CENTER or DT_VCENTER or DT_SINGLELINE;
    FillChar(LParams, SizeOf(LParams), 0);
    LParams.cbSize := SizeOf(LParams);
    //  押されているときは少しずらす
    if (Pushed) then OffsetRect(LRect, 1, 1);

    //  背景透過
    LOldMode := SetBkMode(AHDC, TRANSPARENT);
    DrawTextEx(AHDC, PChar(Caption), -1, LRect, LFormat, @LParams);
    SetBkMode(AHDC, LOldMode);
end;

//-----------------------------------------------------------------------------
//■  TCaptionButtons

//-----------------------------------------------------------------------------
//  コンストラクタ
constructor TCaptionButtons.Create(ATarget: TForm);
begin
    inherited Create();

    //  ボタン保持用リスト作成
    FButtons := TObjectList.Create(True);
    //  フォント作成
    FFont := TFont.Create();
    //  クリックイベント
    FOnClick := nil;

    // のっとりコントロール保存
    FTarget := ATarget;
    // そのハンドル保存
    FWndHandle := FTarget.Handle;
    // 置き換えWindowProcedureをオブジェクトに
    FHookWndProc := Classes.MakeObjectInstance(WndProc);
    // 置き換えてオリジナルを保存
    FOriginalWndProc := Pointer(SetWindowLong(FWndHandle, GWL_WNDPROC,
                                LongInt(FHookWndProc)));
end;

//-----------------------------------------------------------------------------
//  デストラクタ
destructor TCaptionButtons.Destroy();
begin
    // 元に戻そう
    SetWindowLong(FWndHandle, GWL_WNDPROC, LongInt(FOriginalWndProc));
    //  オブジェクトも破棄
    Classes.FreeObjectInstance(FHookWndProc);

    //  フォント破棄
    FFont.Free();
    //  ボタン保持用リスト破棄
    FButtons.Free();
    
    inherited Destroy();
end;

//-----------------------------------------------------------------------------
//  ボタン描画追加
function TCaptionButtons.AddButton(): TCaptionButton;
begin
    Result := TCaptionButton.Create();
    Result.Parent := Self;
    FButtons.Add(Result);
end;

//-----------------------------------------------------------------------------
//  ボタン描画
procedure TCaptionButtons.DrawButtons();
var
    i: Integer;
    LRect: TRect;
    LBtnWidth: Integer;
    LButton: TCaptionButton;
    LHDC: HDC;
    LOldObj: HGDIOBJ;
begin
    //  標準ボタン左端
    LRect := LeftEndDefaultButtonRect();

    //  ボタンの幅
    LBtnWidth := CaptionHeight();// GetSystemMetrics(SM_CXSIZE);

    //  描画先確保
    LHDC := GetWindowDC(FWndHandle);
    LOldObj := SelectObject(LHDC, FFont.Handle);

    //  少しずつずらして描画
    for i := 0 to FButtons.Count - 1 do
    begin
        LButton := TCaptionButton(FButtons.Items[i]);
        OffsetRect(LRect, -LBtnWidth, 0);
        LButton.Draw(LHDC, LRect);
    end;

    //  描画先解放
    SelectObject(LHDC, LOldObj);
    ReleaseDC(FWndHandle, LHDC);
end;

//-----------------------------------------------------------------------------
//  作ったボタンが押されかかってるかチェック  
function TCaptionButtons.LeftButtonDown(const APoint: TPoint): Boolean;
var
    i: Integer;
    LRect: TRect;
    LBtnWidth: Integer;
    LButton: TCaptionButton;
    LWndRect: TRect;
begin
    Result := False;

    //  標準ボタン左端
    LRect := LeftEndDefaultButtonRect();

    //  ウィンドウの左上に原点をあわせる
    GetWindowRect(FWndHandle, LWndRect);
    OffsetRect(LRect, LWndRect.Left, LWndRect.Top);

    //  ボタンの幅
    LBtnWidth := CaptionHeight();// GetSystemMetrics(SM_CXSIZE);

    //  少しずつずらしてチェック
    for i := 0 to FButtons.Count - 1 do
    begin
        LButton := TCaptionButton(FButtons.Items[i]);
        OffsetRect(LRect, -LBtnWidth, 0);
        LButton.Pushed := PtInRect(LRect, APoint);
        if (LButton.Pushed) then Result := True;
    end;
end;

//-----------------------------------------------------------------------------
//  ボタンが押されたかどうかチェック  
function TCaptionButtons.LeftButtonUp(const APoint: TPoint): Boolean;
var
    i: Integer;
    LRect: TRect;
    LBtnWidth: Integer;
    LButton: TCaptionButton;
    LWndRect: TRect;
    LClickIndex: Integer;
begin
    Result := False;
    LClickIndex := -1;

    //  標準ボタン左端
    LRect := LeftEndDefaultButtonRect();

    //  ウィンドウの左上に原点をあわせる
    GetWindowRect(FWndHandle, LWndRect);
    OffsetRect(LRect, LWndRect.Left, LWndRect.Top);

    //  WM_LBUTTONUP でクリックを検出しているので座標調整
    //  ※クライアント領域左上が原点になる
    OffsetRect(LRect, - FTarget.ClientOrigin.X, - FTarget.ClientOrigin.Y);

    //  ボタンの幅
    LBtnWidth := CaptionHeight();// GetSystemMetrics(SM_CXSIZE);

    //  少しずつずらしてチェック
    for i := 0 to FButtons.Count - 1 do
    begin
        LButton := TCaptionButton(FButtons.Items[i]);
        OffsetRect(LRect, -LBtnWidth, 0);
        if (PtInRect(LRect, APoint) and LButton.Pushed) then
        begin
            LClickIndex := i;
            Result := True;
        end;
        LButton.Pushed := False;
    end;

    //  クリックイベント生成
    if ((LClickIndex > -1) and Assigned(FOnClick)) then
    begin
        FOnClick(LClickIndex);
    end;
end;

//-----------------------------------------------------------------------------
//  差し込むウィンドウプロシージャ
procedure TCaptionButtons.WndProc(var Msg: TMessage);
var
    LHandled: Boolean;
    LWMNCLBUTTONDOWN: TWMNCLBUTTONDOWN;
    LWMLBUTTONUP: TWMLBUTTONUP;
begin
    LHandled := False;

    case Msg.Msg of

        WM_CAPTURECHANGED,
        WM_ENTERIDLE,
        WM_NCPAINT,
        WM_SETICON,
        WM_PAINT,
        WM_SYSCOMMAND,
        WM_NCACTIVATE,
        WM_SIZE,
        WM_ACTIVATE,
        WM_SETCURSOR:
        begin
            DrawButtons();
        end;

        WM_NCLBUTTONDOWN:
        begin
            LWMNCLBUTTONDOWN := TWMNCLBUTTONDOWN(Msg);
            LHandled := LeftButtonDown(Point(LWMNCLBUTTONDOWN.XCursor, LWMNCLBUTTONDOWN.YCursor));
            if (LHandled) then SetCapture(FWndHandle);
            DrawButtons();
        end;

        WM_LBUTTONUP:
        begin
            LWMLBUTTONUP := TWMLBUTTONUP(Msg);
            LHandled := LeftButtonUp(Point(LWMLBUTTONUP.XPos, LWMLBUTTONUP.YPos));
            ReleaseCapture();
            DrawButtons();
        end;

//        WM_NCHITTEST    : OnNcPaint(Msg, LHandled);
//        WM_NCLBUTTONDOWN: OnNcLButtonDown(Msg, LHandled);
//        WM_LBUTTONUP    : OnLButtonUp(Msg, LHandled);
//        WM_MOUSEMOVE    : OnMouseMove(Msg, LHandled);
//        WM_ACTIVATE     : OnActivate(Msg, LHandled);
    end;

    // それはそうとオリジナルも実行する
    if (not LHandled) then
    begin
        Msg.Result := CallWindowProc(FOriginalWndProc, FWndHandle,
                                    Msg.Msg, Msg.wParam, Msg.lParam);
    end;
end;

//------------------------------------------------------------------------------
//  キャプションの高さ
function TCaptionButtons.CaptionHeight(): Integer;
begin
    //  ツールバー型
    if (FTarget.BorderStyle in [bsToolWindow, bsSizeToolWin]) then
    begin
        Result := GetSystemMetrics(SM_CYSMCAPTION);
    end
    //  標準のウインドウ
    else
    begin
        Result := GetSystemMetrics(SM_CYCAPTION);
    end;
end;

//------------------------------------------------------------------------------
//  タイトルバーのサイズを取得
function TCaptionButtons.CaptionBarRect(): TRect;
var
    LCxSize: Integer;
    LCySize: Integer;
begin
    //  一重線のウインドウの枠の太さ
    if (FTarget.BorderStyle in [bsDialog, bsSingle, bsToolWindow]) Then
    begin
        LCxSize := GetSystemMetrics(SM_CXFIXEDFRAME);
        LCySize := GetSystemMetrics(SM_CYFIXEDFRAME);
    end
    else
    //  二重線のウインドウの枠の太さ
    begin
        LCxSize := GetSystemMetrics(SM_CXSIZEFRAME);
        LCySize := GetSystemMetrics(SM_CYSIZEFRAME);
    end;

    //  ウインドウサイズ取得
    GetWindowRect(FWndHandle, Result);

    //  左上を原点に移動
    OffsetRect(Result, - Result.Left, - Result.Top);

    //  枠の分を削る
    InflateRect(Result, - LCxSize, - LCySize);

    //  高さ調整
    Result.Bottom := Result.Top + CaptionHeight() - 1;
end;

//-----------------------------------------------------------------------------
//  タイトルバーにある標準ボタンの数を数える
function TCaptionButtons.DefaultButtonCount(): Integer;
var
    LButtons: TBorderIcons;
begin
    Result := 0;
    LButtons := FTarget.BorderIcons;

    case FTarget.BorderStyle of

    bsDialog:
        begin
            if (biSystemMenu in LButtons) then
            begin
                //  閉じる
                Inc(Result);
                //  ヘルプ
                if (biHelp in LButtons) then Inc(Result);
            end;
        end;

    bsNone:
        begin
            //  なし
        end;

    bsSingle, bsSizeable:
        begin
            if (biSystemMenu in LButtons) then
            begin
                //  閉じる
                Inc(Result);

                //  閉じる以外にボタンがひとつ
                if (LButtons = [biSystemMenu, biMinimize])
                or (LButtons = [biSystemMenu, biMaximize])
                or (LButtons = [biSystemMenu, biHelp]) then
                    Inc(Result, 1)

                //  閉じる以外にボタンがふたつ
                else
                if (LButtons = [biSystemMenu, biMinimize, biMaximize, biHelp])
                or (LButtons = [biSystemMenu, biMinimize, biMaximize]) then
                    Inc(Result, 2)

                //  閉じる以外にボタンがみっつ
                else
                if (LButtons = [biSystemMenu, biMinimize, biHelp])
                or (LButtons = [biSystemMenu, biMaximize, biHelp]) then
                    Inc(Result, 3)
                ;
            end;
        end;

    bsSizeToolWin, bsToolWindow:
        begin
            //  閉じる
            if (biSystemMenu in LButtons) then Inc(Result);
        end;
    end;
end;

//------------------------------------------------------------------------------
//  左端にある標準ボタンの領域を取得
function TCaptionButtons.LeftEndDefaultButtonRect(): TRect;
var
    LCxSize: Integer;
    LBtnNum: Integer;
begin
    //  通常ボタンの横を取得
    LCxSize := GetSystemMetrics(SM_CXSIZE);

    //  ボタンの数
    LBtnNum := DefaultButtonCount();

    //  標準のボタンのある領域を決定
    Result := CaptionBarRect();
    InflateRect(Result, 0, - 2);    //  実験値

    Result.Left := Result.Right - LBtnNum * LCxSize;

    //  一番左端のボタンの領域を決定
    Result.Right := Result.Left + LCxSize - 2;

    //  ツールバー型のスタイルだったら
    if (fTarget.BorderStyle in [bsToolWindow, bsSizeToolWin]) then
    begin
        InflateRect(Result, -3, 0); //  実験値
        OffsetRect(Result, 6, 0);   //  実験値
    end;
end;

end.

イメージトレーニングTOP

まったく当てもなく無責任に妄想してみます。

アレか。タスクバーでええやん。アドレスバーとか。

EOFTOP