概要

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

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
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マクロを呼び出しソートを実行します。

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を返します。

リストビューへのその他のメッセージ

リストビューのすべてのアイテムを消去する

SendMessage(リストビューのウィンドウハンドル, LVM_DELETEALLITEMS, 0, 0);
ListView_DeleteAllItems(リストビューのウィンドウハンドル);

リストビューの列を削除する

リストビューの指定する列のヘッダーとアイテムをすべて削除します。
ListView_DeleteColumn(リストビューのウィンドウハンドル, 列番号);

リストビューの列幅を最大文字数に合わせる

ListView_SetColumnWidth(リストビューのウィンドウハンドル, 列番号, LVSCW_AUTOSIZE);

プログラムソース

listview1.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("listview1");
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]);

//      比較関数
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("リストビュー1"),
                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);      //      リストビューのソート
                                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));
        }
}

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

ダウンロード listview1.zip(44.0kByte)

ZIPファイルに含まれるファイル
listview1.cpp
listview1.exe