EternalWindows
リストビュー / オーナードロー

リストビューにおけるオーナードローとは、 アイテムのテキストや背景色をアプリケーションが独自に描画することを意味します。 こうした描画を行うにはアイテムの範囲を格納したRECT構造体が必要になるため、 ListView_GetItemRectで取得することになります。

BOOL ListView_GetItemRect(
  HWND hwnd,
  int i,
  RECT *prc,
  int code
);

hwndは、リストビューのハンドルを指定します。 iは、範囲を取得するアイテムのインデックスを指定します。 prcは、RECT構造体のアドレスを指定します。 codeは、アイテムのどの部分の範囲を取得するかを指定します。 テキストを囲む範囲を取得する場合は、LVIR_LABELを指定します。

サブアイテムの範囲を取得するには、ListView_GetSubItemRectを呼び出します。

BOOL ListView_GetSubItemRect(
  HWND hwndLV,
  int iItem,
  int iSubItem,
  int code,
  LPRECT lpRect
);

hwndは、リストビューのハンドルを指定します。 iItemは、サブアイテムの親であるアイテムのインデックスを指定します。 iSubItemは、サブアイテムのインデックスを指定します。 codeは、アイテムのどの部分の範囲を取得するかを指定します。 テキストを囲む範囲を取得する場合は、LVIR_LABELを指定します。 prcは、RECT構造体のアドレスを指定します。

今回のプログラムは、リストビューのアイテムをオーナードローで描画します。

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

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

void DrawItem(LPDRAWITEMSTRUCT lpDraw);
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 | LVS_OWNERDRAWFIXED, 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);
		
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 100;
		column.pszText = TEXT("col2");
		ListView_InsertColumn(hwndListView, 1, &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    = 0;
		item.iSubItem = 1;
		item.pszText  = TEXT("subitem");
		ListView_SetItem(hwndListView, &item);

		return 0;
	}

	case WM_DRAWITEM:
		DrawItem((LPDRAWITEMSTRUCT)lParam);
		return TRUE;
	
	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);
}

void DrawItem(LPDRAWITEMSTRUCT lpDraw)
{
	HDC    hdc = lpDraw->hDC;
	HWND   hwndListView = lpDraw->hwndItem;
	HBRUSH hbr;
	RECT   rcItem, rcSubItem;
	TCHAR  szItemName[256], szSubItemName[256];
	
	SaveDC(hdc);
	
	ListView_GetItemRect(hwndListView, lpDraw->itemID, &rcItem, LVIR_LABEL);
	ListView_GetSubItemRect(hwndListView, lpDraw->itemID, 1, LVIR_LABEL, &rcSubItem);

	ListView_GetItemText(hwndListView, lpDraw->itemID, 0, szItemName, sizeof(szItemName) / sizeof(TCHAR));
	ListView_GetItemText(hwndListView, lpDraw->itemID, 1, szSubItemName, sizeof(szSubItemName) / sizeof(TCHAR));

	if (lpDraw->itemState & ODS_SELECTED)
		hbr = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
	else
		hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
	FillRect(hdc, &rcItem, hbr);
	
	SetBkMode(hdc, TRANSPARENT);

	SetTextColor(hdc, RGB(255, 0, 0));
	DrawText(hdc, szItemName, -1, &rcItem, DT_LEFT | DT_VCENTER | DT_SINGLELINE);

	SetTextColor(hdc, RGB(0, 255, 0));
	DrawText(hdc, szSubItemName, -1, &rcSubItem, DT_LEFT | DT_VCENTER | DT_SINGLELINE);

	if (lpDraw->itemState & ODS_FOCUS)
		DrawFocusRect(hdc, &rcItem);
	
	DeleteObject(hbr);

	RestoreDC(hdc, -1);
}

リストビューでオーナードローを行うには、ウインドウスタイルにLVS_OWNERDRAWFIXEDを指定します。 これにより、WM_DRAWITEMが送られることになるので、DrawItemという自作関数で描画を行うことになります。 まず、SaveDCで現在のデバイスコンテキストの属性を保存し、 描画終了時にRestoreDCで属性を戻せるようにしておきます。 次に、ListView_GetItemRectやListView_GetItemTextでアイテムの範囲やテキストを取得します。 このとき、アイテムのIDは、DRAWITEMSTRUCT構造体のitemIDから参照しています。 必要な情報が揃えば、実際に描画を開始していきます。 まず、アイテムの背景を塗りつぶすためのブラシCreateSolidBrushで作成し、 FillRectで塗りつぶしを行います。 このとき、アイテムが選択されているかによってブラシの色を変更している点が重要です。 次に、SetBkModeでテキストを透過モードに設定し、SetTextColorで色を調整してからDrawTextを呼び出します。 今回は、オーナードローの効果を確認するために、テキスト色は黒に設定していません。 アイテムにフォーカスが割り当てられている場合は、DrawFocusRectで点線の長方形を描画します。


戻る