EternalWindows
ツリービュー / チェックボックスの表示

ツリービューのアイテムにチェックボックスを表示するには、ウインドウスタイルにTVS_CHECKBOXESを指定するようにします。 アイテムがチェックされているかを確認するには、TreeView_GetCheckStateを呼び出します。

UINT TreeView_GetCheckState(
  HWND hwndTV,
  HTREEITEM hItem
);

hwndTVは、ツリービューのウインドウハンドルを指定します。 hItemは、チェックを確認したいアイテムのハンドルを指定します。 戻り値が1の場合はアイテムがチェックされていることを意味し、 0の場合はアイテムがチェックされていないことを意味します。

今回のプログラムは、ツリービューのアイテムにチェックボックスを表示します。 アイテムがチェックされた場合、そのアイテムの名前をウインドウタイトルに表示します。

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

#define WM_CHECKSTATECHANGE WM_APP

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

HTREEITEM InsertTreeItem(HWND hwndTreeView, HTREEITEM hitemParent, LPTSTR lpszName);
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_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_CHECKBOXES, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		hitemRoot = InsertTreeItem(hwndTreeView, TVI_ROOT, TEXT("A"));
		hitemParent = InsertTreeItem(hwndTreeView, hitemRoot, TEXT("B"));
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("C"));
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("D"));

		return 0;
	}
	
	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == NM_CLICK) {
			TVHITTESTINFO ht;
			
			GetCursorPos(&ht.pt);
			ScreenToClient(((LPNMHDR)lParam)->hwndFrom, &ht.pt);

			TreeView_HitTest(((LPNMHDR)lParam)->hwndFrom, &ht);
			if (ht.flags & TVHT_ONITEMSTATEICON)
				PostMessage(hwnd, WM_CHECKSTATECHANGE, 0, (LPARAM)ht.hItem);
		}
		else if (((LPNMHDR)lParam)->code == TVN_KEYDOWN) { 
			if (((LPNMTVKEYDOWN)lParam)->wVKey == VK_SPACE)
				PostMessage(hwnd, WM_CHECKSTATECHANGE, 0, (LPARAM)TreeView_GetSelection(hwndTreeView));
		}
		return 0;

	case WM_CHECKSTATECHANGE: {
		int       nChecked;
		TCHAR     szName[256];
		TCHAR     szBuf[256];
		TVITEMEX  item;
		HTREEITEM hitem = (HTREEITEM)lParam;

		item.mask       = TVIF_HANDLE | TVIF_TEXT;
		item.hItem      = hitem;
		item.pszText    = szName;
		item.cchTextMax = sizeof(szName) / sizeof(TCHAR);
		TreeView_GetItem(hwndTreeView, &item);

		nChecked = TreeView_GetCheckState(hwndTreeView, hitem);
		if (nChecked == 1) {
			wsprintf(szBuf, TEXT("アイテム%s Check"), szName);
			SetWindowText(hwnd, szBuf);
		}
		else if (nChecked == 0) {
			wsprintf(szBuf, TEXT("アイテム%s Uncheck"), szName);
			SetWindowText(hwnd, szBuf);
		}
		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)
{
	TVINSERTSTRUCT is;

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

チェックボックスにチェックが付けられた瞬間を検出するには、 NM_CLICKを処理することになります。 この通知コードはアイテムがクリックされたことを通知するものですが、 TreeView_HitTestの結果にTVHT_ONITEMSTATEICONが含まれている場合は、 チェックボックスがクリックされたことを特定できます。 TreeView_HitTestは、TVHITTESTINFO.ptに位置するアイテムのハンドルと情報を取得するため、 ptには現在のカーソル座標を指定することになります。

WM_CHECKSTATECHANGEという独自のメッセージをポストして、そこでチェックを確認しているのには理由があります。 それは、NM_CLICKの時点では内部的にチェックの状態が変化していないからです。 つまり、NM_CLICKでTreeView_GetCheckStateを呼び出しても正しい結果は得られないことになります。 試しに、PostMessageの代わりにSendMessageを呼び出せば、WM_CHECKSTATECHANGEが直ちに処理されることになりますが、 これでは正しい結果が得られないことが分かります。 DefWindowProcを呼び出して、デフォルト処理を先に行うという方法も考えられそうですが、 これも上手くいきません。

アイテムの選択は、クリック以外にSpaceキーの押下で行われることもあります。 キーが押下された場合は、通知コードとしてTVN_KEYDOWNが送られるため、 このときにNMTVKEYDOWN.wVKeyがVK_SPACEであるかを確認します。 NM_CLICKと同じく、TVN_KEYDOWNの時点でもチェックボックスの状態は変化していないため、 独自のメッセージをポストすることになります。 LPARAMはアイテムのハンドルでなければならないため、 現在選択されているアイテムを取得するTreeView_GetSelectionを呼び出しています。


戻る