EternalWindows
ツリービュー / アイテムの展開

ツリービューは階層構造になっているため、 下に存在するアイテムを選択するためには、その上に存在するアイテムを展開しなければなりません。 ただし、特定のアイテムを検索するような場合はユーザーに明示的に展開してもらうわけにはいきませんから、 アプリケーション内からアイテムを展開する方法を理解しておく必要があります。 これには、TreeView_Expandを呼び出します。

BOOL TreeView_Expand(
  HWND hwndTV,
  HTREEITEM hItem,
  UINT flag
);

hwndTVは、ツリービューのハンドルを指定します。 hitemは、アイテムのハンドルを指定します。 flagは、展開するかどうかを表す定数を指定します。 TVE_EXPANDを指定すると展開が行われ、TVE_COLLAPSEを指定すると展開が閉じられます。

アイテムが展開されているならば、TreeView_GetNextItemで下のアイテムを参照することができます。

HTREEITEM TreeView_GetNextItem(
  HWND hwndTV,
  HTREEITEM hitem,
  UINT flag
);

hwndTVは、ツリービューハンドルを指定します。 hitemは、アイテムのハンドルを指定します。 flagは、どのような種類のアイテムを取得するかを表す定数を指定します。 TVGN_CHILDを指定すると子アイテムのハンドルが返り、TVGN_NEXTを指定すると次のアイテムのハンドルが返ります。

目的のアイテムを発見できた場合は、そのアイテムを選択状態にしたいことがあります。 これには、TreeView_Selectを呼び出します。

BOOL TreeView_Select(
  HWND hwndTV,
  HTREEITEM hitem,
  UINT flag
);

hwndTVは、ツリービューのハンドルを指定します。 hitemは、アイテムのハンドルを指定します。 flagは、アイテムに対する動作を表す定数を指定します。 TVGN_CARETを指定するとアイテムは選択されます。

今回のプログラムは、特定のテキストを持ったアイテムをツリービューから検索します。

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

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

BOOL SearchTreeItem(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName);
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_HASLINES | TVS_HASBUTTONS | 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"));
		 
		hitemParent = InsertTreeItem(hwndTreeView, hitemRoot, TEXT("E"));
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("F"));
		InsertTreeItem(hwndTreeView, hitemParent, TEXT("G"));
		
		SearchTreeItem(hwndTreeView, TVI_ROOT, TEXT("F"));

		SetFocus(hwndTreeView);

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

BOOL SearchTreeItem(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName)
{
	BOOL     bEqual = FALSE;
	TCHAR    szBuf[256];
	TVITEMEX item;

	TreeView_Expand(hwndTreeView, hitem, TVE_EXPAND);
	hitem = TreeView_GetNextItem(hwndTreeView, hitem, TVGN_CHILD);

	while (hitem != NULL) {
		item.mask       = TVIF_HANDLE | TVIF_TEXT | TVIF_CHILDREN;
		item.hItem      = hitem;
		item.pszText    = szBuf;
		item.cchTextMax = sizeof(szBuf) / sizeof(TCHAR);
		TreeView_GetItem(hwndTreeView, &item);

		if (lstrcmp(szBuf, lpszName) == 0) {
			TreeView_Select(hwndTreeView, hitem, TVGN_CARET);
			bEqual = TRUE;
			break;
		}
		else if (item.cChildren > 0) {
			bEqual = SearchTreeItem(hwndTreeView, hitem, lpszName);
			if (bEqual)
				break;
			else
				TreeView_Expand(hwndTreeView, hitem, TVE_COLLAPSE);
		}
		else
			;
		
		hitem = TreeView_GetNextItem(hwndTreeView, hitem, TVGN_NEXT);
	}
	
	return bEqual;
}

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

SearchTreeItemという自作関数は、第3引数のテキストを持つアイテムをツリービューから検索します。 第2引数は子を持つ親アイテムのハンドルであり、まずTreeView_Expandで親アイテムを展開します。 これにより、TreeView_GetNextItemにTVGN_CHILDを指定して子アイテムのハンドルを取得することができます。 次に、このアイテムのテキストをTreeView_GetItemで取得し、 目的のテキストと一致するかを調べるためにlstrcmpを呼び出します。 これが一致した場合は、アイテムを選択状態にしてループ文を抜けることになります。 一致しなかった場合は、そのアイテムが子アイテムを持つかどうかを調べるためにcChildrenを確認します。 このメンバは、TVITEMEX.maskにTVIF_CHILDRENを指定した場合に初期化され、 0より大きい場合は子を持つということで、このアイテムを親としてTreeView_GetNextItem(TVGN_CHILD指定)を呼び出すことができます。 つまり、SearchTreeItemを再帰的に呼び出すことができるため、 最下層のアイテムまで検索できることになります。 SearchTreeItemがFALSEの場合は、hitemの下には目的のテキストを持ったアイテムが存在しなかったことを意味するため、 展開を閉じるためにTreeView_ExpandにTVE_COLLAPSEを指定します。 この後は、下のアイテムではなく同階層の次のアイテムを調べるために、TVGN_NEXTを指定してTreeView_GetNextItemを呼び出します。 SearchTreeItemの後にSetFocusを呼び出しているのは、 ツリービューにフォーカスを持たすことで、 選択されたアイテムをハイライトに表示するためです。

今回は指定したテキストを持つアイテムを検索しましたが、 目的のアイテムのハンドルを既に取得している場合は、 TreeView_EnsureVisibleを呼び出すことでアイテムを発見することができます。

hitem = InsertTreeItem(hwndTreeView, hitemParent, TEXT("F"));

TreeView_EnsureVisible(hwndTreeView, hitem);

TreeView_EnsureVisibleを呼び出すと、第2引数に指定したアイテムは確実に表示されることになります。


戻る