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

リストビューのアイテムにチェックボックスを表示するには、 リストビューの拡張スタイルにLVS_EX_CHECKBOXESを指定する必要があります。 拡張スタイルの設定を行うには、ListView_SetExtendedListViewStyleExを呼び出します。

void ListView_SetExtendedListViewStyleEx(
  HWND hwndLV,
  DWORD dwExMask,
  DWORD dwExStyle
);

hwndLVは、リストビューのハンドルを指定します。 dwExMaskは、変更したい拡張スタイルを表す定数を指定します。 dwExStyleは、dwExMaskに指定したスタイルをどうするかを指定します。 dwExMaskと同じ値を指定すればdwExMaskのスタイルは実際に設定され、 0を指定すればdwExMaskのスタイルは取り除かれます。

拡張スタイルの設定には、ListView_SetExtendedListViewStyleでも行うことができますが、 このマクロを呼び出す場合は以下のように処理が多くなります。

DWORD dwExStyle;
dwExStyle  = ListView_GetExtendedListViewStyle(hwndListView);
dwExStyle |= LVS_EX_CHECKBOXES;
ListView_SetExtendedListViewStyle(hwndListView, dwExStyle);

// ListView_SetExtendedListViewStyleExの場合は次の一行だけで済む
ListView_SetExtendedListViewStyleEx(hwndListView, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);

ListView_SetExtendedListViewStyleに直接LVS_EX_CHECKBOXESを指定していないのは、 それによって既存の拡張スタイルを上書きしてしまうからです。 よって、ListView_GetExtendedListViewStyleで既存の拡張スタイルを取得し、 それにLVS_EX_CHECKBOXESを組み合わせるようにしています。 一方、ListView_SetExtendedListViewStyleExにはマスクがありますから、 変更したい拡張スタイルのみを対象にすることができます。

特定のアイテムがチェックされているかを調べるには、 ListView_GetCheckStateを呼び出します。

BOOL ListView_GetCheckState(
  HWND hwndLV,
  UINT iIndex
);

hwndLVは、リストビューのハンドルを指定します。 iIndexは、チェックを確認したいアイテムのインデックスを指定します。

今回のプログラムは、リストビューのアイテムにチェックボックスを表示します。

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

#define WM_CHECKSTATECHANGE WM_APP

#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;
		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);
		
		ListView_SetExtendedListViewStyleEx(hwndListView, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES);

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

		item.mask     = LVIF_TEXT;
		item.iItem    = 0;
		item.iSubItem = 0;
		item.pszText  = TEXT("item1");
		ListView_InsertItem(hwndListView, &item);

		item.mask     = LVIF_TEXT;
		item.iItem    = 1;
		item.iSubItem = 0;
		item.pszText  = TEXT("item2");
		ListView_InsertItem(hwndListView, &item);

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

			ListView_HitTest(((LPNMHDR)lParam)->hwndFrom, &ht);
			if (ht.flags & LVHT_ONITEMSTATEICON)
				PostMessage(hwnd, WM_CHECKSTATECHANGE, 0, (LPARAM)ht.iItem);
		}
		else if (((LPNMHDR)lParam)->code == LVN_KEYDOWN) { 
			if (((LPNMLVKEYDOWN)lParam)->wVKey == VK_SPACE) {
				int i;
				int nCount = ListView_GetItemCount(hwndListView);

				for (i = 0; i < nCount; i++) {
					if (ListView_GetItemState(hwndListView, i, LVIS_SELECTED)) {
						PostMessage(hwnd, WM_CHECKSTATECHANGE, 0, (LPARAM)i);
						break;
					}
				}
			}
		}

		return 0;


	case WM_CHECKSTATECHANGE: {
		int    i = (int)lParam;
		TCHAR  szName[256];
		TCHAR  szBuf[256];
		LVITEM item;

		item.mask       = LVIF_TEXT;
		item.iItem      = i;
		item.iSubItem   = 0;
		item.pszText    = szName;
		item.cchTextMax = sizeof(szName) / sizeof(TCHAR);
		ListView_GetItem(hwndListView, &item);

		if (ListView_GetCheckState(hwndListView, i)) {
			wsprintf(szBuf, TEXT("%s Check"), szName);
			SetWindowText(hwnd, szBuf);
		}
		else {
			wsprintf(szBuf, TEXT("%s Uncheck"), szName);
			SetWindowText(hwnd, szBuf);
		}

		return 0;
	}
	
	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_GetCheckStateを呼び出せばよいだけです。 しかし、チェックが入った瞬間を特定したいような場合は、少し処理が複雑なことになります。 リストビューにはアイテムが選択されたことを示すLVN_ITEMCHANGEDなどはありますが、 チェックが入ったときに送られる通知メッセージは存在しないからです。 したがって、やや強引な方法で判定を行うことになります。

考え方としては、アイテムのクリックを検出し、その位置がチェックボックスに相当するかどうかです。 コントロールがクリックされたときに送られる基本的なメッセージとして、 NM_CLICK、NM_RCLICK(右クリック)、NM_DBLCLK(ダブルクリック)があるため、まずこれらを検出します。 次にListView_HitTestを呼び出すことによって、アイテムのどの箇所がクリックされたのかを確認しますが、 このためにはクリックされた座標をLVHITTESTINFO.ptに格納しておく必要があります。 これは、GetCursorPosで現在のカーソルの位置を取得し、それをScreenToClientでクライアント座標に変換すればよいでしょう。 LVHITTESTINFO.flagsを確認すればクリックされた箇所が分かるようになり、 LVHT_ONITEMSTATEICONが含まれる場合は、チェックボックスの箇所をクリックしたことになります。 よって、ここでListView_GetCheckStateを呼び出せばよいように思えますが、 この時点ではまだチェックボックスの内容が更新されていないため、 チェック確認の前にデフォルト処理が実行される必要があります。 PostMessageでポストしたメッセージがWindowProcに送られる頃には、 そのようなデフォルト処理も実行されているため、 このメッセージ内ならばチェックの状態を正しく確認できます。

チェックボックスを表示するリストビューでは、 Enterキーを押すことでアイテムをチェックできるようになっています。 この瞬間を特定するためには、Spaceキーが押されたことを通知するLVN_KEYDOWNを検出すればよいでしょう。 NMLVKEYDOWN構造体からは、現在選択されているアイテムが分からないため、 ListView_GetItemStateを呼び出すことで、選択されたアイテムのインデックスを求めます。 そして、このインッデクスをLPARAMとして独自メッセージをポストすることで、 先のクリックの例と同じようにチェックの確認ができます。


戻る