EternalWindows
ツールバー / カスタムドロー

カスタムドローとは、コントロールの一部をアプリケーションが独自に描画することを意味します。 ツールバーにおけるカスタムドローでは、ボタンのテキストや背景の色を自由に変更することができます。

#include <windows.h>
#include <commctrl.h>

#define ID_TOOLBAR 100
#define ID_OPEN    200
#define ID_SAVE    300
#define ID_STYLE   400

#pragma comment (lib, "comctl32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndToolbar = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		TBBUTTON tbButton[] = {
			{STD_FILEOPEN, ID_OPEN, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE, {0}, 0, (INT_PTR)TEXT("開く")},
			{STD_FILESAVE, ID_SAVE, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE, {0}, 0, (INT_PTR)TEXT("保存")},
			{I_IMAGENONE, ID_STYLE, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE, {0}, 0, (INT_PTR)TEXT("スタイル変更")}
		};
		int                  nCount = sizeof(tbButton) / sizeof(tbButton[0]);
		DWORD                dwStyle;
		INITCOMMONCONTROLSEX ic;
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_BAR_CLASSES;
		InitCommonControlsEx(&ic);

		hwndToolbar = CreateToolbarEx(hwnd, WS_CHILD | WS_VISIBLE | TBSTYLE_LIST, ID_TOOLBAR, 0, HINST_COMMCTRL, IDB_STD_SMALL_COLOR, tbButton, nCount, 0, 0, 0, 0, sizeof(TBBUTTON));

		dwStyle = (DWORD)SendMessage(hwndToolbar, TB_GETSTYLE, 0, 0) | TBSTYLE_FLAT;
		SendMessage(hwndToolbar, TB_SETSTYLE, 0, (LPARAM)dwStyle);

		return 0;
	}

	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == NM_CUSTOMDRAW) {
			LPNMTBCUSTOMDRAW lpCustomDraw = (LPNMTBCUSTOMDRAW)lParam;

			if (lpCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
				return CDRF_NOTIFYITEMDRAW;
			else if (lpCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
				if (lpCustomDraw->nmcd.dwItemSpec == ID_TEXT)
					lpCustomDraw->clrText = RGB(0, 0, 255);

				if (lpCustomDraw->nmcd.uItemState & CDIS_SELECTED) {
					lpCustomDraw->clrBtnFace = RGB(255, 0, 0);
					lpCustomDraw->clrHighlightHotTrack = RGB(255, 0, 0);
					return TBCDRF_HILITEHOTTRACK | TBCDRF_NOEDGES;
				}
				else if (lpCustomDraw->nmcd.uItemState & CDIS_HOT) {
					lpCustomDraw->clrBtnFace = RGB(0, 255, 200);
					lpCustomDraw->clrHighlightHotTrack = RGB(0, 255, 0);
					return TBCDRF_HILITEHOTTRACK | TBCDRF_NOEDGES;
				}
				else
					;				

				return TBCDRF_NOBACKGROUND;
			}
			else
				;
			
			return 0;
		}
		break;
	
	case WM_SIZE:
		MoveWindow(hwndToolbar, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_NOTIFYの通知コードがNM_CUSTOMDRAWである場合は、 カスタムドローに関する通知が送られてきたことを意味します。 lParamにはNMTBCUSTOMDRAW構造体が格納されており、 nmcd.dwDrawStageから現在の描画ステージを確認することができます。 描画ステージとはいわば描画段階であり、 CDDS_PREPAINTの際にCDRF_NOTIFYITEMDRAWを返すと、 アイテム(ツールバーのボタン)が描画される段階でさらに通知を受け取ることができます。 この際の描画ステージがCDDS_ITEMPREPAINTであり、 次のような処理になっています。

else if (lpCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
	if (lpCustomDraw->nmcd.dwItemSpec == ID_TEXT)
		lpCustomDraw->clrText = RGB(0, 0, 255);

	if (lpCustomDraw->nmcd.uItemState & CDIS_SELECTED) {
		lpCustomDraw->clrBtnFace = RGB(255, 0, 0);
		lpCustomDraw->clrHighlightHotTrack = RGB(255, 0, 0);
		return TBCDRF_HILITEHOTTRACK | TBCDRF_NOEDGES;
	}
	else if (lpCustomDraw->nmcd.uItemState & CDIS_HOT) {
		lpCustomDraw->clrBtnFace = RGB(255, 0, 255);
		lpCustomDraw->clrHighlightHotTrack = RGB(255, 0, 255);
		return TBCDRF_HILITEHOTTRACK | TBCDRF_NOEDGES;
	}
	else
		;				

	return TBCDRF_NOBACKGROUND;
}

nmcd.dwItemSpecにはボタンのIDが格納されており、 上記の例ではID_TEXTのボタンの色をRGB(0, 0, 255)にしています。 clrTextはボタンのテキスト色であるため、テキストは青色で表示されることになります。 nmcd.uItemStateはボタンの現在の状態を表し、 CDIS_SELECTEDが含まれる場合はボタンが選択状態、 CDIS_HOTが含まれる場合はボタンがホット状態であること意味します。 これらの場合はボタンの背景色を変更するということで、 アイコンの背景色(clrBtnFace)とアイコンを除くボタンの背景色(clrHighlightHotTrack)を初期化しています。 clrHighlightHotTrackを初期化する場合は、ツールバーがフラットになっており、 さらに戻り値としてTBCDRF_HILITEHOTTRACKを返す必要があります。 上記コードではTBCDRF_NOEDGESも返るようにしていますが、 これを指定するとホット状態における枠が表示されなくなります。 背景色を変更しない場合は、TBCDRF_NOBACKGROUNDを返します。


戻る