ツールバーのドロップダウンメニュー
メニューを持つボタン
ツールバーボタンにはドロップダウンメニューを付けることができます。
ドロップダウンメニューを持つボタンは、ボタンのすぐ右側に三角形(▼)のボタンが追加で表示され、これをクリックすることでメニューが表示されます。
メニューリソースの用意
ツールバーのドロップダウンメニューはショートカットメニューをボタンの下に表示することで実現しています。
なので、まずは表示したいメニューのリソースを用意します。
ここでは以下のリソースを使用します。
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDR_MENU1 MENU
BEGIN
POPUP "あ"
BEGIN
MENUITEM "あ", ID_A
MENUITEM "か", ID_KA
MENUITEM "さ", ID_SA
MENUITEM "た", ID_TA
MENUITEM "な", ID_NA
END
END
BTNS_DROPDOWNスタイルの指定
TBBUTTON構造体でのボタンの定義で、ドロップダウンメニューを持たせたいツールバーボタンのスタイルにBTNS_DROPDOWN
またはTBSTYLE_DROPDOWN
を指定します。
TBBUTTON tbb[] = {
{ index++, IDC_A, TBSTATE_ENABLED, BTNS_DROPDOWN }, //ドロップダウンメニュー
{ index++, IDC_I, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_U, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_E, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_O, TBSTATE_ENABLED, BTNS_BUTTON }
};
上記の例では最初のボタンのみにドロップダウンメニューを設定しています。
TBSTYLE_EX_DRAWDDARROWS拡張スタイルの設定
次にツールバーにTBSTYLE_EX_DRAWDDARROWS
拡張スタイルを設定します。
拡張スタイルはSendMessage関数でツールバーにTB_SETEXTENDEDSTYLE
メッセージを送信することで設定できます。
SendMessage(hToolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS);
WPARAMは使用しないので0を指定します。
LPARAMに設定したい拡張スタイル(TBSTYLE_EX_DRAWDDARROWS
)を指定します。
戻り値はメッセージ送信前に設定されていた拡張スタイルを示す整数値です。
(DWORD型)
TBSTYLE_EX_DRAWDDARROWS
拡張スタイルは、ドロップダウンメニューを持つボタンの右側に矢印ボタン(▼)を追加します。
この拡張スタイルを設定しない場合は矢印ボタンは表示されず、元のボタンを押下することでメニューが表示されます。
矢印ボタンをクリックすると、後述するWM_NOTIFY
メッセージが親ウィンドウに送信されます。
拡張スタイルを設定しない場合、ボタンクリック時にWM_NOTIFY
メッセージが送信され、WM_COMMAND
メッセージは送信されなくなります。
WM_NOTIFYメッセージの処理
ツールバーの矢印ボタンを押下すると、親ウィンドウにWM_NOTIFY
メッセージが送信されます。
WM_NOTIFY
メッセージの通知コードがTBN_DROPDOWN
のとき、ツールバーボタンのドロップダウンメニューの表示要求です。
(WM_NOTIFYメッセージの項も参照してください)
NMTOOLBAR構造体
通知コードがTBN_DROPDOWN
のとき、LPARAMはNMTOOLBAR
構造体のポインタが格納されています。
- typedef struct tagNMTOOLBARA {
NMHDR hdr;
int iItem;
TBBUTTON tbButton;
int cchText;
LPSTR pszText;
RECT rcButton;
} NMTOOLBARA, *LPNMTOOLBARA; - ツールバーボタンの情報を格納する構造体。
hdr
メンバはNMHDR構造体です。
iItem
メンバは通知コードを送信したボタンの識別子です。
tbButton
メンバはボタンのTBBUTTON構造体です。
ただしTB_SETEXTENDEDSTYLE
通知コードでは使用できません。
(無意味な値が格納されている)
cchText
メンバはボタンのテキストの文字数です。
pszText
メンバはボタンテキストが格納されているバッファへのポインタです。
rcButton
メンバはボタンが表示されている領域を示すRECT構造体です。
(ツールバーを基準とした座標)
ただしあまりに古いWindows(9x系など)ではこのメンバは使用できません。
処理の流れとしては
WM_NOTIFY
メッセージで通知コード(LPARAM、NMHDR構造体のcode
メンバ)を調べるTBN_DROPDOWN
のとき、LPARAMをNMTOOLBAR構造体にキャストNMTOOLBAR構造体
のiItem
メンバでドロップダウンメニューを表示するボタンを識別
となります。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
NMTOOLBAR *nmtb;
switch (message)
{
case WM_NOTIFY:
switch (((NMHDR*)lParam)->code) {
case TBN_DROPDOWN:
nmtb = (NMTOOLBAR*)lParam;
switch (nmtb->iItem) {
case IDC_A:
//ボタンIDC_Aのドロップダウンメニュー表示処理
TB_GETRECTメッセージ
ドロップダウンメニューの表示位置を決定するために、ツールバーボタンの表示領域を取得する必要があります。
最近のOSではNMTOOLBAR構造体のrcButton
メンバにボタンの矩形領域が格納されているので、これをそのまま使用します。
古いOS(Win9x系など)ではこのメンバは使用できないので、TB_GETRECT
メッセージで座標を取得します。
RECT rc;
SendMessage(hToolbar, TB_GETRECT, nmtb->iItem, &rc);
WPARAMは座標を取得するツールバーボタンの識別子を指定します。
LPARAMは座標を格納するRECT構造体のポインタを指定します。
成功した場合は0を、失敗した場合は0以外を返します。
座標の変換
rcButton
メンバやTB_GETRECT
メッセージで取得されるボタン領域はツールバーを基準とした座標です。
メニューを表示する位置はスクリーン座標で指定する必要があるので、MapWindowPoints
関数で座標を変換します。
- int MapWindowPoints(
HWND hWndFrom,
HWND hWndTo,
LPPOINT lpPoints,
UINT cPoints
); - ウィンドウhWndFromの座標を基準にしたポイントlpPointsの座標情報を、ウィンドウhWndToから見た相対的な座標に変換する。
戻り値の下位ワードは変換後の座標に加算されたx座標のピクセル数。
上位ワードは変換後の座標に加算されたy座標のピクセル数。
第一引数hWndFrom
は変換元となるウィンドウのハンドルです。
第二引数hWndTo
は変換先となるウィンドウのハンドルです。
これらはNULL
またはHWND_DESKTOP
という定数を指定すると、スクリーン座標が基準となります。
第三引数lpPoints
は変換される座標です。
これはLPPOINT型、つまりPOINT型のポインタです。
POINT型はx座標とy座標の二つの情報で位置を表すデータ型ですが、第三引数にはRECT構造体を指定することもできます。
またPOINT型の配列を指定して複数を同時に変換することもできます。
第四引数cPoints
は第三引数lpPoints
に指定したPOINT型の数です。
通常は「1」を指定しますが、第三引数にPOINT型の配列を指定した場合は配列の要素数を指定します。
RECT構造体を指定した場合、ここには「2」を指定する必要があります。
MapWindowPoints(hToolbar, HWND_DESKTOP, (LPPOINT)&nmtb->rcButton, 2);
メニューの表示
後はTrackPopupMenuEx関数を使用して、ボタン座標のすぐ下にショートカットメニュー(ポップアップメニュー)を表示します。
TPMPARAMS tpm;
tpm.cbSize = sizeof(TPMPARAMS);
tpm.rcExclude = nmtb->rcButton;
hMenuPopup = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1));
hMenuPopupSub = GetSubMenu(hMenuPopup, 0);
TrackPopupMenuEx(hMenuPopupSub,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,
nmtb->rcButton.left, nmtb->rcButton.bottom, hWnd, &tpm);
DestroyMenu(hMenuPopup);
TrackPopupMenuEx関数の最後の引数は、メニューの表示禁止領域の指定です。
(TPMPARAMS構造体。詳しくは」TrackPopupMenuEx関数のページを参照)
ここに先ほど取得したボタン領域を示すRECT構造体を指定することで、メニューはボタンに重ならない位置に表示されるようになります。
最後にDestroyMenu
関数でロードしたメニューを破棄します。
TrackPopupMenuEx関数の実行中(メニューが表示されている間)は処理は一時停止するので、メニューが閉じられた後にDestroyMenu関数が実行されることになります。
コード全体
サンプルコード全体を示します。
#pragma comment(lib, "Comctl32.lib")
#include <windows.h>
#include <commctrl.h>
#include "resource.h"
//ウィンドウの生成等は省略
HWND CreateToolbar(HWND hWnd)
{
//コモンコントロールの初期化
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
icc.dwICC = ICC_BAR_CLASSES;
InitCommonControlsEx(&icc);
//ツールバー作成
HWND hToolbar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hWnd, NULL, hInst, NULL);
//ツールバー初期化処理
SendMessage(hToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
SendMessage(hToolbar, TB_SETBITMAPSIZE, 0, MAKELPARAM(16, 15));
//ビットマップの色の置き換え
COLORMAP colorMap;
colorMap.from = RGB(255, 255, 255);
colorMap.to = GetSysColor(COLOR_BTNFACE);
HBITMAP hbm = CreateMappedBitmap(hInst, IDB_BITMAP1, 0, &colorMap, 1);
//ビットマップの登録
TBADDBITMAP tb;
tb.hInst = NULL;
tb.nID = (UINT_PTR)hbm;
int index = SendMessage(hToolbar, TB_ADDBITMAP, (WPARAM)5, (LPARAM)&tb);
//ボタンの登録
TBBUTTON tbb[] = {
{ index++, IDC_A, TBSTATE_ENABLED, BTNS_DROPDOWN },
{ index++, IDC_I, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_U, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_E, TBSTATE_ENABLED, BTNS_BUTTON },
{ index++, IDC_O, TBSTATE_ENABLED, BTNS_BUTTON }
};
SendMessage(hToolbar, TB_ADDBUTTONS, (WPARAM)5, (LPARAM)&tbb);
//ツールバーに拡張スタイル(TBSTYLE_EX_DRAWDDARROWS)を設定
SendMessage(hToolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS);
return hToolbar;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hToolbar;
NMTOOLBAR *nmtb;
TPMPARAMS tpm;
HMENU hMenuPopup;
HMENU hMenuPopupSub;
switch (message)
{
case WM_CREATE: //ウィンドウの作成
hToolbar = CreateToolbar(hWnd);
break;
case WM_SIZE: //ウィンドウサイズの変更
SendMessage(hToolbar, TB_AUTOSIZE, 0, 0);
break;
case WM_NOTIFY: //コモンコントロールからの通知
switch (((NMHDR*)lParam)->code) {
case TBN_DROPDOWN: //ドロップダウンメニューの表示要求
nmtb = (NMTOOLBAR*)lParam;
switch (nmtb->iItem) {
case IDC_A:
//座標返還
MapWindowPoints(hToolbar, HWND_DESKTOP, (LPPOINT)&nmtb->rcButton, 2);
//メニュー位置調整
tpm.cbSize = sizeof(TPMPARAMS);
tpm.rcExclude = nmtb->rcButton;
//ドロップダウンメニュー表示
hMenuPopup = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU1));
hMenuPopupSub = GetSubMenu(hMenuPopup, 0);
TrackPopupMenuEx(hMenuPopupSub,
TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,
nmtb->rcButton.left, nmtb->rcButton.bottom, hWnd, &tpm);
DestroyMenu(hMenuPopup);
break;
}
break;
}
break;
case WM_COMMAND: //コントロールの操作
switch (LOWORD(wParam))
{
case ID_A:
MessageBox(hWnd, L"あ", L"情報", MB_OK);
break;
case ID_KA:
MessageBox(hWnd, L"か", L"情報", MB_OK);
break;
case ID_SA:
MessageBox(hWnd, L"さ", L"情報", MB_OK);
break;
case ID_TA:
MessageBox(hWnd, L"た", L"情報", MB_OK);
break;
case ID_NA:
MessageBox(hWnd, L"な", L"情報", MB_OK);
break;
}
break;
case WM_DESTROY: //ウィンドウの破棄
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}