EternalWindows
ツリービュー / アイテムの複数選択

Windows Vistaからのツリービューは、拡張スタイルの設定をサポートするようになりました。 この拡張スタイルの中で特に有用であると思われるのは、 アイテムの複数選択を可能にするTVS_EX_MULTISELECT でしょう。 拡張スタイルを設定するには、TreeView_SetExtendedStyleを呼び出します。

HRESULT TreeView_SetExtendedStyle(
  HWND hwnd,
  DWORD dw,
  UINT mask
);

hwndは、ツリービューのハンドルを指定します。 dwは、maskに対して指定する値を指定します。 maskは、指定したい拡張スタイルを表す定数を指定します。

今回のプログラムは、Enterキーが押された場合に現在選択されているアイテムの名前を表示します。 バージョン5系列のcomctl32.dllを使用している場合はTVS_EX_MULTISELECTの効果が有効にならないため、 プロジェクトにマニフェストファイルを追加する必要があります。

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

#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, 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"));
		
		TreeView_SetExtendedStyle(hwndTreeView, TVS_EX_MULTISELECT, TVS_EX_MULTISELECT);

		return 0;
	}

	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == NM_RETURN) {
			HTREEITEM hitem;
			TVITEMEX  item;
			TCHAR     szBuf[256];

			hitem = TreeView_GetNextItem(hwndTreeView, NULL, TVGN_CARET);
			while (hitem != NULL) {
				item.mask       = TVIF_HANDLE | TVIF_TEXT;
				item.hItem      = hitem;
				item.pszText    = szBuf;
				item.cchTextMax = sizeof(szBuf) / sizeof(TCHAR);
				TreeView_GetItem(hwndTreeView, &item);
				MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
				
				hitem = TreeView_GetNextItem(hwndTreeView, hitem, TVGN_NEXTSELECTED);
			}
		}
		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);
}

WM_CREATEでは、TreeView_SetExtendedStyleを呼び出してTVS_EX_MULTISELECTを指定しています。 第2引数と第3引数は両方ともTVS_EX_MULTISELECTになっていますが、 第2引数は0を指定することもあります。 これを指定すると、拡張スタイルから第3引数のスタイル(今回はTVS_EX_MULTISELECT)が取り除かれます。 第2引数と第3引数の両方がTVS_EX_MULTISELECTであれば、拡張スタイルにTVS_EX_MULTISELECTが含まれることになりますから、 Shiftキーを押してアイテムを複数選択できるようになります。

キーボードフォーカスが割り当てられている場合にEnterキーを押した場合は、 通知コードがNM_RETURNであるWM_NOTIFYが送られます。 今回はこのタイミングで、複数選択されているアイテムの情報を取得しています。

case WM_NOTIFY:
	if (((LPNMHDR)lParam)->code == NM_RETURN) {
		HTREEITEM hitem;
		TVITEMEX  item;
		TCHAR     szBuf[256];

		hitem = TreeView_GetNextItem(hwndTreeView, NULL, TVGN_CARET);
		while (hitem != NULL) {
			item.mask       = TVIF_HANDLE | TVIF_TEXT;
			item.hItem      = hitem;
			item.pszText    = szBuf;
			item.cchTextMax = sizeof(szBuf) / sizeof(TCHAR);
			TreeView_GetItem(hwndTreeView, &item);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			
			hitem = TreeView_GetNextItem(hwndTreeView, hitem, TVGN_NEXTSELECTED);
		}
	}
	return 0;

アイテムの複数選択といっても、内部的に選択されているアイテムは1つだけであり、 残りのアイテムは単にハイライト表示になっているだけです。 この選択されているアイテムはTreeView_GetNextItemにTVGN_CARETを指定することで取得可能であり、 残りのアイテムはTreeView_GetNextItemにTVGN_NEXTSELECTEDを指定することで取得できます。 ちなみに、選択されたアイテムの数を取得するTreeView_GetSelectedCountは、 ハイライト表示だけであるアイテムも考慮します。


戻る