ツールバーを作成してみる ACT-1

ツールバー

今回作成するのは、コモンコントロールの1つであるツールバーです。ツールバーはボタンを持った右図のようなコントロールです。よく見かけるコントロールですね。たいていのWindowsアプリはこのツールバーを持っています。今回はこのツールバーを作成して、ボタンが押されたらそのことをユーザーに通知するようなスクリプトを作ります。


ツールバーを作成する手順は以下のようになります。

  1. InitCommonControls 関数でコモンコントロールライブラリを初期化する。
  2. CreateWindowEx 関数で、ウィンドウクラス名を "ToolbarWindow32" にし、ウィンドウスタイルを WS_CHILD | WS_VISIBLE | CCS_TOP にする。
  3. ツールバーに TB_BUTTONSTRUCTSIZE メッセージを送信する。
  4. 必要なら TB_SETBITMAPSIZE メッセージでイメージサイズを指定する。
  5. ツールボタンに表示するイメージのビットマップオブジェクトを作成する。
  6. TBADDBITMAP 構造体にビットマップの情報を格納して、ツールバーに TB_ADDBITMAP メッセージを送信する。
  7. TBBUTTON 構造体に追加するボタンの情報を格納して、ツールバーに TB_ADDBUTTONS メッセージもしくは TB_INSERTBUTTON メッセージを送信する。
  8. ツールバーに TB_AUTOSIZE メッセージを送信して、ツールバーのサイズを調整する。
  9. 親ウィンドウに WM_COMMAND メッセージが送られたらその旨を表示する。

コモンコントロールライブラリ初期化

まずは、全コモンコントロール共通の InitCommonControls 関数を呼び出します。これによってツールバーのウィンドウクラスが登録されるので、ツールバーを作成できるようになります。

コントロール作成

次に CreateWindowEx 関数を呼び出します。このとき、ウィンドウクラス名には "ToolbarWindow32" を指定し、ウィンドウスタイルには WS_CHILD | WS_VISIBL | CCS_TOP を指定します。また、 CCS_NORESIZE スタイルを指定しなければウィンドウの位置やサイズは自動的に決められるので、位置とサイズにはすべて 0 を指定してかまいません。

TB_BUTTONSTRUCTSIZE メッセージ送信

ツールバーが作成できたら、まずは TB_BUTTONSTRUCTSIZE メッセージを送信しなければなりません。

#define TB_BUTTONSTRUCTSIZE       0x041E

TB_BUTTONSTRUCTSIZE
    wParam = 20;
    lParam = 0;

このメッセージは、ツールバーに TBBUTTON 構造体のサイズを伝えるためのものです。この構造体のサイズは 20 バイトなので、 wParam パラメータに 20 を指定します。また、 lParam パラメータには 0 を指定してください。

イメージサイズ設定

ツールバーは、ボタンに表示するイメージのリストを内部に持っており、ボタンイメージはこのリスト中のものが表示されるようになっています。したがって、イメージをこのリストの中に追加しなくてはなりません。

まずは、イメージのサイズを設定する必要があります。デフォルトでは横16ピクセル、縦15ピクセルになっていますが、それ以外のサイズのイメージを使用する場合には TB_SETBITMAPSIZE メッセージを送信してサイズを設定する必要があります。

#define TB_SETBITMAPSIZE       0x0420

TB_SETBITMAPSIZE
    wParam = 0;
    lParam = dxBitmap | ( dyBitmap << 16 );

dxBitmap パラメータと dyBitmap パラメータには、それぞれビットマップイメージの幅と高さをピクセル単位で指定します。

ビットマップ作成

次に、表示するイメージのビットマップオブジェクトを作成する必要があります。このビットマップは、ディスプレイ互換のDDBとして作成しましょう。

ディスプレイ互換ビットマップの作成方法については、『ビットマップオブジェクトを作成する』の項を参照してください。

ビットマップの背景となる部分を、システムカラーの3Dオブジェクト色にしておきたいので、まず GetSysColor 関数でシステムカラーを取得し、背景部分をこの色で塗りつぶしておきます。 gmode で透明色付きコピーを指定すればできると思いますが、ここでは手間を省くために、ビットマップファイルを256色で作成し、パレットモードで初期化したバッファウィンドウに読み込んで、 palette 命令で透明色用のパレットインデックスの色をシステムカラーに設定するという方法を使いましょう。

例えば、ビットマップイメージとして背景色がパレットコードの 0 になるように作られた256色ビットマップファイルを使う場合、

; 画像のロード
buffer 2,,, 1               ; パレットモードで初期化
picload "toolbtn.bmp"

; パレット 0 をシステムカラーのボタン表面色に変更
pm = 15                     ; COLOR_BTNFACE
dllproc "GetSysColor", pm, 1, D_USER
palette 0, stat&$FF, (stat>>8)&$FF, (stat>>16)&$FF
palfade

というようにします。

内部リストにイメージ追加

ビットマップが作成できたら、これをリストに追加します。これには、 TBADDBITMAP 構造体にビットマップの情報を格納して TB_ADDBITMAP メッセージを送信します。

typedef struct {
    HINSTANCE hInst;  // モジュールのインスタンスハンドル
    UINT      nID;    // リソースIDまたはビットマップハンドル
} TBADDBITMAP, *LPTBADDBITMAP;

hInst メンバは、ここでは 0 (NULL) を指定します。 nID メンバに、作成したビットマップのハンドルを指定します。

#define TB_ADDBITMAP       0x0413

TB_ADDBITMAP
    wParam = nButtons;
    lParam = ptbadd;

nButtons パラメータには、ビットマップ中に含まれるボタンイメージの数を指定します。 ptbadd パラメータには、情報を格納した TBADDBITMAP 構造体のアドレスを指定します。 TB_ADDBITMAP メッセージを送信すると、戻り値として、追加されたイメージのリスト中でのインデックスが返ります。一番最初に追加されたイメージのインデックスは 0 になります。また、一度に複数のイメージを追加した場合には、1番目のイメージのインデックスが返ります。

ボタンの追加

イメージをリストに追加できたら、ボタンを追加します。ボタンを追加するには、 TBBUTTON 構造体に追加するボタンの情報を格納して、ツールバーに TB_ADDBUTTONS メッセージまたは TB_INSERTBUTTON メッセージを送ります。

TBBUTTON 構造体は以下のように定義されています。

typedef struct _TBBUTTON {
    int   iBitmap;        // ビットマップのインデックス
    int   idCommand;      // コマンドID
    BYTE  fsState;        // ボタンの状態
    BYTE  fsStyle;        // ボタンのスタイル
    BYTE  bReserved[2]    // 予約(= 0 )
    DWORD_PTR dwData;     // アプリケーション定義値
    INT_PTR   iString;    // テキストのインデックス
} TBBUTTON, NEAR* PTBBUTTON, FAR* LPTBBUTTON;

iBitmap メンバには、ボタンに表示させるイメージの、リスト中でのインデックスを指定します。

idCommand メンバには、ボタンが押されたときに親ウィンドウに送られる WM_COMMAND メッセージのパラメータとして含まれるコマンドIDを指定します。

fsState メンバにはボタンの状態を指定します。このメンバに 0x04 (TBSTATE_ENABLED) を指定していなければボタンが表示されないので注意してください。

fsStyle メンバにはボタンのスタイルを指定します。

dwData メンバにはアプリケーション定義の32ビット値を指定します。

iString メンバはボタンに表示させる文字列の、リスト中でのインデックスを指定します。今回は使用しません。

TB_ADDBUTTONS メッセージは、指定したツールバーの最後にボタンを追加します。このメッセージは、同時に複数個のボタンを追加することができます。

#define TB_ADDBUTTONS       0x0414

TB_ADDBUTTONS
    wParam = nNumButtons;
    lParam = lpButtons;

nNumButtons パラメータには追加するボタンの数を、 lpButtons パラメータにはその数だけの要素を持つ TBBUTTON 構造体の配列のアドレスを指定します。 TBBUTTON 構造体のサイズは20バイトであるので、数値型配列変数に格納する場合には5要素ずつ区切られます。例えば、数値型変数 tbb にとったとき、

tbb.0tbb.4 : 1つ目の TBBUTTON 構造体
tbb.5tbb.9 : 2つ目の TBBUTTON 構造体
tbb.10tbb.14 : 3つ目の TBBUTTON 構造体
…………

というようになります。

TB_INSERTBUTTON メッセージは、ツールバー上の指定した位置に1個のボタンを挿入します。

#define TB_INSERTBUTTON       0x0415

TB_INSERTBUTTON
    wParam = iButton;
    lParam = lpButton;

iButton パラメータには、ボタンを挿入する位置のインデックスを指定します。このインデックスで指定されたボタンの右側に新しいボタンが挿入されます。 lpButton パラメータには、ボタンの情報を格納した TBBUTTON 構造体のアドレスを指定します。

サイズ調整

最後に、ツールバーに TB_AUTOSIZE メッセージを送信して、ツールバーのサイズを調整します。このメッセージにより、ボタンのサイズに合わせてツールバーの高さが変更されます。

#define TB_AUTOSIZE          0x0421

TB_AUTOSIZE
    wParam = 0;
    lParam = 0;

以上の操作で、ツールバーの作成が完了します。あとは、メインループ中でメッセージが送られてくるのを監視します。ツールバーのボタンが押されると、親ウィンドウに WM_COMMAND メッセージが送られてきます。このときの wParam パラメータの下位ワードは、ボタンに設定したコマンドIDになります。また、 lParam パラメータにはツールバーのハンドルが格納されます。これによって、どのボタンが押されたのかを知ることができます。

サンプルスクリプト

さて、実際にスクリプトを書いてみます。ツールバーに表示させるイメージとして下のビットマップを使います。このビットマップファイルは、それぞれのイメージサイズが 16×15 ピクセル、イメージ数が9個、背景色の白色がパレットコードの 0 になるように作られた256色ビットマップファイルです。

(toolbtn.bmp)

ビットマップファイルのダウンロード

以前から幾度となく使用しているビットマップオブジェクト作成モジュールを、今回も使用しています。

    #include "llmod.as"
    #include "hsgetmsg.as"

    #module ;#######################################################

    ;===============================================================
    ; CreateBitmap  ディスプレイ互換DDBオブジェクト作成
    ;===============================================================
    #deffunc CreateBitmap int, int, int, int
    mref px, 0              ; HSPウィンドウx座標
    mref py, 1              ; HSPウィンドウy座標
    mref sx, 2              ; xサイズ
    mref sy, 3              ; yサイズ
    mref stt, 64            ; stat
    mref bmscr, 67          ; 描画中ウィンドウのBMSCR構造体
    ; ディスプレイのデバイスコンテキストのハンドル取得
    devname = "DISPLAY"
    pm = 0,0,0,0
    getptr pm, devname      ; "DISPLAY"のアドレス
    dllproc "CreateDCA", pm, 4, D_GDI@
    hdcScreen = stat        ; ディスプレイのデバイスコンテキスト
    ; ディスプレイ互換ビットマップオブジェクト作成
    pm = hdcScreen, sx, sy
    dllproc "CreateCompatibleBitmap", pm, 3, D_GDI@
    hbitmap = stat          ; ビットマップオブジェクトのハンドル
    ; ディスプレイ互換デバイスコンテキスト作成
    pm = hdcScreen
    dllproc "CreateCompatibleDC", pm, 1, D_GDI@
    hdcMemory = stat        ; メモリデバイスコンテキストのハンドル
    ; ビットマップをデバイスコンテキストに選択
    pm = hdcMemory, hbitmap
    dllproc "SelectObject", pm, 2, D_GDI@
    ; HSPウィンドウからビットマップにイメージをコピー
    pm = hdcMemory, 0, 0, sx, sy, bmscr.4, px, py, $CC0020
    dllproc "BitBlt", pm, 9, D_GDI@
    ; デバイスコンテキストを削除
    dllproc "DeleteDC", hdcMemory, 1, D_GDI@
    dllproc "DeleteDC", hdcScreen, 1, D_GDI@
    stt = hbitmap           ; ビットマップオブジェクトのハンドル
    return

    #global ;############# モジュール終わり ########################

    ; コモンコントロールライブラリ初期化
    dllproc "InitCommonControls", pm, 0, D_COMCTL

    ; ツールバー作成
    pm = 0, 0, 0, 0         ; 座標・サイズは 0 でよい
    pm.4 = 0x50000001       ; WS_CHILD | WS_VISIBLE | CCS_NORESIZE
    pm.5 = 0, 0
    _makewnd pm, "ToolbarWindow32"
    hTool = pm              ; ツールバーのハンドル

    ; TB_BUTTONSTRUCTSIZE メッセージ送信
    pm = hTool, 0x041E, 20, 0
    sendmsg pm

    ; TB_SETBITMAPSIZE メッセージでビットマップのサイズを設定
    ; (サイズが 16×15 なので、送らなくてもよい)
    pm = hTool, 0x420, 0
    pm.3 = 16 | ( 15 << 16 )    ;  x | (y << 16) を指定
    sendmsg pm

    ; ボタンイメージ用ビットマップファイルのロード
    buffer 2,,, 1               ; バッファウィンドウをパレットモードで作成
    picload "toolbtn.bmp"

    ; パレット 0 をシステムカラーのボタン表面色に変更
    pm = 15                     ; COLOR_BTNFACE
    dllproc "GetSysColor", pm, 1, D_USER
    palette 0, stat&$FF, (stat>>8)&$FF, (stat>>16)&$FF
    palfade

    ; ビットマップオブジェクトの作成
    CreateBitmap 0, 0, 16*9, 15
    hBitmap = stat              ; 作成されたビットマップのハンドル

    ; ビットマップをボタンイメージのリストに追加
    ; TBADDBITMAP 構造体
    tbadd.0 = 0
    tbadd.1 = hBitmap           ; ビットマップハンドル

    ;TB_ADDBITMAPメッセージを送信
    pm = hTool, 0x0413
    pm.2 = 9                    ; ビットマップ中のボタンイメージの数
    getptr pm.3, tbadd          ; TBADDBITMAP 構造体のアドレス
    sendmsg pm
    idxImage = stat             ; ここでは必ず0になるのであえて保存する必要はない

    ; ビットマップオブジェクトを削除
    dllproc "DeleteObject", hBitmap, 1, D_GDI

    ; ボタンを追加(ボタンの数だけ繰り返し)
    repeat 9
        ; TBBUTTON 構造体をセット
        tbb.0 = idxImage + cnt      ; イメージのインデックス
        tbb.1 = 1000 + cnt          ; コマンドID
        tbb.2 = 4                   ; fsState=TBSTATE_ENABLED, fsStyle=0
        tbb.3 = 0
        tbb.4 = 0

        ; TB_ADDBUTTONS メッセージを送信
        pm = hTool, 0x0414
        pm.2 = 1                    ; 追加するボタンの数(1個)
        getptr pm.3, tbb            ; TBBUTTON 構造体のアドレス
        sendmsg pm
    loop

    ; TB_AUTOSIZE メッセージ送信
    pm = hTool, 0x0421, 0, 0
    sendmsg pm

    ; ウィンドウのサブクラス化
    gsel 0
    set_subclass                ; サブクラス化
    hwnd = stat                 ; メインウィンドウのハンドル
    set_message 0x0111          ; WM_COMMAND を取得するように設定
    ; メッセージパラメータ用変数
    dup msg,  msgval.1          ; メッセージが格納される変数
    dup wprm, msgval.2          ; wParamパラメータが格納される変数
    dup lprm, msgval.3          ; lParamパラメータが格納される変数


*mainloop
    get_message msgval
    if msgval == hwnd {
        if msg == 0x0111 : gosub *onCommand
    } else {
        wait 10
    }
    goto *mainloop

*onCommand
    ; WM_COMMANDが送られたとき
    ; ツールバー以外から送られた場合は何もしない
    if lprm != hTool : return

    cmdID = wprm & 0xFFFF   ; コマンドID
    dialog "コマンドID "+cmdID+" のボタンが押されました"
    return

ツールバーのボタンが押されると、押されたボタンのコマンドIDが表示されます。

ツールバーの高さについて

上のサンプルスクリプト中では問題になっていませんが、ツールバーはHSPウィンドウ上に子ウィンドウとして貼り付けられるものなので、ツールバーの下に描画されている画像は見えなくなります。したがって、ツールバーの高さを取得して、それに合わせてウィンドウを描画していく必要があります。

ツールバーの高さの取得は、ステータスバーの場合と同様に、ツールバー作成後に GetWindowRect 関数でウィンドウの座標を取得し、これから高さを計算するという方法を用います。

pm.0 = hTool                  ; ツールバーのハンドル
getptr pm.1, rect             ; 変数 rect (RECT構造体)のアドレス
dllproc "GetWindowRect", pm, 2, D_USER
nheight = rect.3 - rect.1     ; 高さの取得