タブコントロールを作成してみる ACT-2

タブコントロール中への描画

今回は、前回作成したタブコントロールの中に描画したり、HSPオブジェクトを乗せてみたりします。こつさえ分かってしまえば楽です。HSPウィンドウに描画するのと同じですから。

さらに、ウィンドウをシステムカラーで塗りつぶしてみたりもします。

手順

タブコントロールの中に描画をするのに、今回はちょっとした裏技的手法を用います。どのような方法かというと、「bgscr で作成したHSPウィンドウをタブコントロールの子ウィンドウとして貼り付ける」というものです。いったんウィンドウを貼り付けてしまえば、あとは通常どおりに描画することができるので、非常に簡単になります。

HSPウィンドウをタブコントロールの子ウィンドウとする実際の手順としては、以下のようになります。

  1. タブコントロールを作成する。
  2. タブコントロールに TCM_ADJUSTRECT メッセージを送信して、子ウィンドウが表示されるべき領域を取得する。
  3. 子ウィンドウにHSPウィンドウを bgscr 命令で作成し、ウィンドウスタイルから WS_POPUP スタイルを取り除き、WS_CHILD スタイルを付加する。
  4. SetParent 関数でタブコントロールを親ウィンドウに設定する。
  5. MoveWindow 関数でHSPウィンドウ(子ウィンドウ)の位置・サイズを変更する。

コントロールの作成

まずはタブコントロールを作成して、タブアイテムも追加しておきます。これは前回行なったことと同じです。

コントロールの表示領域の取得

次に、タブコントロール上に子ウィンドウを貼り付ける際に必要となる、タブコントロール上の表示領域の座標を取得する必要があります。この座標から、子ウィンドウとなるHSPウィンドウの幅と高さを求めることができます。

表示領域の座標を求めるには、タブコントロールに TCM_ADJUSTRECT メッセージを送信します。

#define  TCM_ADJUSTRECT    0x1328

TCM_ADJUSTRECT
    wParam = fLarger;        // 実行する操作
    lParam = pRect;          // RECT構造体のアドレス

このメッセージは、タブコントロールのサイズから内部の表示領域を求めるので、 fLarger パラメータには 0 (FALSE) を指定します。 pRect パラメータには、タブコントロールのウィンドウサイズを格納した RECT 構造体を指定します。ただし、この構造体が指す左上座標(left メンバと top メンバ)には 0 を、右下座標(right メンバと bottom メンバ)にはコントロールのサイズを格納しておかなければなりません。

この際に必要となるタブコントロールのサイズを取得するには、ウィンドウのクライアント座標を取得する GetClientRect 関数を使うのが便利です。

BOOL GetClientRect(
    HWND   hWnd,    // ウィンドウハンドル
    PRECT  pRect    // RECT構造体へのポインタ
);

GetClientRect 関数の第2引数(pRect パラメータ)で指定される RECT 構造体にはタブコントロールのクライアント座標(左上座標が 0 になる)が格納されるので、この構造体をそのまま TCM_ADJUSTRECT メッセージのパラメータに指定することができます。タブコントロールには非クライアント領域がなく、ウィンドウサイズとクライアント領域サイズが等しいので、これで問題ありません。

TCM_ADJUSTRECT メッセージを送って取得した RECT 構造体には、ウィンドウの左上および右下の座標がそれぞれ格納されているので、右下x座標(right メンバ)から左上x座標(left メンバ)を引いて幅を求め、同様に右下y座標(bottom メンバ)から左上y座標(top メンバ)を引いて高さを求めることができます。

子ウィンドウの作成とウィンドウスタイルの変更

次に、 bgscr 命令でHSPウィンドウを作成します。このとき、ウィンドウの幅と高さは、先ほど取得したコントロールの表示領域から幅と高さを求めて、その値を指定します。また、ウィンドウは非表示の状態になっている必要があるので、 bgscr 命令実行時に非表示のオプションを指定するか、ウィンドウ作成後に gsel でウィンドウをいったん非表示にしておく必要があります。ここでいったん非表示にしておかないと、後でタスクバー上にボタンが残ったままになってしまいます。


HSPウィンドウは作成された状態で子ウィンドウスタイルを持っていないので、ウィンドウスタイルを変更する必要があります。

スタイルの変更を行なうには、まず GetWindowLong 関数を呼び出して、現在のウィンドウスタイルを取得します。

取得したスタイルから、 WS_POPUP (0x80000000) を取り除き、 WS_CHILD (0x40000000) を付加します。 WS_CHILD スタイルは、ウィンドウが子ウィンドウであることを示すスタイルです。 WS_POPUP スタイルを取り除くのは、 WS_POPUP と WS_CHILD は同時に指定することができないためです。 WS_POPUP を取り除くには、元のスタイルに 0x7FFFFFFF をAND結合すればできます。

こうして修正された新しいスタイルを、 SetWindowLong 関数を呼び出してHSPウィンドウに設定します。

GetWindowLong 関数や SetWindowLong 関数は、LLMODモジュールで定義されている setwndlong 命令を使って呼び出すことができます。

親ウィンドウの設定

ウィンドウスタイルの変更ができたら、 SetParent 関数を呼び出し、タブコントロールを親ウィンドウにしてしまいます。

HWND SetParent(
    HWND  hwndChild,     // 子ウィンドウのハンドル
    HWND  hwndNewParent  // 新しい親ウィンドウのハンドル
);

第1引数(hwndChild パラメータ)にHSPウィンドウのハンドルを BMSCR 構造体から取得して指定し、第2引数(hwndNewParent パラメータ)にタブコントロールのハンドルを指定します。

子ウィンドウの移動

これでHSPウィンドウがタブコントロールの子ウィンドウになったので、ウィンドウはタブコントロール上に表示されますが、このままでは正しい位置に表示されません。実際に表示させるべき位置に移動させる必要があります。

はじめに TCM_ADJUSTRECT メッセージを使ってコントロールの表示領域を求めているので、その領域に移動させます。このとき、 width 命令ではうまく設定することができないので、 MoveWindow 関数を使ってウィンドウ位置を設定します。

BOOL MoveWindow(
    HWND hWnd,     // ウィンドウハンドル
    int  x,        // x座標
    int  y,        // y座標
    int  nWitdh,   // 幅
    int  nHeight,  // 高さ
    BOOL bRepaint  // 再描画フラグ
);

最後の引数(bRepaint パラメータ)は、再描画フラグです。 1 (TRUE) を指定すると、直ちに再描画を行ないます。ウィンドウが非表示の状態で移動する場合には、再描画をする必要はありません。


以上の操作で、ウィンドウをタブコントロール上に貼り付けることができました。あとは貼り付けてあるウィンドウ上にいつもどおりの方法で描画をして、最初に非表示にしたのを再び gsel で表示すれば、タブコントロール上に表示されるはずです。タブコントロール上に貼り付けたウィンドウのサイズ変更されてしまうかもしれませんが、これはシステム変数 winx, winy から得ることができます。


ところで、たいていの場合はタブコントロールに複数のページを作成すると思います。別のページが選択されて TCN_SELCHANGE メッセージが送られてきたときに、一度画面を消去してからもう一度描画しなおすのもいいですが、せっかく bgscr 命令でウィンドウをたくさん作成することができるのですから、各ページで1個ずつウィンドウを割り当ててしまうと良いでしょう。複数のウィンドウをタブコントロール上に貼り付けておいて、 TCN_SELCHANGE メッセージが送られてきたら、今までのウィンドウを非表示にして、選択されたタブに割り当てたウィンドウを表示すれば良いのです。表示・非表示の切り替えは gsel 命令で行なうことができます。

システムカラーでの塗りつぶし

タブコントロール上に貼り付けたウィンドウやタブコントロールの親ウィンドウの背景が白色のままというのはちょっと違和感がありますね。ついでなのでもう1つ、ウィンドウをシステムカラーで塗りつぶしてしまいましょう。

Windowsのシステムカラーは、 GetSysColor 関数で簡単に取得することができます。

DWORD GetSysColor(
    int nIndex   // システムカラーのインデックス
);

引数(nIndex パラメータ)には通常は 15 (COLOR_BTNFACE) を指定すれば良いでしょう。この関数を呼び出すと、戻り値として取得した色のCORORREF値(RGB値)が返ります。この値は、赤の輝度を 0xRR、緑の輝度を 0xGG、青の輝度を 0xBB としたとき、 0x00BBGGRR で表されます。これを各色の輝度に分けて取得するには以下のようにすれば良いです。

rgb = (RGB値)
r = rgb & 0xFF           ; 赤の輝度
g = (rgb >> 8 ) & 0xFF   ; 緑の輝度
b = (rgb >> 16) & 0xFF   ; 青の輝度

あとは、この値を color 命令で指定してから boxf で塗りつぶせばOKですね。

ただし、ウィンドウがパレットモードで初期化されている場合は、取得したシステムカラーをパレットに含ませる必要があります。


ところで、ディスプレイがフルカラー表示(24ビット以上)でない場合には、上記の方法で描画された部分がタブコントロールやほかのウィンドウの色とは微妙に異なってしまう場合があります。完全に正しく表示されるには、ディスプレイがフルカラー表示でなければなりません。

サンプルスクリプト

さて、実際にスクリプトを書いてみます。

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

    #module ;######## タブコントロール操作モジュール ###############

    ;===============================================================
    ; CreateTabCtl  タブコントロール作成
    ;===============================================================
    #deffunc CreateTabCtl int, int, int, int
    mref cx, 0                  ; x座標
    mref cy, 1                  ; y座標
    mref sx, 2                  ; 幅
    mref sy, 3                  ; 高さ
    mref stt, 64                ; stat
    mref bmscr, 67              ; 描画中ウィンドウのBMSCR構造体

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

    ; タブコントロールの作成
    pm.0 = cx, cy, sx, sy       ; 座標、サイズ
    pm.4 = 0x52000000           ; WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS
    pm.5 = 0                    ; 親ウィンドウ(0のとき描画中ウィンドウ)
    pm.6 = 0                    ; 拡張ウィンドウスタイル
    _makewnd pm, "SysTabControl32"
    stt = pm                    ; タブコントロールのハンドル
    return

    ;===============================================================
    ; AddTabItem  タブアイテム追加
    ;===============================================================
    #deffunc AddTabItem int, str, int
    mref hTab, 0                ; タブコントロールのハンドル
    mref setText, 33            ; 表示する文字列
    mref idx, 2                 ; インデックス

    sdim szText, 256
    szText = setText            ; いったん別の変数に移しておく

    ; TCITEM 構造体
    tcitem.0 = 0x0001           ; TCIF_TEXT
    getptr tcitem.3, szText

    ; TCM_INSERTITEM メッセージ送信
    pm = hTab, 0x1307, idx
    getptr pm.3, tcitem
    sendmsg pm
    return

    ;===============================================================
    ; GetTabSel  選択タブアイテム取得
    ;===============================================================
    #deffunc GetTabSel int
    mref hTab, 0                ; タブコントロールのハンドル

    ; TCM_GETCURSEL メッセージ送信
    pm = hTab, 0x130B, 0, 0
    sendmsg pm
    return

    ;===============================================================
    ; GetTabPos  タブコントロールの描画位置とサイズ取得
    ;===============================================================
    #deffunc GetTabPos  val, int
    mref ret, 16                ; 座標とサイズを格納する変数
    mref hTab, 1                ; タブコントロールのハンドル

    ; クライアント座標取得
    pm = hTab
    getptr pm.1, ret
    dllproc "GetClientRect", pm, 2, D_USER@

    ; 表示領域の座標に変換
    ; TCM_ADJUSTRECT メッセージ送信
    pm = hTab, 0x1328, 0
    getptr pm.3, ret
    sendmsg pm

    ; 幅・高さを求める
    ret.2 -= ret.0
    ret.3 -= ret.1
    return

    ;===============================================================
    ; SetTabChild  タブコントロールに描画中HSPウィンドウを貼り付け
    ;===============================================================
    #deffunc SetTabChild  int, int
    mref hTab, 0                ; タブコントロールのハンドル
    mref bmscr, 67              ; 描画中ウィンドウのBMSCR構造体

    ; ウィンドウスタイル取得
    pm = bmscr.13, -16
    setwndlong pm, 1
    style = stat

    ; WS_POPUP (0x80000000) 削除, WS_CHILD (0x40000000) 付加
    style = style & 0x7FFFFFFF | 0x40000000

    ; ウィンドウスタイル設定
    pm = bmscr.13, -16, style
    setwndlong pm

    ; 親ウィンドウの設定
    pm = bmscr.13, hTab
    dllproc "SetParent", pm, 2, D_USER@

    ; 表示領域を求める
    GetTabPos rect, hTab

    ; ウィンドウを移動
    pm = bmscr.13, rect.0, rect.1, rect.2, rect.3, 0
    dllproc "MoveWindow", pm, 6, D_USER@
    return

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

    #module ;######### システムカラー取得モジュール ################

    ;===============================================================
    ; SystemColor カレントカラーをシステムカラーに設定
    ;===============================================================
    #deffunc SystemColor int
    mref index, 0               ; システムカラーのインデックス

    ; システムカラーを取得
    pm = index
    dllproc "GetSysColor", pm, 1, D_USER@
    clr = stat & $FF, (stat>>8) & $FF, (stat>>16) & $FF
    ; カレントカラーをシステムカラーに設定
    palette 137+index, clr.0, clr.1, clr.2 : palfade
    color clr.0, clr.1, clr.2
    return

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


    #define WM_NOTIFY         0x004E

    ; 負の数は10進数で #define 定義できないので16進形式で定義
    ; (#define TCN_SELCHANGE     -551)
    #define TCN_SELCHANGE     0xFFFFFDD9

    screen 0, 250, 200, 2       ; 非表示で作成

    ; システムカラーで塗りつぶす
    SystemColor 15
    boxf

    objsize 90
    pos  50, 170 : button "OK", *lb_ok
    pos 150, 170 : button "キャンセル", *lb_cansel

    ; タブコントロールの作成
    CreateTabCtl 10, 10, 230, 150
    hTab = stat                 ; コントロールのハンドル

    ; タブアイテム(ページ)の追加
    AddTabItem hTab, "タブ1", 0
    AddTabItem hTab, "タブ2", 1
    AddTabItem hTab, "タブ3", 2

    ; タブコントロールの位置・サイズを取得
    GetTabPos tabpos, hTab

    ; 1ページ目タブの子ウィンドウ
    bgscr 2, tabpos.2, tabpos.3, 2  ; 非表示で作成
    SetTabChild hTab                ; タブコントロール上に貼り付ける
    SystemColor 15 : boxf           ; システムカラーで塗りつぶし
    pos 10, 10 : color 0,0,0 : objsize 150
    mes "ページ1"
    mes "描画領域サイズは "+winx+", "+winy
    chkbox "チェックボックス", chk
    mes "コンボボックス"
    combox cb,, "アイテム0\nアイテム1\nアイテム2"

    ; 2ページ目タブの子ウィンドウ
    bgscr 3, tabpos.2, tabpos.3, 2  ; 非表示で作成
    SetTabChild hTab                ; タブコントロール上に貼り付ける
    SystemColor 15 : boxf           ; システムカラーで塗りつぶし
    pos 10, 10 : color 0,0,0
    mes "ページ2"
    mes "エディットボックス"
    sdim buf, 1024
    mesbox, buf, winx-20, 60, 1

    ; 3ページ目タブの子ウィンドウ
    bgscr 4, tabpos.2, tabpos.3, 2  ; 非表示で作成
    SetTabChild hTab                ; タブコントロール上に貼り付ける
    SystemColor 15 : boxf           ; システムカラーで塗りつぶし
    pos 10, 10 : color 0,0,0 : objsize 150
    mes "ページ3"
    button "ボタン1", *lb_btn1
    button "ボタン2", *lb_btn2

    ; 現在の選択タブのウィンドウID
    widNow = 2
    gsel widNow, 1

    ; ウィンドウのサブクラス化
    gsel 0, 1                   ; ウィンドウを表示
    set_subclass                ; サブクラス化
    set_notify TCN_SELCHANGE    ; WM_NOTIFY 形式通知の取得設定
    ; メッセージパラメータ用変数
    dup msg,  msgval.1          ; メッセージが格納される変数
    dup wprm, msgval.2          ; wParamパラメータが格納される変数
    dup lprm, msgval.3          ; lParamパラメータが格納される変数
    dup nmhdr, msgval.4         ; NMHDR 構造体が格納される変数

*mainloop
    get_message msgval
    if msgval {
        if (msg == WM_NOTIFY) & (nmhdr == hTab) : gosub *onTabNotify
    } else {
        wait 10
    }
    goto *mainloop

*onTabNotify
    ; タブコントロールから WM_NOTIFY メッセージが送られたとき
    if nmhdr.2 == TCN_SELCHANGE {
        ; TCN_SELCHANGE通知が送られたとき
        gsel widNow, -1         ; 今までのウィンドウを非表示に
        ; 選択されたタブアイテムを取得
        GetTabSel hTab
        if stat == 0 : widNow = 2
        if stat == 1 : widNow = 3
        if stat == 2 : widNow = 4
        gsel widNow, 1          ; 新しいウィンドウを表示
        gsel 0
    }
    return

*lb_btn1
    dialog "ボタン1が押されました"
    goto *mainloop

*lb_btn2
    dialog "ボタン2が押されました"
    goto *mainloop

*lb_cansel
    dialog "キャンセルされました"
    end

*lb_ok
    ; 結果の表示
    if chk {
        t = "チェックされました"
    } else {
        t = "チェックされませんでした"
    }
    dialog  t, 0, "チェックボックス"
    dialog "「アイテム"+cb+"」が選択されました", 0, "コンボボックス"
    dialog buf, 0, "エディットボックス"
    end