左の図は前章で作ったものを改良したプログラムです。
「丸」と「四角」のボタンの横に小さな三角形のボタンがあり
これを押すとドロップダウンメニューが表示されます。
このようなツールバーを作るにはComctl32.dllのバージョンが4.71以降で
あることが必要です。
と、まあこんな具合に作ります。メニューリソースは第1階層は表示されません (ダミーとなります)。第44章を参照して下さい。0.表示するメニューリソースを作っておく。 1.TBBUTTON構造体のfsStyleメンバをTBSTYLE_DROPDOWNに設定する。 2.ツールバーを作る 3.ツールバーに対してTB_SETEXTENDEDSTYLEメッセージを送る。 この時lParamはTBSTYLE_EX_DRAWDDARROWSにする。 4.TBN_DROPDOWN通知メッセージの処理。
TBBUTTON構造体のfStyleメンバはTBSTYLE_BUTTON | TBSTYLE_DROPDOWNとしても 単にTBSTYLE_DROPDOWNとしても同じ事です。(TBSTYLE_BUTTON=0)
CreateToolbarEx関数の第2引数にTB_EX_DRAWDDARROWSを加えてもうまくいきません。 TB_SETEXTENDEDSTYLEメッセージを使ってください。
dwExStyleに拡張スタイルを設定します。TB_SETEXTENDEDSTYLE wParam = 0; lParam = (LPARAM)(DWORD)dwExStyle;
TBN_DROPDOWNメッセージはWM_NOTIFYメッセージの形でやってきます。
lpnmtbはNMTOOLBAR構造体へのポインタです。TBN_DROPDOWN lpnmtb = (LPNMTOOLBAR) lParam;
hdrはNMHDR構造体です。typedef struct tagNMTOOLBAR { NMHDR hdr; int iItem; TBBUTTON tbButton; int cchText; LPTSTR pszText; } NMTOOLBAR, FAR* LPNMTOOLBAR;
iItemはボタンのコマンドIDです。
tbButtonはTBBUTTON構造体です。
cchTextはボタンテキストの文字数です。
pszTextはボタンテキストを含んでいるバッファのアドレスです。
さて、WM_NOTIFYメッセージが来たら、これがツールバーからのもので なおかつコードがTBN_DROPDOWNであればlParamの値がこの構造体への アドレスとなっています。次にツールバーに対してTB_GETRECTメッセージを 送ります。
このメッセージは指定されたツールバーボタンに結合した四角形を取得します。 (ボタンの4隅がわかる)TB_GETRECT wParam = (WPARAM)(INT) iID; lParam = (LPARAM)(LPRECT) lprc;
iIDはボタンのIDです。
lprcは四角形の情報を受け取るRECT構造体のアドレスです。
ボタンの座標がわかれば第44章の方法でTrackPopupMenu関数を 呼んでもよいわけですが、ここではTrackPopupMenuEx関数を使ってボタンが ポップアップメニューに隠されないようにします。 (何とかEx関数とか、何とかEX構造体というのが多いですね)
hmenuは表示するメニューのハンドルです。(GetSubMenuで取得)BOOL TrackPopupMenuEx( HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, LPTPMPARAMS lptpm );
fuFlagsは位置などのオプションです。
TPM_HORIZONTALはメニューがlptpmで指定した長方形に重なってしまう場合
水平方向に移動します。
TPM_VERTICALは垂直方向に移動します。
その他TrackPopupMenu関数のフラグも有効です。
x, yはメニューの位置をスクリーン座標で指定します。
hwndはメニューを所有するウィンドウハンドルを指定します。このウィンドウが WM_COMMANDメッセージを受け取ります。
lptpmはTPMPARAMS構造体へのポインタです。
cbSizeはこの構造体の大きさです。typedef struct tagTPMPARAMS { UINT cbSize; RECT rcExclude; } TPMPARAMS, FAR *LPTPMPARAMS;
rcExcludeは重なっては困る四角形のRECT構造体です。
さて、前置きがすっかり長くなってしまいました。プログラムを 見てみましょう。
メニューの第1階層は表示されない点に注意して下さい。// newtool2.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Toolbar // IDR_TOOLBAR2 TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON IDM_MARU BUTTON IDM_SANKAKU BUTTON IDM_SHIKAKU END IDR_TOOLBAR1 TOOLBAR DISCARDABLE 16, 15 BEGIN BUTTON IDM_MARU BUTTON IDM_SANKAKU BUTTON IDM_SHIKAKU END ///////////////////////////////////////////////////////////////////////////// // // Bitmap // IDR_TOOLBAR2 BITMAP DISCARDABLE "toolbar2.bmp" IDR_TOOLBAR1 BITMAP DISCARDABLE "toolbar1.bmp" ///////////////////////////////////////////////////////////////////////////// // // Menu // MARUMENU MENU DISCARDABLE BEGIN POPUP "ダミーです" BEGIN MENUITEM "丸−項目&1", IDM_RND1 MENUITEM "丸−項目&2", IDM_RND2 MENUITEM "丸−項目&3", IDM_RND3 END END SHIKAKUMENU MENU DISCARDABLE BEGIN POPUP "ダミーです" BEGIN MENUITEM "四角−項目&1", IDM_RCT1 MENUITEM "四角−項目&2", IDM_RCT2 END END
ドロップダウンメニューをつけたいボタンのスタイルをTBSTYLE_DROPDOWNにします。// newtool2.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include <commctrl.h> #include "resource.h" #define ID_TOOL 1 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); HWND CreateMyToolbar(HWND); char szClassName[] = "newtool2"; //ウィンドウクラス HINSTANCE hInst; HIMAGELIST hImage; TBBUTTON tb[] = { {0, IDM_MARU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN, 0, 0, 0}, {1, IDM_SANKAKU, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0}, {2, IDM_SHIKAKU, TBSTATE_ENABLED, TBSTYLE_DROPDOWN, 0, 0, 0} };
このへんは、いつもと同じです。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; //グローバル変数にコピー if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); 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 = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&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; }
WM_NOTIFYメッセージの処理のし方に注意して下さい。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static HWND hTool; INITCOMMONCONTROLSEX ic; LPNMHDR lpnmhdr; HMENU hMenu, hPopup; RECT rc; TPMPARAMS tpm; LPNMTOOLBAR lpnmTB; char szMenuName[32]; switch (msg) { case WM_CREATE: InitCommonControls(); ic.dwSize = sizeof(INITCOMMONCONTROLSEX); ic.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx(&ic); hTool = CreateMyToolbar(hWnd); break; case WM_SIZE: SendMessage(hTool, WM_SIZE, wp, lp); break; case WM_NOTIFY: lpnmhdr = (LPNMHDR)lp; if (lpnmhdr->hwndFrom == hTool && lpnmhdr->code == TBN_DROPDOWN) { lpnmTB = (LPNMTOOLBAR)lp; SendMessage(hTool, TB_GETRECT, (WPARAM)lpnmTB->iItem, (LPARAM)&rc); MapWindowPoints(hTool, HWND_DESKTOP, (LPPOINT)&rc, 2); tpm.cbSize = sizeof(TPMPARAMS); tpm.rcExclude.top = rc.top; tpm.rcExclude.left = rc.left; tpm.rcExclude.bottom = rc.bottom; tpm.rcExclude.right = rc.right; switch (lpnmTB->iItem) { case IDM_MARU: lstrcpy(szMenuName, "MARUMENU"); break; case IDM_SHIKAKU: lstrcpy(szMenuName, "SHIKAKUMENU"); break; } hMenu = LoadMenu(hInst, szMenuName); hPopup = GetSubMenu(hMenu, 0); TrackPopupMenuEx(hPopup, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL, rc.left, rc.bottom, hWnd, &tpm); DestroyMenu(hMenu); } else { return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_MARU: MessageBox(hWnd, "「丸」が押されました", "丸", MB_OK); break; case IDM_SANKAKU: MessageBox(hWnd, "「三角」が押されました", "三角", MB_OK); break; case IDM_SHIKAKU: MessageBox(hWnd, "「四角」が押されました", "四角", MB_OK); break; case IDM_RND1: MessageBox(hWnd, "丸メニュー項目1が選択されました", "メニュー選択", MB_OK); break; case IDM_RND2: MessageBox(hWnd, "丸メニュー項目2が選択されました", "メニュー選択", MB_OK); break; case IDM_RND3: MessageBox(hWnd, "丸メニュー項目3が選択されました", "メニュー選択", MB_OK); break; case IDM_RCT1: MessageBox(hWnd, "四角メニュー項目1が選択されました", "メニュー選択", MB_OK); break; case IDM_RCT2: MessageBox(hWnd, "四角メニュー項目2が選択されました", "メニュー選択", MB_OK); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { ImageList_Destroy(hImage); DestroyWindow(hTool); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
あるウィンドウを基準とする座標を他のウィンドウを基準とする座標に 変換します。複数の点を一度に指定することができます。int MapWindowPoints( HWND hWndFrom, HWND hWndTo, LPPOINT lpPoints, UINT cPoints );
hWndFromは変換前の座標の基準となるウィンドウのハンドルです。
hWndToは変換後に基準となるウインドウのハンドルです。
lpPointsは変換する座標が入ったPOINT構造体へのポインタです。
cPointsは変換する点の個数です。
TrackPopupMenuEx関数のあとですぐにDestroyMenu関数を 呼んでいるのを不思議に思われる方がいるかもしれませんが、 これでよいと思われます。なぜなら TrackPopupMenuEx関数はユーザーがメニュー項目を選択し終わるまで 制御を返しませんのでDestroyMenu関数が呼ばれた時は すでに選択が終了してメニューは消えているからです。
最初のほうにも書きましたが、CreateToolbarEx関数の第2引数に TBSTYLE_EX_DRAWDDARROWSを加えても無効です。 案外こういう間違いがわかりにくいバグとなるので注意して下さい。HWND CreateMyToolbar(HWND hWnd) { HWND hTool; LONG lStyle; hTool = CreateToolbarEx(hWnd, //親ウィンドウ WS_CHILD | WS_VISIBLE | CCS_NODIVIDER, //ウィンドウスタイル ID_TOOL, //コントロールID 3, //ボタンイメージの数 hInst, //インスタンスハンドル IDR_TOOLBAR1, //リソースID tb, //TBBUTTON構造体のアドレス 3, //ボタンの数 0, 0, //ボタンの幅、高さ 0, 0, //ボタンイメージの幅、高さ sizeof(TBBUTTON)); //TBBUTTON構造体の大きさ lStyle = GetWindowLong(hTool, GWL_STYLE); lStyle |= TBSTYLE_FLAT; SetWindowLong(hTool, GWL_STYLE, lStyle); hImage = ImageList_LoadBitmap(hInst, MAKEINTRESOURCE(IDR_TOOLBAR2), 16, 0, RGB(255,255,255)); SendMessage(hTool, TB_SETHOTIMAGELIST, 0, (LPARAM)hImage); SendMessage(hTool, TB_SETEXTENDEDSTYLE, 0, (LPARAM)TBSTYLE_EX_DRAWDDARROWS); return hTool; }
Update 29/Mar/1999 By Y.Kumei