第71章 イメージ付きタブコントロール


今回は、前章でやったイメージリスト を利用してタブにイメージを貼り付けてみます。また、 第69章では、各ページの 表示はstaticウィンドウに描画していました。 そうすると各ページにボタンやら、エジットボックスなどが たくさんある場合プログラムが煩雑になります。 そこで、今回は各ページの表示は ダイアログボックスを利用してみます。 これだと、ボタン付けやらエジットボックスやらはリソースエジタで 簡単に好みの位置に付けることができます。

タブのところに少し大きめのイメージを貼り付けてみました。 エジットボックスとボタンはダイアログリソースを利用しています。 では、さっそくプログラムを見てみましょう。

// tab04.rcの一部 // 自前で作る人はwindows.hと自作ヘッダーファイル(シンボル定義) // をインクルードしてください。 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "設定(&S)", IDM_SET END END ///////////////////////////////////////////////////////////////////////////// // // Bitmap // MYBMP0 BITMAP DISCARDABLE "mybmp0.bmp" MYBMP1 BITMAP DISCARDABLE "mybmp1.bmp" ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG0 DIALOG DISCARDABLE 0, 0, 165, 94 STYLE WS_CHILD | WS_BORDER FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "更新",IDOK,17,71,50,14 PUSHBUTTON "閉じる",IDCANCEL,100,71,50,14 LTEXT "設定1です",IDC_STATIC,7,7,32,8 LTEXT "氏名",IDC_STATIC,7,34,15,8 LTEXT "住所",IDC_STATIC,7,50,15,8 EDITTEXT IDC_NAME,31,31,124,12,ES_AUTOHSCROLL EDITTEXT IDC_ADDRESS,31,49,124,12,ES_AUTOHSCROLL END MYDLG1 DIALOG DISCARDABLE 0, 0, 165, 94 STYLE WS_CHILD | WS_BORDER FONT 9, "MS Pゴシック" BEGIN DEFPUSHBUTTON "更新",IDOK,7,73,50,14 PUSHBUTTON "閉じる",IDCANCEL,112,73,50,14 LTEXT "設定2です",IDC_STATIC,7,7,32,8 END

自前で作る人はIDM_ENDとIDM_SETなどを定義したヘッダーファイルを リソーススクリプトにインクルードしておいてください。 VC++のリソースエジタで作る人はソースファイルにresource.hを インクルードするのを忘れないでください。

// tab04.cpp #define STRICT #include <windows.h> #include <windowsx.h> // マクロ使用 #include <commctrl.h> //コモンコントロール使用 #include <string.h> // strlen()など #include "resource.h" // 自分でヘッダー作らない #define ID_MYTABCTRL 100 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlg0Proc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlg1Proc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND MakeMyTab(HWND); HIMAGELIST MakeMyImage(HWND); HWND hTabWnd, hPage0, hPage1; char szClassName[] = "tab04"; //ウィンドウクラス int img0, img1; HIMAGELIST hIList; RECT rc; char str_name[64], str_address[256]; 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 msg.wParam; }

このへんはいつもと同じです。イメージリスト ハンドルはグローバル変数にしておきました。

//ウィンドウ・クラスの登録 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; int wmax, hmax, x, y; wmax = GetSystemMetrics(SM_CXSCREEN); hmax = GetSystemMetrics(SM_CYSCREEN); x = (wmax - 320) / 2; y = (hmax - 240) / 2; hWnd = CreateWindow(szClassName, "猫でもわかるタブコントロール", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 x, //X座標 y, //Y座標 320, //幅 240, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

ウィンドウを画面の中央に来るようにしてみました。 この方法は、すでに第67章で解説してあります。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; PAINTSTRUCT ps; HDC hdc; switch (msg) { case WM_CREATE: InitCommonControls(); hIList = MakeMyImage(hWnd); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); ImageList_Draw(hIList, 0, hdc, 10, 10, ILD_NORMAL); TextOut(hdc, 100, 10, str_name, strlen(str_name)); ImageList_Draw(hIList, 1, hdc, 10, 60, ILD_NORMAL); TextOut(hdc, 100, 60, str_address, strlen(str_address)); EndPaint(hWnd, &ps); 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); break; } hTabWnd = MakeMyTab(hWnd); GetClientRect(hWnd, &rc); SendMessage(hWnd, WM_SIZE, 0, MAKELPARAM(rc.right, rc.bottom)); break; } break; case WM_SIZE: GetClientRect(hWnd, &rc); TabCtrl_AdjustRect(hTabWnd, FALSE, &rc); MoveWindow(hTabWnd, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); MoveWindow(hPage0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); MoveWindow(hPage1, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); break; case WM_NOTIFY: switch(TabCtrl_GetCurSel(((NMHDR *)lp)->hwndFrom)) { case 0: ShowWindow(hPage0, SW_SHOW); ShowWindow(hPage1, SW_HIDE); break; case 1: ShowWindow(hPage1, SW_SHOW); ShowWindow(hPage0, SW_HIDE); break; } break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: if (hIList) ImageList_Destroy(hIList); PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

親ウィンドウができたらすぐに(WM_CREATE)コモンコントロールを 初期化して、イメージリストを作ります。

WM_PAINTメッセージを受け取ったら、タブコントロールで 受け取った入力を親のクライアント領域に表示します。 ついでにイメージリストのイメージもクライアント 領域に表示します。これをみてもイメージリストを作ってしまえば 表示は簡単であることがわかりますね。

メニューが選択されたら(WM_COMMAND)IDM_SETで タブコントロールを表示します。このときタブコントロール ウィンドウの有無をチェックしておかないと複数の タブコントロールが作られてしまいます。 (タブコントロールが表示されているにもかかわらず メニューから「設定」を選択したとき)
また、このとき親ウィンドウのクライアント領域の大きさを 測っておいてWM_SIZEメッセージを送信します。これを 忘れるとみっともない結果になります。

WM_SIZEメッセージは自分で処理しなくてはいけません。 これを省略するとどうなるかは実験してみればすぐにわかります。 タブコントロールとダイアログボックスの大きさを 調整します。これが意外と難しいものです。とくに、 ダイアログリソースを作るときVC++のリソースエジタは デフォルトで「ボップアップ」になっています。 そうすると位置の調整ができません。こうなるとなかなか わかりにくいバグになります。ダイアログボックスは WS_CHILD スタイルにしておく必要があります。

WM_NOTIFYメッセージの処理は重要です。 TabCtrl_GetCurSelでどのタブが選ばれているかを 知り、関係のないダイアログボックスを非表示にします。

HWND MakeMyTab(HWND hWnd) { HWND hTab; static HINSTANCE hInst; RECT rc; TC_ITEM tc[2]; GetClientRect(hWnd, &rc); hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); hTab = CreateWindowEx( 0, WC_TABCONTROL, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, rc.left, rc.top, rc.right, rc.bottom, hWnd, (HMENU)ID_MYTABCTRL, hInst, NULL); tc[0].mask = TCIF_TEXT | TCIF_IMAGE; tc[0].pszText = "設定1"; tc[0].iImage = img0; SendMessage(hTab, TCM_INSERTITEM, (WPARAM)0, (LPARAM)&tc[0]); tc[1].mask = TCIF_TEXT | TCIF_IMAGE; tc[1].pszText = "設定2"; tc[1].iImage = img1; SendMessage(hTab, TCM_INSERTITEM, (WPARAM)1, (LPARAM)&tc[1]); SendMessage(hTab, TCM_SETIMAGELIST, (WPARAM)0, (LPARAM)hIList); hPage0 = CreateDialog(hInst, "MYDLG0", hWnd, (DLGPROC)MyDlg0Proc); hPage1 = CreateDialog(hInst, "MYDLG1", hWnd, (DLGPROC)MyDlg1Proc); ShowWindow(hPage0, SW_SHOW); ShowWindow(hPage1, SW_HIDE); return hTab; }

タブコントロールの作り方は第69章と同じです。 ただし、今回はイメージリストを使うのでTC_ITEM構造体のiImageメンバを 設定します。 また、タブコントロールウィンドウにTCM_SETIMAGELISTメッセージを 送信する必要があります。

TCM_SETIMAGELIST wParam = 0; // 使用しない。常に0 lParam = (LPARAM) (HIMAGELIST) himl; // イメージリストハンドル

これは、TabCtrl_SetImageList マクロを使っても同じことです。

BOOL TabCtrl_SetImageList( HWND hwnd, HIMAGELIST himl );

次に各ページに相当するダイアログボックスを作ります。 これは、モードレスダイアログボックスです。 モードレスダイアログボックスについては、 第30章で解説しています。

HIMAGELIST MakeMyImage(HWND hWnd) { HINSTANCE hInst; HIMAGELIST hImage; HBITMAP hBitmap; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); hImage = ImageList_Create(32, 32, FALSE, 2, 0); hBitmap = LoadBitmap(hInst, "MYBMP0"); img0 = ImageList_Add(hImage, hBitmap, NULL); hBitmap = LoadBitmap(hInst, "MYBMP1"); img1 = ImageList_Add(hImage, hBitmap, NULL); return hImage; }

イメージリストを作る関数です。今回はマスクを 使いません。

LRESULT CALLBACK MyDlg0Proc(HWND hDlg0, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_INITDIALOG: Edit_SetText(GetDlgItem(hDlg0, IDC_NAME), str_name); Edit_SetText(GetDlgItem(hDlg0, IDC_ADDRESS), str_address); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: // 更新ボタンが押されました Edit_GetText(GetDlgItem(hDlg0, IDC_NAME), str_name, sizeof(str_name)); Edit_GetText(GetDlgItem(hDlg0, IDC_ADDRESS), str_address, sizeof(str_address)); InvalidateRect( GetParent(hDlg0), NULL, TRUE); return TRUE; case IDCANCEL: DestroyWindow(hDlg0); DestroyWindow(hTabWnd); return TRUE; } break; } return FALSE; }

最初のページのダイアログプロシージャです。 更新ボタン(IDOK)が押されたらエジットボックスの内容を 取得して親ウィンドウのクライアント領域を再描画させます。 筆者も時々間違えるのですがダイアログプロシージャの 第1引数はダイアログボックスのウィンドウハンドルです。 これをうっかり 親ウィンドウのハンドルであると錯覚する ことがあります。従って親ウィンドウのクライアント領域を 再描画させるには

InvalidateRect( GetParent(hDlg0), NULL, TRUE);

となることに注意してください。GetParent関数については 第29章で解説してあります。

LRESULT CALLBACK MyDlg1Proc(HWND hDlg1, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: MessageBox(hDlg1, "OKボタン", "ボタン", MB_OK); return TRUE; case IDCANCEL: DestroyWindow(hDlg1); DestroyWindow(hTabWnd); return TRUE; } break; } return FALSE; }

2ページ目は手抜きでほとんど何もしていません。 1ページ目をまねしていろいろコントロールを付けて処理をしてみてください。

今回のプログラムではウィンドウを大きくしてもボタンの位置は かわりません。そこでウィンドウの大きさに合わせて ボタンをページの下の方に表示するように改良してみてください。

また、タブコントロールにツールチップを付けるにはどうしたらよいでしょうか。 これも挑戦してみてください。

ツールバーや、ステータスバーも付けてみてください。

また、アクセラレーターキーも付けてみてください。


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

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