EternalWindows
リストビュー / グループ化

今回は、リストビューをグループ化します。 次に、グループ化したリストビューを示します。

詳細表示のリストビューでは上から下へとアイテムが追加されていくわけですが、 これらの間はグループ化することができます。 ここで言うグループ化とは、「ここからここまではA系統のアイテム」というように区切ることであり、 この区切りによりアイテムの種類が明確になります。

アイテムをグループ化するには、ListView_InsertGroupでグループを作成します。

int ListView_InsertGroup(
  HWND hwnd,
  int index,
  PLVGROUP pgrp
);

hwndは、リストビューのハンドルを指定します。 indexは、グループが追加されるアイテムのインデックスを指定します。 -1を指定した場合は、リストの最後に追加されます。 pgrpは、LVGROUP構造体を指定します。

LVGROUP構造体は、次のように定義されています。

typedef struct tagLVGROUP
{
    UINT    cbSize;
    UINT    mask;
    LPWSTR  pszHeader;
    int     cchHeader;

    LPWSTR  pszFooter;
    int     cchFooter;

    int     iGroupId;

    UINT    stateMask;
    UINT    state;
    UINT    uAlign;
#if _WIN32_WINNT >= 0x0600
    LPWSTR  pszSubtitle;
    UINT    cchSubtitle;
    LPWSTR  pszTask;
    UINT    cchTask;
    LPWSTR  pszDescriptionTop;
    UINT    cchDescriptionTop;
    LPWSTR  pszDescriptionBottom;
    UINT    cchDescriptionBottom;
    int     iTitleImage;
    int     iExtendedImage;
    int     iFirstItem;         // Read only
    UINT    cItems;             // Read only
    LPWSTR  pszSubsetTitle;     // NULL if group is not subset
    UINT    cchSubsetTitle;
#endif
} LVGROUP, *PLVGROUP;

cbSizeは、この構造体のサイズを指定します。 maskは、初期化するメンバを表す定数を指定します。 pszHeaderは、ヘッダ名を表す文字列を指定します。 cchHeaderは、pszHeaderのサイズを指定します。 ヘッダ名を取得しない場合は無視して問題ありません。 pszFooterは、フッタ名を表す文字列を指定します。 cchFooterは、pszFooterのサイズを指定します。 フッタ名を取得しない場合は無視して問題ありません。 iGroupIdは、グループのIDを指定します。 stateMaskは、グループの状態を表す定数を指定します。 グループを作成する場合は無視して問題ありません。 stateは、グループの状態を表す定数を指定します。 グループを作成する場合は無視して問題ありません。 uAlignは、ヘッダ名を描画する位置を表す定数を指定します。 これ以降のメンバはWindows Vistaから使用可能です。

アイテムがグループ化されていることを示す区切りを表示するには、ListView_EnableGroupViewを呼び出します。

int ListView_EnableGroupView(
  HWND hwnd,
  BOOL fEnable
);

hwndは、リストビューのハンドルを指定します。 fEnable、グループの区切りを表示する場合にTRUEを指定します。

アイテムをグループ化したことにより、特定のグループのアイテムを表示したり非表示にしたりすることが可能になります。 こうしたグループの状態を変更する場合は、ListView_SetGroupStateを呼び出します。

LRESULT ListView_SetGroupState(
  HWND hwnd,
  UINT dwGroupId,
  UINT dwMask,
  UINT dwState
);

hwndは、リストビューのハンドルを指定します。 dwGroupIdは、設定対象とするグループのIDを指定します。 dwMaskは、設定対象の状態を表す定数を指定します。 dwStateは、dwMaskの状態をどのように設定するかを表す定数を指定します。

ヘッダ名など、グループの情報を変更したい場合はListView_SetGroupInfoを呼び出します。

int ListView_SetGroupInfo(
  HWND hwnd,
  int iGroupId,
  PLVGROUP pgrp
);

hwndは、リストビューのハンドルを指定します。 iGroupIdは、設定対象とするグループのIDを指定します。 pgrpは、LVGROUP構造体のアドレスを指定します。

今回のプログラムは、リストビューのアイテムをグループ化します。 バージョン5系列のcomctl32.dllを使用している場合はグループ化ができないため、 プロジェクトにマニフェストファイルを追加する必要があります。

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

#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 hwndListView = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		LVITEM               item;
		LVCOLUMN             column;
		LVGROUP              group;
		INITCOMMONCONTROLSEX ic;
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_LISTVIEW_CLASSES;
		InitCommonControlsEx(&ic);
		
		hwndListView = CreateWindowEx(0, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		group.cbSize    = sizeof(LVGROUP);
		group.mask      = LVGF_HEADER | LVGF_GROUPID | LVGF_TASK;
		group.pszHeader = TEXT("Group1");
		group.iGroupId  = 1;
		group.pszTask   = L"Hide";
		ListView_InsertGroup(hwndListView, -1, &group);

		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 100;
		column.pszText = TEXT("col1");
		ListView_InsertColumn(hwndListView, 0, &column);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 100;
		column.pszText = TEXT("col2");
		ListView_InsertColumn(hwndListView, 1, &column);

		item.mask     = LVIF_TEXT | LVIF_GROUPID;
		item.iItem    = 0;
		item.iSubItem = 0;
		item.pszText  = TEXT("item1");
		item.iGroupId = 1;
		ListView_InsertItem(hwndListView, &item);
		ListView_SetItemText(hwndListView, 0, 1, L"subitem");
		
		ListView_EnableGroupView(hwndListView, TRUE);
		return 0;
	}

	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == LVN_LINKCLICK) {
			LVGROUP   group;
			PNMLVLINK pLinkInfo = (PNMLVLINK)lParam;
			int       nGroupId = pLinkInfo->iSubItem;

			if (ListView_GetGroupState(hwndListView, nGroupId, LVGS_COLLAPSED)) {
				ListView_SetGroupState(hwndListView, nGroupId, LVGS_COLLAPSED, 0);
				group.pszTask = L"Hide";
			}
			else {
				ListView_SetGroupState(hwndListView, nGroupId, LVGS_COLLAPSED, LVGS_COLLAPSED);
				group.pszTask = L"Show";
			}
			
			group.cbSize = sizeof(LVGROUP);
			group.mask   = LVGF_TASK;
			ListView_SetGroupInfo(hwndListView, nGroupId, &group);

			return 0;
		}

		break;
	
	case WM_SIZE:
		MoveWindow(hwndListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

ListView_InsertGroupを呼び出すには、LVGROUP構造体を初期化する必要があります。 maskには使用するメンバを表す定数を指定し、今回はLVGF_HEADER、LVGF_GROUPID、LVGF_TASKを指定していることから、 pszHeader、iGroupId、pszTaskを初期化することになります。 iGroupIdに1を指定していることから作成されるグループのIDは1となり、 これはListView_InsertItemに指定するLVITEM構造体にも指定することになります。 具体的には、maskにLVIF_GROUPIDを指定し、iGroupIdに作成済みのグループのIDを指定します。 これにより、このアイテムは指定したIDのグループに属することになります。 なお、サブアイテムの設定をListView_SetItemTextで行っているのは、 ListView_SetItemの呼び出しが上手くいかなかったためです。

LVGROUP構造体にpszTaskを指定してListView_InsertGroupを呼び出した場合、 グループのヘッダの右端にリンクが表示されるようになります。 そして、このリンクをクリックした場合はWM_NOTIFYでLVN_LINKCLICKが送られることになります。 ここでどのような事をするかは自由ですが、今回はグループのアイテムの表示/非表示を切り替えるようにしています。 ListView_GetGroupStateにLVGS_COLLAPSEDを指定して0以外の値を返った場合は、 アイテムが非表示であることを意味するため、 アイテムを表示するためにListView_SetGroupStateの第4引数に0を指定します。 また、次のリンクのクリックがアイテムの非表示と解釈されるように、pszTaskにHideを指定します。 else文の方は、if文とは逆にアイテムを非表示にし、pszTaskにTRUEを指定します。 ListView_SetGroupInfoの呼び出しによって、リンクの名前が変更されることになります。


戻る