概要

リストビューのサンプルです。
江戸幕府の歴代将軍の緒元を表示します。
リストビューのヘッダー部を左クリックするとその列の文字列の大小関係によってソートされます。
ソートされると対象のヘッダー部に三角マークが表示されます。
年齢の列をクリックした場合は、文字列を数値に変換した後、数値の大小関係によってソートされます。
名前の列のヘッダーをクリックまたはカーソルキーで移動させて選択アイテムを変更した場合、選択されたアイテムの文字列をウィンドウタイトルに表示します。
リストビューは作成したウィンドウの子ウィンドウです。

ヘッダーの三角マーク(グリル)は、Windows XP以上のコモンコントロールComCtl32.dll (Version 6)以上でサポートしています。

Visual C++ 2008/VC2010でコンパイルする場合

マニフェストファイルでComCtl32.dllのバージョンを指定しないとVersion 5のdllが呼び出され、三角マークは表示されません。ダウンロードファイルにはマニフェストファイルは含まれています。64bit用にコンパイルする場合は、YourApp64.manifestファイルを使用してください。 ComCtl32.dll のVersion 6を使用する方法は、Windows XP ビジュアルスタイルの使用等でインターネット上で検索すれば、ヒットします。http://msdn.microsoft.com/ja-jp/library/ms997646.aspx#xptheming_topic2 概略は上記のリンク先の次の XML 形式を含むソース ツリーの次の背景が着色の部分をコピーし(コードのコピー)、メモ帳等を開いて貼り付けを行います。(貼り付けたテキストの最初にコードのコピー という行があったらこの行は削除します。最後の行には改ページを忘れずに) ) それをYourApp.manifest というファイル名で保存します。(保存形式はUTF-8、拡張子のtxtはつけないでください。)Visual C++メニューのプロジェクトのプロパティを選び、構成のプロパティ、マニフェストツールを選び入力と出力の中の追加のマニフェストファイルのところに先ほどのファイル名を入力します。
64bitの場合は、マニフェストファイルのうちX86と記述されているところをamd64に書き換えれば動作します。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2010 Express 32
Visual C++ 2013 Express 32/64bit

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

_tWinMain関数

Windowsから最初に_tWinMain関数が呼び出されます。 ウィンドウを作成する場合は、RegisterClass APIによりウィンドウクラスを定義してからCreateWindow APIを呼び出しウィンドウを作成します。 Windowsは入力等のイベントが発生するとアプリケーションにメッセージを送付します。 メッセージはキューに保管されます。アプリケーションはメッセージを取り出し、該当ウィンドウにメッセージを配信します。 メッセージの取り出しから配信までループで処理を行いウィンドウから終了メッセージが届くと、ループを抜けるように記述します。 これらの一連の処理は、通常にCreateWindow APIの後に記述しこれをメッセージループと呼んでいます。 詳細は以下を参照してください。
メッセージループ

WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)ウィンドウプロシージャー

RegisterClass APIにより登録することによりWindowsから呼び出されます。
第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。
自分で処理しないメッセージはDefWindowProc APIに渡せばWindowsが標準的な処理を行ってくれます。

case WM_CREATE:

ウィンドウの初期化時に呼び出されます。
CreateWindowEx APIでこのウィンドウの子ウィンドウであるリストビューを作成します。
リストビューの位置、大きさは0で作成し、後でウィンドウサイズを修正します。
リストビューのハンドルは後でソートの比較関数で使用するのでグローバル変数に保存しておきます。
ListView_InsertColumnによりヘッダー(列の見出し)を作成します。
年齢については右詰で表示させます。
set_Lvs関数によりリストビューのアイテムを追加します。

case WM_SIZE:

ウィンドウのサイズが変更されたときに呼び出されます。
MoveWindows APIを呼び出しリストビューのサイズを変更します。

case WM_DESTROY:

ウィンドウが閉じるときに呼び出されます。
PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

case WM_NOTIFY:

子ウィンドウであるコントロール等からのメッセージが届きます。
メッセージの内容を示す構造体へのポインタがlParamに格納されているので、lParamをキャストして構造体へのポインタに変換します。
コントロールの種類により構造体はことなりますが、構造体の先頭はNMHDRという共通の構造体です。
一般的にコントロールに関係なく以下のメッセージが使用できます。
NM_CLICK	左クリック
NM_DBLCLK	左ダブルクリック
NM_RCLICK	右クリック
NM_RDBLCLK	右ダブルクリック
NM_RETURN	Enterキーが押される
NM_SETFOCUS	フォーカス制御
NM_KILLFOCUS	入力フォーカスを失った
NM_OUTOFMEMORY	メモリ不足
ここでは、リストビューのアイテムの状態が変更されたときに発生するLVN_ITEMCHANGEDとヘッダーをクリックしたときに発生するLVN_COLUMNCLICKを処理します。
まずwParamにコントロールのID番号が格納されているのでリストビューと同一であれば処理を進めます。
lParamをLV_DISPFINFO *にキャストしlvinfoに代入します。
後で共通に使用するNM_LISTVIEW *もlParamをキャストしてpNMLVに代入しておきます。
lvinfo->hdr.codeがメッセージの種類をあらわしますので、switchステートメントで処理を割り振ります。
case LVN_ITEMCHANGED:
リストビューのアイテムの状態が変更されたときに呼び出されます。 pNMLV->iItemはアイテム番号をしめしています。-1の場合、アイテム番号が無効です。
pNMLV->uNewStateをチェックしLVIS_SELECTEDまたはLVIS_FOCUSEDのいずれかであることを確認します。
pNMLV->uNewStateには以下のメッセージがあります。
LVIS_FOCUSED	アイテムがフォーカスを持っている(有効アイテムは1個のみ)
LVIS_SELECTED	アイテムが選択されている(複数のアイテム可)
LVIS_CUT	アイテムはカット・アンド・ペーストの対象としてマーク
LVIS_DROPHILITED	ドラッグ・アンド・ドロップのターゲットとしてハイライト表示
LVITEM構造体のメンバに必要な値をセットします。
maskメンバにLVIF_TEXTとLVIF_PARAMをセットします。LVIF_TEXTをアイテムの文字列を取得、LVIF_PARAMはアイテムのlParamを取得することを意味します。lParamはソートされても変わらないのでソート前のアイテム番号を再現することができます。
iItemメンバにアイテム番号、iSubItemメンバには細部アイテム番号、pszTextメンバにはアイテム文字列を格納するバッファへのポインタ、cchTextMaxにはバッファの文字列数を設定し、ListView_GetItemマクロを呼び出します。
取得されたアイテムのlParamを将軍が何代目(アイテムは0からナンバリングされているので1を足す)、アイテム文字列を将軍の名前としてSetWindowText APIによりウィンドウタイトルに表示します。
case LVN_COLUMNCLICK:
リストビューのヘッダー(列の見出し)がクリックされたときに呼び出されます。
pNMLV->iSubItemにサブアイテム番号(列番号)が格納されています。
sortsubno配列に今までのソート方向(昇順または降順)が保存されているので、反転させサブアイテム番号を指定してListView_SortItemマクロを呼び出しソートを実行します。
ソート実行後、ListView_GetHeadeマクロによりヘッダーのハンドルを取得します。
Header_GetItemCountマクロにより列数を取得します。
全列に対して、list_view_header_sort_mark関数を実行し、ソートの基準となった列は三角マークの表示、それ以外は、三角マークを非表示にします。

set_lvs関数

ListView_InsertItemマクロにより0列目のアイテムを設定ます。
ソートを行うためアイテムにlParamを設定します。
し、ListView_SetItemマクロにより1列目以降のサブアイテムを設定します。

int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3)関数

ListView_SortItemマクロを呼び出すと呼び出されるアイテムの比較結果を返す関数です。
引数lp1,lp2が比較対象のアイテムのlParam番号、lp3が対象となるサブアイテム番号です。
ListView_FindItemマクロによりlParamからアイテム番号を取得します。
アイテムの文字列はListView_GetItemTextマクロにより取得します。
サブアイテム番号が2の場合、年齢の列です。例えば10と2を文字列のまま比較すると先頭の文字の文字コード順より2の方が大きいという結果になるので、文字列を_ttoi関数により整数値に変換して比較(減算)します。
それ以外の場合は、文字列として_tcscmp関数により比較します。
返す値は、1番目の引数が2番目の引数よりリストビューの上側に表示させるには負の値を、逆の場合は正の値を、同じ場合は0を返します。

list_view_header_sort_mark(HWND Header, int iSubItem, int sort)

指定したサブアイテム番号のヘッダの三角マークの制御を行います。
UPを第3引数に指定した場合▲ 、DOWNを指定した場合▼ それ以外を指定すると三角は表示されません。
Header_GetItemマクロによりヘッダの情報をHDITEM構造体に取得します。
HDITEM構造体のfmtメンバのうちHDF_SORTDOWNとHDF_SORTUPにかかわるビットをクリアします。
改めて、第3引数に従いfmtメンバを設定しHeader_SetItemマクロを呼び出します。

プログラムソース

listview2.cpp

//	リストビューサンプル(ソート機能付き ヘッダー三角表示サポート)
//	Visual C++ 2008/2013 32/64bit

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

#define NO_OF_SUBITEM 4
#define UP 1
#define DOWN 2
#define ID_LISTVIEW 100


TCHAR* szClassName=TEXT("listview2");
HINSTANCE hInst;
int sortsubno[NO_OF_SUBITEM];	//	各列のソート方向を保存
HWND hList;

//	ウィンドウプロシージャー
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//	リストビューの表示内容を設定
void set_lvs(HWND hList,TCHAR* str[][4]);

//	ヘッダーに三角マークを表示する関数
void list_view_header_sort_mark(HWND Header, int iSubItem, int sort);

//	比較関数
int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3);

TCHAR *str[][4]={	
{ TEXT("徳川家康") , TEXT("男") , TEXT("74") ,TEXT("たぬき、鯛の天ぷら")  },
{ TEXT("徳川秀忠") , TEXT("男") , TEXT("53") ,TEXT("遅参、律儀")  },
{ TEXT("徳川家光") , TEXT("男") , TEXT("47") ,TEXT("生まれながらの将軍、男色の気")  },
{ TEXT("徳川家綱") , TEXT("男") , TEXT("39") ,TEXT("左様せい様")  },
{ TEXT("徳川綱吉") , TEXT("男") , TEXT("63") ,TEXT("お犬さま")  },
{ TEXT("徳川家宣") , TEXT("男") , TEXT("50") ,TEXT("慈悲深い")  },
{ TEXT("徳川家継") , TEXT("男") , TEXT("7")  ,TEXT("側近政治")  },
{ TEXT("徳川吉宗") , TEXT("男") , TEXT("67") ,TEXT("暴れん坊将軍")  },
{ TEXT("徳川家重") , TEXT("男") , TEXT("50") ,TEXT("軟弱")  },
{ TEXT("徳川家治") , TEXT("男") , TEXT("49") ,TEXT("賄賂")  },
{ TEXT("徳川家斉") , TEXT("男") , TEXT("68") ,TEXT("奢侈,女好き")  },
{ TEXT("徳川家慶") , TEXT("男") , TEXT("60") ,TEXT("そうせい")  },
{ TEXT("徳川家定") , TEXT("男") , TEXT("34") ,TEXT("カステラ")  },
{ TEXT("徳川家茂") , TEXT("男") , TEXT("20") ,TEXT("甘党" ) },
{ TEXT("徳川慶喜") , TEXT("男") , TEXT("76") ,TEXT("カメラ小僧")  },
{ 0,0,0,0 }};

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,TCHAR* lpszCmdLine, int nCmdShow){
    HWND hWnd;
	MSG lpMsg;
	WNDCLASS myProg;
	hInst=hInstance;

	if (!hPreInst) {
		myProg.style=CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc=WndProc;
		myProg.cbClsExtra=0;
		myProg.cbWndExtra=0;
		myProg.hInstance=hInstance;
		myProg.hIcon=NULL;
		myProg.hCursor=LoadCursor(NULL, IDC_ARROW);
		myProg.hbrBackground=(HBRUSH__ *)GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName=NULL;
		myProg.lpszClassName=szClassName;
		if (!RegisterClass(&myProg))
			return FALSE;
	}
	hWnd = CreateWindow(szClassName,
		TEXT("リストビュー2"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	while (GetMessage(&lpMsg, NULL, 0, 0)) {
		TranslateMessage(&lpMsg);
		DispatchMessage(&lpMsg);
	}
	return int(lpMsg.wParam);
}

//	ウィンドウプロシージャー

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
	LV_COLUMN lvcol;
	LV_DISPINFO *lvinfo;
	NM_LISTVIEW *pNMLV;

	switch(msg){
	case WM_CREATE:
		hList = CreateWindowEx(0,
			WC_LISTVIEW, TEXT(""),
			WS_CHILD | WS_VISIBLE | LVS_REPORT,
			0, 0, 0, 0,
			hWnd,
			(HMENU)ID_LISTVIEW,
			hInst,
			NULL);
		lvcol.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
		lvcol.fmt = LVCFMT_LEFT;
		lvcol.cx = 64;
		lvcol.pszText = TEXT("名前");
		lvcol.iSubItem = 0;
		ListView_InsertColumn(hList, 0, &lvcol);

		lvcol.cx = 48;
		lvcol.pszText = TEXT("性別");
		lvcol.iSubItem = 1;
		ListView_InsertColumn(hList, 1, &lvcol);

		lvcol.cx = 48;
		lvcol.fmt = LVCFMT_RIGHT;
		lvcol.pszText = TEXT("年齢");
		lvcol.iSubItem = 2;
		ListView_InsertColumn(hList, 2, &lvcol);

		lvcol.cx = 128;
		lvcol.fmt = LVCFMT_LEFT;
		lvcol.pszText = TEXT("趣味");
		lvcol.iSubItem = 3;
		ListView_InsertColumn(hList, 3, &lvcol);
		set_lvs(hList,str);
		break;
	case WM_SIZE:
		MoveWindow(hList, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_NOTIFY:
		if ((int)wParam == ID_LISTVIEW) {
			lvinfo = (LV_DISPINFO *)lParam;
			pNMLV = (NM_LISTVIEW *)lParam;
			switch (lvinfo->hdr.code) {
				case LVN_ITEMCHANGED:	//	アイテムの状態が変更された場合
					if (pNMLV->iItem != -1){
						if (pNMLV->uNewState & (LVIS_SELECTED || LVIS_FOCUSED)){
							LVITEM item;
							TCHAR temp[64];
							TCHAR buf[64];
							item.mask = LVIF_TEXT | LVIF_PARAM;	//	アイテム文字列とlParamの取得
							item.iItem = pNMLV->iItem;	//	選択されているアイテム番号
							item.iSubItem = 0;
							item.pszText = temp;
							item.cchTextMax = sizeof(temp) / sizeof(TCHAR);
							ListView_GetItem(hList, &item);
							_stprintf_s(buf, sizeof(buf) / sizeof(TCHAR), _TEXT("%d代目将軍 %s"), item.lParam + 1, temp);
							SetWindowText(hWnd, buf);
						}
					}
					break;
				case LVN_COLUMNCLICK:{	//	リストビューのヘッダーをクリックしたときに呼び出される
					if (sortsubno[pNMLV->iSubItem] == UP)
						sortsubno[pNMLV->iSubItem] = DOWN;
					else
						sortsubno[pNMLV->iSubItem] = UP;
					ListView_SortItems(hList, MyCompProc, pNMLV->iSubItem);	//	リストビューのソート
					HWND Header = ListView_GetHeader(hList);
					int cmax = Header_GetItemCount(Header);	//	列数の取得
					for (int n = 0; n < cmax; n++){
						if (pNMLV->iSubItem==n)
							list_view_header_sort_mark(Header, n, sortsubno[n]);	//	三角マーク表示
						else
							list_view_header_sort_mark(Header, n, NULL);	//	三角マーク非表示
					}
					break;
				}
			}
		}
		break;
	default:
		return(DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0L);
}

//	リストビューの表示内容を設定

void set_lvs(HWND hList,TCHAR* str[][4]){
	LV_ITEM item;
	for(int i=0;str[i][0];i++){
		item.mask = LVIF_TEXT  | LVIF_PARAM;
		item.pszText = str[i][0];
		item.iItem = i;
		item.iSubItem = 0;
		item.lParam = i;

		ListView_InsertItem(hList, &item);

		item.mask = LVIF_TEXT;
		item.pszText = str[i][1];
		item.iItem = i;
		item.iSubItem = 1;
		ListView_SetItem(hList, &item);

		item.mask = LVIF_TEXT;
		item.pszText = str[i][2];
		item.iItem = i;
		item.iSubItem = 2;
		ListView_SetItem(hList, &item);

		item.mask = LVIF_TEXT;
		item.pszText = str[i][3];
		item.iItem = i;
		item.iSubItem = 3;
		ListView_SetItem(hList, &item);
	}
}

//	比較関数

int CALLBACK MyCompProc(LPARAM lp1, LPARAM lp2, LPARAM lp3){
	static LV_FINDINFO lvf;
	static int nItem1, nItem2;
	static TCHAR buf1[64], buf2[64];

	lvf.flags = LVFI_PARAM;
	lvf.lParam = lp1;
	nItem1 = ListView_FindItem(hList, -1, &lvf);

	lvf.lParam = lp2;
	nItem2 = ListView_FindItem(hList, -1, &lvf);

	ListView_GetItemText(hList, nItem1, (int)lp3, buf1, sizeof(buf1));
	ListView_GetItemText(hList, nItem2, (int)lp3, buf2, sizeof(buf2));
	if(int(lp3)==2){
		int i1=_ttoi(buf1);
		int i2=_ttoi(buf2);
		if(sortsubno[(int)lp3] == UP)
			return i1-i2;
		else
			return i2-i1;
	}else{
		if(sortsubno[(int)lp3] == UP)
			return(_tcscmp(buf1,buf2));
		else
			return(_tcscmp(buf2,buf1));
	}
}

//	ヘッダーに三角マークを表示する関数

void list_view_header_sort_mark(HWND Header, int iSubItem, int sort){
	HDITEM headerItem;
	headerItem.mask = HDI_FORMAT;
	Header_GetItem(Header, iSubItem, &headerItem);
	headerItem.mask = HDI_FORMAT;
	headerItem.fmt &= ~HDF_SORTDOWN & ~HDF_SORTUP;
	if (sort==DOWN || sort==UP)
		headerItem.fmt |= (sort == DOWN ? HDF_SORTDOWN : HDF_SORTUP) | HDF_STRING;
	else
		headerItem.fmt |= HDF_STRING;
	Header_SetItem(Header, iSubItem, &headerItem);
}

ソースファイルと実行ファイルのダウンロード

ダウンロード listview2.zip(45.6kByte)

ZIPファイルに含まれるファイル
listview2.cpp
listview2.exe
YourApp.manifest
YourApp64.manifest