第69章 タブコントロールを作る


今回は、タブコントロールの作り方を解説します。 外観はプロパティーシートとほとんど同じです。 下の方に各ページ共通のボタンがありません。 タブコントロールの使い方はいろいろな方法があります。 今回は最も簡単な方法を紹介します。

1.コモンコントロール使用準備 2.CreateWindowEx関数で、ウィンドウクラスをWC_TABCONTROLにする 3.ウィンドウスタイルは WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS とする 4.STATICクラスのウィンドウを作る 5.TC_ITEM構造体を設定する 6.TCM_INSERTITEMメッセージをタブウィンドウに送りページを追加 7.STATICウィンドウをサブクラス化して各ページを描画する   (TabCtrl_GetCurSelでどのページを描画するかを知る) 8.WM_SIZEメッセージが来たらタブウィンドウ、STATICウィンドウの大きさ調整 9.TCN_SELCHANGEを捕まえて再描画

ま、こんなところです。ここで、STATICクラスのウィンドウについて ちょっと説明しておきます。これは、地味なウィンドウですが 結構使い道があります。単なる長方形のウィンドウで入力を 受け取ったりはできません。ところがこれをメニューバーのすぐ 下につけてこのウィンドウにボタンを付けると ツールバーもどき が作れます。この方法についてはまた、後の章で解説します。 (忘れなければね。)

さて、今回作るプログラムは左のようなものです。 「設定2」を押すと2ページ目が表示されます。
ウィンドウの大きさが変わってもボタンは 下の方の中央に表示されるようにしてあります。 (ウィンドウをあまり小さくするとボタンの位置がおかしくなります。 これを改良してみてください。)

では、さっそくサンプルのプログラムを見てみましょう。

// tab03.rcの一部 // 自前で作る人はwindows.hと自前のヘッダーファイル(IDM_ENDなどの定義) // をインクルードしてください。 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "設定(&S)", IDM_SET END END

単なるメニューリソースです

// resource.hの一部 // 自前で作る人はこれを参考にして作ってください #define IDM_END 40001 #define IDM_SET 40002

resource.hの一部です。

// tab03.cpp #define STRICT #include <windows.h> #include <commctrl.h> #include "resource.h" #define ID_MYTABCTRL 2000 #define ID_MYSTATICWND 2001 #define ID_MYBUTTON 2002 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyStaticProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyButtonProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND MakeMyTabCtrl(HWND); HWND hTabWnd, hStaticWnd, hButton; char szClassName[] = "tab03"; //ウィンドウクラス FARPROC Org_StaticProc; // 元々のスタティックウィンドウ int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }

リソースヘッダーを使わない人は、独自のヘッダーファイルを インクルードしてください。STATICウィンドウをサブクラス化するので 元々のウィンドウプロシージャの保存用のグローバル変数を用意しておいてください。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&wc)); }

これは、いつもの通りです。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるタブコントロール", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

これもいつもと同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static RECT rcDisp; switch (msg) { case WM_CREATE: // コモンコントロールの初期化 InitCommonControls(); break; case WM_COMMAND: switch(LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; case IDM_SET: if (IsWindow(hTabWnd) != 0) { // すでに表示されているとき MessageBox( hWnd, "すでに設定画面が表示されています。", "注意!", MB_OK | MB_ICONEXCLAMATION); return 0L; } MakeMyTabCtrl(hWnd); // 強制的にWM_SIZEを送り出してサイズ調整 GetClientRect(hWnd, &rcDisp); SendMessage(hWnd, WM_SIZE, 0, MAKELPARAM(rcDisp.right, rcDisp.bottom)); break; } break; case WM_SIZE: GetClientRect(hWnd, &rcDisp); TabCtrl_AdjustRect(hTabWnd, FALSE, &rcDisp); MoveWindow(hTabWnd, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); MoveWindow(hStaticWnd, rcDisp.left, rcDisp.top, rcDisp.right - rcDisp.left, rcDisp.bottom - rcDisp.top, TRUE); break; case WM_NOTIFY: switch (((NMHDR *)lp)->code) { // 違うページが押された case TCN_SELCHANGE: InvalidateRect(hTabWnd, &rcDisp, TRUE); InvalidateRect(hStaticWnd, NULL, TRUE); break; } break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: // サブクラス化の解除 if (IsWindow(hButton) != 0) { SetWindowLong(hButton, GWL_WNDPROC, (LONG)Org_StaticProc); } PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ウィンドウができたらすぐにコモンコントロールを初期化します。 (InitCommonControls関数)

メニューで、「設定」が選ばれたら、タブコントロールを 作成します。このときすでにタブコントロールが存在しているときは その旨注意を促します。タブコントロールがあるかどうかは タブウィンドウの存在を調べればよいですね。 これには、IsWindow関数を使います。

BOOL IsWindow( HWND hWnd // ウィンドウハンドル );

ウィンドウが存在すればTURE, 存在しなければFALSEを返します。 また、タブウィンドウやSTATICウィンドウの大きさはWM_SIZE メッセージの所で調整しますから、ここで強制的にWM_SIZEメッセージを 送ります。ここで注意しなくてはいけないのは副メッセージです。 WM_SIZEメッセージのパラメーターは

WM_SIZE nWidth = LOWORD(lParam); nHeight = HIWORD(lParam);

でした。従ってlParamも送ってやらなくてはいけません。 ウィンドウの幅、高さからLPARAM値を作るには MAKELPARAMマクロを使うと便利です。 これは、windowsx.hのなかで

#define MAKELPARAM(l, h) (LPARAM)MAKELONG(l, h)

のように定義されています。 またMAKELONGマクロはwindef.hのなかで

#define MAKELONG(a, b) ((LONG)(((WORD)(a)) | ((DWORD)((WORD)(b))) << 16))

のように定義されています。この意味わかりますか?
C言語編第40章を読めば何とかわかるでしょう。
注:あまりこういうことに神経質にならないでください。 プログラムが書けなくなります。

次にWM_SIZEメッセージの処理ですがこれには、 TabCtrl_AdjustRectマクロを 使います。これは、与えられた矩形を使用してタブコントロールの 表示領域をもらいます。つまり、タブの部分は表示領域として 利用できないのでその部分をのぞいた領域を得ることができます。 ここでは、実際にはタブウィンドウではなくSTATICウィンドウに 表示をするのでこのウィンドウに調整した矩形を渡します。(移動・サイズ調節) 実際にはMoveWindow関数を使います。 第51章を参照してください。

次にTCN_SELCHANGEメッセージの処理ですが、これは WM_NOTIFYメッセージの形できます。これはタブ(ページ)が変更されたことを タブウィンドウの親に知らせます。WM_NOTIFYメッセージについては 第65章を参照してください。 ページ変更があったらタブウィンドウとSTATICウィンドウを描き直します。

// タブコントロールを作る HWND MakeMyTabCtrl(HWND hWnd) { HINSTANCE hInst; RECT rc; TC_ITEM tcItem; int x, y; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); GetClientRect(hWnd, &rc); hTabWnd = CreateWindowEx( 0, //拡張スタイルなし WC_TABCONTROL, // クラスネーム NULL, // ウィンドウネーム WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, // ウィンドウスタイル 0, 0, 4, 4, // 位置、大きさ(初期状態では極端に小さくしておく) hWnd, // 親ウィンドウのハンドル (HMENU)ID_MYTABCTRL, // 子供ウィンドウ識別子 hInst, // インスタンスハンドル NULL); // WM_CREATEのパラメーターなし hStaticWnd = CreateWindowEx( 0, "STATIC", NULL, WS_CHILD | WS_VISIBLE, 0, 0, 10, 10, hWnd, (HMENU)ID_MYSTATICWND, hInst, NULL); tcItem.mask = TCIF_TEXT; tcItem.pszText = "設定1"; SendMessage(hTabWnd, TCM_INSERTITEM, (WPARAM)0, (LPARAM)&tcItem); tcItem.mask = TCIF_TEXT; tcItem.pszText = "設定2"; SendMessage(hTabWnd, TCM_INSERTITEM, (WPARAM)1, (LPARAM)&tcItem); // 元々のスタティックウィンドウプロシージャの保存 Org_StaticProc = (FARPROC)GetWindowLong(hStaticWnd, GWL_WNDPROC); // スタティックウィンドウのサブクラス化 SetWindowLong(hStaticWnd, GWL_WNDPROC, (LONG)MyStaticProc); // スタティックウィンドウに「閉じる」ボタンを付ける x = (rc.right - rc.left -100) / 2; y = rc.bottom - rc.top - 40; hButton = CreateWindow( "BUTTON", // クラスネーム "閉じる", // ウィンドウネーム WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, // ウィンドウスタイル x, y, // 位置 100, 30, // 大きさ hStaticWnd, // 親ウィンドウ (HMENU)ID_MYBUTTON, // 子供ウィンドウ識別子 hInst, //インスタンスハンドル NULL); // WM_CREATE副メッセージなし SetFocus(hButton); return hTabWnd; }

タブウィンドウとSTATICウィンドウの作り方は特に解説を 要しないでしょう。

次にTC_ITEM構造体ですが、

typedef struct _TC_ITEM { UINT mask; UINT lpReserved1; // 予約済み UINT lpReserved2; // 予約済み LPSTR pszText; // タブに付く文字列へのポインタ int cchTextMax; // pszTextメンバのサイズ int iImage; // イメージリストのインデックス LPARAM lParam; // タブに関連づけられたアプリケーション関連データ } TC_ITEM;

のように定義されています。 イメージリストについては後の章で解説します。 maskメンバはiImageメンバが有効なら TCIF_IMAGEを、pszTextメンバが有効ならTCIF_TEXTを含めます。

構造体を設定したらTCM_INSERTITEMメッセージをタブウィンドウに送ります。

TCM_INSERTITEM wParam = (WPARAM) (int) iItem; //新しいタブインデックス lParam = (LPARAM) (const TC_ITEM FAR*) pitem; //TC_ITEM構造体へのポインタ

次に、実際の描画をするSTATICウィンドウをサブクラス化しておきます。 サブクラス化は最も重要なプログラムテクニックの一つです。 第36章を参照してください。

LRESULT CALLBACK MyStaticProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { PAINTSTRUCT ps; HDC hdc; static int nPage; RECT rc; int x, y; switch (msg) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); nPage = TabCtrl_GetCurSel(hTabWnd); switch (nPage) { case 0: SetBkMode(hdc, TRANSPARENT); TextOut(hdc, 10, 10, "1ページ目です", 14); SetTextColor(hdc, RGB(255, 0, 0)); TextOut(hdc, 10, 40, "粂井康孝 制作・著作", 20); break; case 1: SetBkMode(hdc, TRANSPARENT); TextOut(hdc, 10, 10, "2ページ目です", 14); SetTextColor(hdc, RGB(255, 0, 0)); TextOut(hdc, 10, 40, "粂井康孝 制作・著作", 20); break; } EndPaint(hWnd, &ps); break; case WM_SIZE: GetClientRect(hStaticWnd, &rc); x = (rc.right - rc.left - 100) / 2; y = rc.bottom - rc.top - 40; MoveWindow(hButton, x, y, 100, 30, TRUE); break; case WM_COMMAND: switch (LOWORD(wp)) { case ID_MYBUTTON: DestroyWindow(hTabWnd); DestroyWindow(hStaticWnd); break; } break; default: break; } return (CallWindowProc((WNDPROC)Org_StaticProc, hWnd, msg, wp, lp)); }

サブクラス化したSTATICウィンドウのプロシージャです。 ここで、好きなように描画すればよいですね。 ボタンウィンドウはSTATICウィンドウの子供として作ったので ボタンが押されたらメッセージはこのプロシージャに来ます。 また、描画の際どのページかを知るにはTabCtrl_GetCurSelマクロを 使います。

int TabCtrl_GetCurSel( HWND hwnd );

となっています。戻り値がページです。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Aug/18/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。