リストボックス2

リストボックスの基本的な操作は前ページを参照してください。

リストボックスのスタイル

リストボックスには以下のスタイルが指定できます。

定数 説明
LBS_NOTIFY LBN_SELCHANGELBN_DBLCLKLBN_SELCANCEL通知コードを親ウィンドウに通知する。
LBS_SORT アイテムをアルファベット順に自動で並べ替える。
ただしLB_INSERTSTRINGで追加したアイテムの並べ替えは行わない。
LBS_NOREDRAW リストボックスへの変更を画面に反映しない。
反映させるにはWM_SETREDRAWメッセージを送信する。
LBS_MULTIPLESEL アイテムを複数選択可能にする。
アイテムをクリックする度に選択状態が切り替わる。
LBS_USETABSTOPS タブ文字を展開する。
LBS_NOINTEGRALHEIGHT リストボックスのサイズをCreateWindow関数の引数通りに作成する。
このスタイルを指定しない場合、高さが足りないために一部しか表示できないアイテムが発生しないようにシステムがリストボックスのサイズを調整する。
LBS_MULTICOLUMN 複数列のリストボックスを作成する。
リストは水平にスクロールし、WM_VSCROLLメッセージを無視する。
LBS_WANTKEYBOARDINPUT リストボックスがフォーカスを持っているとき、キー入力に対して親ウィンドウにWM_VKEYTOITEMメッセージを送信する。
LBS_EXTENDEDSEL ShiftキーまたはCtrlキーとマウスクリックの組み合わせ、またはマウスドラッグにより、複数項目の選択を可能にする。
LBS_DISABLENOSCROLL 項目数にかかわらずスクロールバーを常に表示する。
(自動で非表示にしない)
WS_VSCROLLまたはWS_HSCROLLスタイルと同時に使用する。
LBS_NODATA データを持てないリストボックスを作成する。
LBS_NOSEL 項目を選択できないリストボックスを作成する。
LBS_STANDARD LBS_NOTIFYLBS_SORTWS_VSCROLLWS_BORDERスタイルの組み合わせ
LBS_OWNERDRAWFIXED オーナードローリストボックスコントロール。
コントロール内の描画をプログラマ自身が行う。
LBS_OWNERDRAWVARIABLE オーナードローリストボックスコントロール。
コントロール内の描画をプログラマ自身が行う。
LBS_HASSTRINGS アイテムに文字列が含まれている。
オーナードローリストボックスコントロールで使用する。
それ以外ではこのフラグは既定でオン。

通知コード

リストボックスを操作するとWM_COMMANDメッセージが発行され、WPARAMの上位ワードには以下の通知コードが格納されます。

通知コード 説明
LBN_SELCHANGE アイテムの選択状態が変更されたとき
LBN_DBLCLK アイテムをダブルクリックしたとき
LBN_SELCANCEL アイテムの選択をキャンセルしたとき
LBN_SETFOCUS フォーカスを得たとき
LBN_KILLFOCUS フォーカスを失ったとき
LBN_ERRSPACE メモリ不足エラー

太字で示している通知コードは既定では通知されません。
これらを受け取るにはウィンドウスタイルにLBS_NOTIFYを指定します。


CreateWindowEx(
	WS_EX_CLIENTEDGE,
	L"LISTBOX", NULL,
	WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY,
	10, 10, 100, 100,
	hWnd, (HMENU)IDC_LIST1, hInst, NULL);

LBN_SELCHANGEはユーザーの操作時に発生します。
プログラムからSendMessage関数で選択状態を変更したときなどには発生しません。

複数のアイテムの同時選択

リストボックスは標準ではひとつのアイテムしか選択状態にすることができません。
複数同時に選択できるリストボックスを作成するにはLBS_MULTIPLESELスタイルを追加します。


CreateWindowEx(
	WS_EX_CLIENTEDGE,
	L"LISTBOX", NULL,
	WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_MULTIPLESEL,
	10, 10, 100, 100,
	hWnd, (HMENU)IDC_LIST1, hInst, NULL);

このスタイルで作成されたリストボックスはアイテムを選択するごとに選択状態と非選択状態が切り替わります。

もしくはLBS_EXTENDEDSELスタイルでも複数項目の選択が可能になります。
こちらはCtrlキーやShiftキーを押しながらマウスクリック、またはマウスドラッグで複数項目を選択します。
Windowsエクスプローラーに近い操作方法です。

なお、複数同時に選択状態にできるため、例えばLB_GETCURSELなどのメッセージでは正しく選択中のアイテム番号を取得することができません。
複数選択可能なスタイルではLB_GETSELLB_SETSELなどを使用します。

リストボックスのメッセージ

リストボックスは以下のメッセージを使用して操作します。

メッセージ WPARAM LPARAM 戻り値
LB_ADDSTRING 0 追加する文字列 追加された位置を示すインデックス
エラー発生時はLB_ERRまたはLB_ERRSPACE
(負数)
末尾にアイテムを追加する。
LB_INSERTSTRING インデックス 追加する文字列 追加された位置を示すインデックス
エラー発生時はLB_ERRまたはLB_ERRSPACE
インデックスを指定してアイテムを追加する。
インデックスに「-1」を指定すると末尾に追加される。
LBS_SORTスタイルを持つリストボックスでも並べ替えは行われない。
LB_DELETESTRING インデックス 0 残りのアイテム数
存在しないインデックスを指定した場合はLB_ERR
インデックスで指定したアイテムを削除する。
LB_RESETCONTENT 0 0 なし
全てのアイテムを削除する。
LB_GETCOUNT 0 0 アイテム数
エラーが発生した場合はLB_ERR
リストボックス内のアイテム数を取得する。
メッセージ WPARAM LPARAM 戻り値
LB_SETSEL TRUE:選択
FALSE:選択解除
インデックス
-1を指定するとすべてのアイテムに対して設定する
エラーが発生した場合はLB_ERR
インデックスで指定したアイテムの選択状態を設定する。
LB_SETCURSEL インデックス 0 WPARAMが-1の場合、またはエラーが発生した場合はLB_ERR
インデックスで指定したアイテムを選択する。
単一選択のリストボックスで有効。
LB_GETSEL インデックス 0 アイテムが選択状態ならば0以上を返す
非選択状態なら0を返す
エラーが発生した場合はLB_ERR
インデックスで指定したアイテムの選択状態を取得する。
LB_GETCURSEL 0 0 現在選択されているアイテムのインデックス
選択アイテムがない場合はLB_ERR
現在選択されているアイテムのインデックスを取得する。
単一選択のリストボックスで有効。
LB_SELITEMRANGE TRUE:選択
FALSE:選択解除
下位ワード:選択する最初のアイテムのインデックス
上位ワード:選択する最後のアイテムのインデックス
エラーが発生した場合はLB_ERR
範囲を指定してアイテムの選択状態を設定する。
LPARAMはMAKELPARAMマクロで作成する。
LB_SELITEMRANGEEX 選択する最初のアイテムのインデックス 選択する最後のアイテムのインデックス エラーが発生した場合はLB_ERR
範囲を指定してアイテムの選択状態を設定する。
WPARAMがLPARAMより小さいときは、アイテムが選択される。
WPARAMがLPARAMより大きいときは、アイテムが選択解除される。
LB_SETANCHORINDEX インデックス 0 成功した場合は0
失敗した場合はLB_ERR
アンカーとなるアイテムを設定する。
アンカーとは複数選択の起点となる項目のこと。
LB_GETANCHORINDEX 0 0 インデックス
アンカーとなるアイテムのインデックスを取得する。
LB_SETCARETINDEX インデックス TRUE:アイテムが一部でも表示されるようにスクロール
FALSE:アイテムが完全に表示されるようにスクロール
LB_OKAY(0)
エラーが発生した場合はLB_ERR

インデックスで指定したアイテムにフォーカスをセットし、アイテムが表示されるようにスクロールする。
(フォーカスを持つアイテムは周りが薄い点線で囲われている)

単一選択リストボックスでは、現在選択されているアイテムがない場合はインデックスのアイテムにフォーカスがセットされる。
現在選択されているアイテムがある場合は何もせず、LB_ERRを返す。

LB_GETCARETINDEX 0 0 インデックス
フォーカスを持つアイテムのインデックスを取得する。
単一選択リストボックスでは、現在選択されているアイテムのインデックスを取得する。
LB_SELECTSTRING 検索を開始するインデックス 検索する文字列 見つかったアイテムのインデックス
見つからなかった場合はLB_ERR

指定の文字列から始まるアイテムを検索し、選択状態にする。
複数ある場合は最初に見つかったものを対象とする。
検索文字列は大文字と小文字を区別しない。
見つからなかった場合は現在の選択状態を変更しない。

検索はWPARAMで指定したインデックスから開始され、最後のアイテムに達すると最初のアイテムに戻り、WPARAMで指定したインデックスまで検索が続行される。
-1を指定するとリストボックス全体を最初から検索する。
LBS_MULTIPLESELスタイルおよびLBS_EXTENDEDSELスタイルのリストボックスには使用できない。

LB_FINDSTRING 検索を開始するインデックス 検索する文字列 見つかったアイテムのインデックス
見つからなかった場合はLB_ERR

指定の文字列から始まるアイテムを検索する。
複数ある場合は最初に見つかったものを対象とする。
検索文字列は大文字と小文字を区別しない。

検索はWPARAMで指定したインデックスから開始され、最後のアイテムに達すると最初のアイテムに戻り、WPARAMで指定したインデックスまで検索が続行される。
-1を指定するとリストボックス全体を最初から検索する。

LB_FINDSTRINGEXACT 検索を開始するインデックス 検索する文字列 見つかったアイテムのインデックス
見つからなかった場合はLB_ERR

指定の文字列と一致するアイテムを検索する。
複数ある場合は最初に見つかったものを対象とする。
検索文字列は大文字と小文字を区別しない。

検索はWPARAMで指定したインデックスから開始され、最後のアイテムに達すると最初のアイテムに戻り、WPARAMで指定したインデックスまで検索が続行される。
-1を指定するとリストボックス全体を最初から検索する。

LB_GETSELCOUNT 0 0 選択中のアイテムの数
単一選択リストボックスで使用した場合はLB_ERR
選択中のアイテムの数を取得する。
単一選択リストボックスでは使用できない。
LB_GETSELITEMS 取得するアイテムの最大数 整数値を格納するバッファ 取得したアイテムの数
単一選択リストボックスで使用した場合はLB_ERR
選択中のアイテムのインデックスを全て取得し、バッファ(配列)に格納する。
単一選択リストボックスでは使用できない。
LB_GETTOPINDEX 0 一番上に表示中のアイテムのインデックス
現在表示されている中で一番上にあるアイテムのインデックスを取得する。
LB_SETTOPINDEX インデックス 0 エラー発生時はLB_ERR
インデックスで指定したアイテムができるだけ上部に表示されるようにスクロールする
メッセージ WPARAM LPARAM 戻り値
LB_GETTEXT インデックス 文字列を格納するバッファ 取得した文字列の長さ
(NULL文字除く)
存在しないインデックスを指定した場合はLB_ERR
インデックスで指定したアイテムの文字列を取得する。
LB_GETTEXTLEN インデックス 0 文字列の長さ
(NULL文字除く)
存在しないインデックスを指定した場合はLB_ERR
インデックスで指定したアイテムの文字列の長さを取得する。
LB_GETTEXTメッセージで文字列を取得する前に必要なバッファを確保するのに使用すると良い。
LB_SETTABSTOPS LPARAMの配列の要素数 タブ文字幅の配列 全てのタブ幅が設定されている場合はTRUE
タブ文字の幅を設定する。
WPARAMが0の場合はデフォルトの幅(2)に設定され、LPARAMは無視される。
WPARAMが1の場合はすべてのタブ幅にLPARAMの先頭の要素の値が使用される。
WPARAMが2以上の場合はタブ幅にLPARAMの配列の値が順番に適用される。
LB_SETITEMHEIGHT 0 高さ(ピクセル単位) 失敗した場合はLB_ERR

すべての項目の高さを設定する。

LBS_OWNERDRAWVARIABLEスタイルを持つリストボックスの場合、WPARAMでインデックス指定した項目の高さを設定する。

LB_GETITEMHEIGHT 0 0 項目の高さ(ピクセル単位)
エラーが発生した場合はLB_ERR

すべての項目の高さを取得する。

LBS_OWNERDRAWVARIABLEスタイルを持つリストボックスの場合、WPARAMでインデックス指定した項目の高さを取得する。

LB_SETCOLUMNWIDTH 全ての列の幅(ピクセル数) 0 なし
複数列リストボックスのすべての列の幅をピクセル単位で設定する。
LB_SETHORIZONTALEXTENT 水平スクロール可能な幅
(ピクセル単位)
0 なし

水平スクロール可能な幅をピクセル単位で設定する。
リストボックスの幅がこの設定値よりも小さい場合に水平スクロールバーが表示される。
WS_HSCROLLスタイルの指定が必要。

リストボックスはこのメッセージを送信しない限り、水平スクロールバーを表示したりスクロール幅を更新したりしない。

LB_GETHORIZONTALEXTENT 0 0 水平スクロール可能なピクセル数
水平スクロール可能な幅をピクセル単位で取得する。
WS_HSCROLLスタイルの指定が必要。
メッセージ WPARAM LPARAM 戻り値
LB_SETITEMDATA インデックス アイテムに関連付けられる値 エラー発生時はLB_ERR
インデックスで指定したアイテムに特定の値を関連付ける。
この値はLB_GETITEMDATAメッセージで取得できる。
LB_GETITEMDATA インデックス 0 アイテムに関連付けられている値
インデックスで指定したアイテムに関連付けられている値を取得する。
LB_DIR 検索対象を指定する定数 検索するパス
(絶対パス/相対パス)
最後に追加されたアイテムのインデックス。
エラー発生時はLB_ERRまたはLB_ERRSPACE

パス文字列および指定の属性に一致するファイル名やディレクトリ名を取得しリストボックスに追加する。
パスにはワイルドカードが使用可能。

WPARAMは以下の定数の組み合わせて属性を指定する。
DDL_ARCHIVE:アーカイブファイル
DDL_DIRECTORYサブディレクトリ
DDL_DRIVES:ドライブ
DDL_EXCLUSIVE:指定された属性を持つファイルのみを含むようにする
DDL_HIDDEN:隠しファイル
DDL_READONLY:読み取り専用ファイル
DDL_READWRITE:特別な属性を持たない読み書き可能なファイル(デフォルト)
DDL_SYSTEM:システムファイル

アイテム追加の高速化

リストボックスにアイテムを追加すると、リストボックスの再描画が発生します。
例えばリストボックスに一度に100個のアイテムを追加する処理をそのまま書くと、100回の再描画が行われることになります。
再描画はやや重たい処理なので、パソコンの処理速度によっては一時的に固まってしまう可能性があります。

大量のアイテムを追加する場合、追加処理の前にリストボックスにWM_SETREDRAWメッセージを送信します。
このメッセージのWPARAMFALSEを指定することで、ウィンドウの描画処理を一時的に停止することができます。
コントロールの画面更新が停止するので高速にアイテムを追加できるようになります。
アイテム追加処理が終わったら、同じメッセージをWPARAMにTRUEを指定して送信します。
(LPARAMは使用しません)


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

#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100
#define IDC_BUTTON2 101
#define IDC_LIST1 1000
#define IDC_LIST2 1001

#define ITEM_LENGTH 1000 //追加するアイテムの数
#define ITEMTEXT_BUFFERSIZE 8
#define MSGTEXT_BUFFERSIZE 64

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hList1, hList2;
	static WCHAR items[ITEM_LENGTH][ITEMTEXT_BUFFERSIZE];
	static WCHAR txt[MSGTEXT_BUFFERSIZE];
		
	static DWORD time1, time2;
	DWORD startTime;

	HDC hdc;
	PAINTSTRUCT ps;
	RECT rt;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		hList1 = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"LISTBOX", NULL,
			WS_CHILD | WS_VISIBLE | WS_VSCROLL,
			0, 0, 150, 200,
			hWnd, (HMENU)IDC_LIST1, hInst, NULL);
		hList2 = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			L"LISTBOX", NULL,
			WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOREDRAW,
			150, 0, 150, 200,
			hWnd, (HMENU)IDC_LIST1, hInst, NULL);

		CreateWindow(
			L"BUTTON", L"Normal",
			WS_CHILD | WS_VISIBLE,
			0, 200, 150, 25,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		CreateWindow(
			L"BUTTON", L"NoReDraw",
			WS_CHILD | WS_VISIBLE,
			150, 200, 150, 25,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);

		//表示用アイテムをITEM_LENGTH個作っておく
		for (int i = 0; i < ITEM_LENGTH; i++) {
			StringCchPrintf(items[i], ITEMTEXT_BUFFERSIZE, L"%d", i + 1);
		}
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_BUTTON1: //通常の追加
			startTime = timeGetTime();

			SendMessage(hList1, LB_RESETCONTENT, 0, 0);
			for (int i = 0; i < ITEM_LENGTH; i++) {
				SendMessage(hList1, LB_ADDSTRING, 0, items[i]);
			}

			time1 = timeGetTime() - startTime;
			InvalidateRect(hWnd, NULL, TRUE);
			break;

		case IDC_BUTTON2: //画面更新を停止して追加
			startTime = timeGetTime();
			SendMessage(hList2, WM_SETREDRAW, FALSE, 0); //更新停止

			SendMessage(hList2, LB_RESETCONTENT, 0, 0);
			for (int i = 0; i < ITEM_LENGTH; i++) {
				SendMessage(hList2, LB_ADDSTRING, 0, items[i]);
			}

			SendMessage(hList2, WM_SETREDRAW, TRUE, 0); //更新再開
			time2 = timeGetTime() - startTime;
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		}
		break;

	case WM_PAINT:
		GetClientRect(hWnd, &rt);
		rt.top = 230;
		StringCchPrintf(txt, MSGTEXT_BUFFERSIZE,
			L"更新停止なし: %dミリ秒\n更新停止あり: %dミリ秒\n",
			time1, time2);

		hdc = BeginPaint(hWnd, &ps);
		DrawText(hdc, txt, -1, &rt, DT_WORDBREAK);
		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

画面の左側には通常の方法でアイテムを追加するリストボックスを、右側にはWM_SETREDRAWメッセージを利用してアイテムを追加するリストボックスを配置しています。
ボタンを押すとアイテムの追加処理が開始され、処理時間をtimeGetTime関数で計測します。
リストボックスに高速にアイテムを追加するサンプル

なお、右側のリストボックスはLBS_NOREDRAWスタイル(更新の停止)を使用して作成していますが、これはリストボックスの初期状態を設定するだけです。
このスタイルを持たないリストボックスでもWM_SETREDRAWメッセージを送信することで更新を停止することが可能です。

WM_SETREDRAWメッセージはリストボックス以外にも使用可能です。
例えばエディットコントロールにループなどで大量のテキストを追記する場合にも利用できます。