ツールバーのドロップダウンメニュー

ツールバーボタンにはドロップダウンメニューを付けることができます。
ドロップダウンメニューを持つボタンは、ボタンのすぐ右側に三角形(▼)のボタンが追加で表示され、これをクリックすることでメニューが表示されます。
ドロップダウンメニューを持つツールバーボタン

メニューリソースの用意

ツールバーのドロップダウンメニューはショートカットメニューをボタンの下に表示することで実現しています。
なので、まずは表示したいメニューのリソースを用意します。
ここでは以下のリソースを使用します。


/////////////////////////////////////////////////////////////////////////////
//
// 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;
}