EternalWindows
リストビュー / 仮想リストビュー

リストビューに追加するアイテムの数が非常に多い場合、 アイテムの表示に時間が掛かってしまうことがあります。 この問題を回避するためには、仮想リストビューという概念を取り入れ、 アイテムを追加する代わりに、アイテムが表示されるタイミングを検出することになります。 このタイミングでアイテムのテキストを設定するようにすれば、 アイテムがリストビューに追加されているように見せかけることができます。

仮想リストビューでは、アイテムの総数をリストビューが表示される前に設定しておく必要があります。 先に述べたように、仮想リストビューではアイテムの追加は行わないのですが、 アイテムの総数を設定することで、どれだけのスクロールが必要になるかを伝えることができます。 アイテムの総数を設定するには、ListView_SetItemCountExを呼び出します。

void ListView_SetItemCountEx(
  HWND hwndLV,
  int cItems,
  DWORD dwFlags
);

hwndLVは、リストビューのハンドルを指定します。 cItemsは、リストビューに設定するアイテムの数を指定します。 dwFlagsは、アイテムの数を設定した際のリストビューの動作を指定します。 通常は、LVSICF_NOINVALIDATEALLを指定します。

今回のプログラムは、仮想リストビューの使い方を示しています。

#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;
	static char *lpData = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		int                  i;
		int                  nCount;
		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 | LVS_OWNERDATA, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 100;
		column.pszText = TEXT("col1");
		ListView_InsertColumn(hwndListView, 0, &column);
		
		nCount = 58;
		lpData = (char *)HeapAlloc(GetProcessHeap(), 0, nCount * sizeof(char));
		for (i = 0; i < nCount; i++)
			lpData[i] = 'A' + (char)i;

		ListView_SetItemCountEx(hwndListView, nCount, LVSICF_NOINVALIDATEALL);
		
		return 0;
	}
	
	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == LVN_GETDISPINFO) {
			TCHAR        szBuf[256];
			NMLVDISPINFO *lpDispInfo = (NMLVDISPINFO *)lParam;

			if (lpDispInfo->item.mask & LVIF_TEXT) {
				wsprintf(szBuf, TEXT("%c"), lpData[lpDispInfo->item.iItem]);
				lstrcpy(lpDispInfo->item.pszText, szBuf);
			}
			
		}
		return 0;
	
	case WM_SIZE:
		MoveWindow(hwndListView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		HeapFree(GetProcessHeap(), 0, lpData);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

リストビューを仮想リストビューにするには、ウインドウスタイルにLVS_OWNERDATAを指定します。 また、ListView_SetItemCountExでアイテムの総数を設定しておきます。 今回は、ANSIコード表のAからzまでの文字を表示するということで、総数を58に設定しています。 仮想リストビューでは、アイテムを追加するのではなく、 アイテムのためのデータを用意しておくことになりますから、 HeapAllocでメモリを確保し、各文字を設定しておきます。

WM_NOTIFYのLVN_GETDISPINFOは、リストビューの中で見えているアイテムの数だけ送られます。 つまり、スクロールしなければ見えないアイテムなどは考慮されませんから、 アイテムの総数がどれだけ多くても速度に変化はありません。 lParamには、NMLVDISPINFO構造体のアドレスが格納されており、 item.maskにLVIF_TEXTが含まれている場合は、 item.pszTextにアイテムのテキストを設定すべきことを意味しています。 アイテムに関連するテキストは、アイテムのインデックスであるitem.iItemをlpDataに指定すれば分かります。 ちなみに、リストビューの中で見えているアイテムの数はListView_GetCountPerPageで、 見えているアイテムの中で最初のアイテムのインデックスはListView_GetTopIndexで取得することができます。 見えていないアイテムを表示したい場合は、ListView_EnsureVisibleを呼び出します。


戻る