EternalWindows
ツリービュー / カスタムドロー

今回は、ツリービューにおけるカスタムドローの実装例を示します。 カスタムドローを使用すれば、アイテムの見た目をカスタマイズすることができます。

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

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

HTREEITEM InsertTreeItem(HWND hwndTreeView, HTREEITEM hitemParent, LPTSTR lpszName, int nItemIndex);
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 hwndTreeView = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		HTREEITEM            hitemRoot, hitemParent;
		INITCOMMONCONTROLSEX ic;
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_TREEVIEW_CLASSES;
		InitCommonControlsEx(&ic);
		
		hwndTreeView = CreateWindowEx(0, WC_TREEVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		hitemRoot = InsertTreeItem(hwndTreeView, TVI_ROOT, TEXT("A"), 1);
		hitemParent = InsertTreeItem(hwndTreeView, hitemRoot, TEXT("B"), 2);
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("C"), 3);
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("D"), 4);

		return 0;
	}
	
	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == NM_CUSTOMDRAW) {
			LPNMTVCUSTOMDRAW lpCustomDraw = (LPNMTVCUSTOMDRAW)lParam;

			if (lpCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
				return CDRF_NOTIFYITEMDRAW;
			else if (lpCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
				if (lpCustomDraw->nmcd.lItemlParam == 3) {
					lpCustomDraw->clrText = RGB(255, 255, 255);
					lpCustomDraw->clrTextBk = RGB(255, 0, 0);
				}
				return CDRF_NEWFONT;
			}
			else
				;
		}
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndTreeView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

HTREEITEM InsertTreeItem(HWND hwndTreeView, HTREEITEM hitemParent, LPTSTR lpszName, int nItemIndex)
{
	TVINSERTSTRUCT is;

	is.hParent      = hitemParent;
	is.item.mask    = TVIF_TEXT | TVIF_PARAM;
	is.item.pszText = lpszName;
	is.item.lParam  = nItemIndex;
	
	return TreeView_InsertItem(hwndTreeView, &is);
}

カスタムドローの際には、通知コードがNM_CUSTOMDRAWであるWM_NOTIFYが送られます。 lParamにはNMTVCUSTOMDRAW構造体のアドレスが格納されており、 nmcd.dwDrawStageからは現在の描画段階を示す描画ステージが参照できます。 これがCDDS_PREPAINTである場合はこれから描画が行われることを意味し、 CDRF_NOTIFYITEMDRAWを返すことで引き続き描画に関する通知を受け取ることができます。 描画ステージがCDDS_ITEMPREPAINTである場合は正にアイテムが描画されようとしていることを意味し、 ここでclrTextを変更すればテキスト色が、clrTextBkを変更すれば背景色を決定できます。 変更対象とするアイテムは、lItemlParamが3であるアイテムとしています。

NM_CUSTOMDRAWでlItemlParamを参照するためには、TreeView_InsertItemでlParamを設定しておく必要があります。 これには、item.maskにTVIF_PARAMを指定し、item.lParamに任意の値を指定します。 そもそも、lParamが必要になる理由は、NM_CUSTOMDRAWで現在どのアイテムが描画対象になっているかが分からないからであり、 アイテムを区別するための手段としてlParamを使用するようにしたわけです。 今回の場合では、lParamが3であるアイテムはCというテキストを持ちますから、 Cというアイテムの見た目がカスタマイズされることになります。


戻る