設定画面にリストを置くとき、CListBoxとCListCtrlのどちらを使うかで迷うことがあります。CListBoxは1項目を1つの文字列として持ち、CListCtrlは1行にファイル名・状態・サイズのような複数の値を列に分けて持ちます。
1項目が文字列1つで足りるならCListBox、1行に複数の値を並べて列ごとに読み書きしたいならCListCtrlを使います。CListBoxにもLBS_MULTICOLUMNで複数列に並べるスタイルがありますが、列ごとに別の値を持つ表にはなりません。選択が変わったときに届く通知メッセージも違うので、途中で載せ替えるときはメッセージマップも書き換えます。
最初に見る判断表
| やりたいこと | 向くコントロール | 主なAPI | 判断のポイント |
|---|---|---|---|
| 候補を1つ選ばせたい | CListBox | AddString、GetCurSel、GetText | 1項目 = 1文字列で足りるなら素直 |
| 1行に状態、サイズ、更新日などを並べたい | CListCtrl | InsertColumn、InsertItem、SetItemText | サブアイテムとヘッダーが必要 |
| 列ヘッダー付きの表を作りたい | CListCtrl | LVS_REPORT | CListBoxのmulticolumnでは代替しにくい |
| 大量データを仮想リストで表示したい | CListCtrl | LVS_OWNERDATA、SetItemCountEx | 行数が増えるならlist view系が強い |
| 横に流れる複数列の文字列リストを作りたい | CListBoxも可 | LBS_MULTICOLUMN、SetColumnWidth | ヘッダーやセル単位管理は持たない |
迷ったときは、1件を文字列1つで表せるか、1行に複数の値を持たせたいかで選びます。
検証環境
| OS | Windows 11 |
| IDE | Visual Studio 2026 |
| ワークロード | C++ によるデスクトップ開発 |
| MFC / ATL | 有効 |
| ビルド構成 | x64 / Debug |
| 比較対象 | CListBoxとCListCtrlを並べたダイアログ |
CListCtrlの例はReport View、CListBoxの例は通常の単一選択リストです。
まず実行画面の全体像を見る
最初に全体の比較画面を見ると、左は「設定候補を1つ選ぶ」だけ、右は「ファイル一覧を複数列で見る」用途だと分かります。

左は項目名だけで意味が通じます。右は1行の中に状態、サイズ、更新日時が並んでいて、ファイル名だけでは足りません。
- 左は「出力形式」「通知設定」のように、1項目 = 1文字列です。
- 右は「ファイル名 + 状態 + サイズ + 更新日時」という1行 = 複数フィールドです。
- 下部のメモのとおり、CListBoxをLBS_MULTICOLUMNで複数列に並べても、Report Viewのように1行に列ごとの値を持つことはできません。
CListBoxが向く場面
CListBoxは、候補を並べて1つ選ばせる場面に向いています。選択中の項目は、GetCurSelとGetTextの2つで取り出せます。

| 向いている例 | 理由 |
|---|---|
| 出力形式の選択 | CSV / JSON / XMLのように候補を1つ返せば足りる |
| 通知先やログレベルの選択 | 選択後に必要なのは項目名か項目IDだけ |
| 単純なログ行の表示 | 1行を1文字列として増やせる |
たとえば設定画面の「出力形式」を選ぶだけなら、ヘッダーもサイズ列も更新日時も不要です。このときCListCtrlを使うと、列定義やセル設定のぶんだけ準備が増えます。
CListBox* pList = (CListBox*)GetDlgItem(IDC_LIST_BOX);
pList->AddString(_T("CSV"));
pList->AddString(_T("JSON"));
pList->AddString(_T("XML"));
const int sel = pList->GetCurSel();
if (sel != LB_ERR)
{
CString text;
pList->GetText(sel, text);
// text に選択中の候補が入る
}
候補の追加はAddString、選ばれた位置はGetCurSel、その文字列はGetTextと、やりたいことと関数が1対1で対応します。
CListCtrlが向く場面
CListCtrlは、1行の中に複数の情報を持たせたいときに使うコントロールです。Report Viewにすると、列ヘッダー付きの表として扱えます。

たとえばファイル一覧や検索結果では、ファイル名だけでは足りません。状態、サイズ、更新日時などを同じ行で見たくなります。これらをCListBoxの1つの文字列にまとめて入れると、列の位置がそろわず、あとで状態だけを取り出すときも文字列を分解し直すことになります。
| 向いている例 | 必要な要素 |
|---|---|
| ファイル一覧 | 名前、状態、サイズ、更新日時 |
| 検索結果 | タイトル、カテゴリ、ヒット件数 |
| タスク一覧 | 担当者、優先度、期限、進捗 |
CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST_CTRL);
pListCtrl->SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
pListCtrl->InsertColumn(0, _T("ファイル名"), LVCFMT_LEFT, 170);
pListCtrl->InsertColumn(1, _T("状態"), LVCFMT_LEFT, 74);
pListCtrl->InsertColumn(2, _T("サイズ"), LVCFMT_RIGHT, 64);
pListCtrl->InsertColumn(3, _T("更新日時"), LVCFMT_LEFT, 86);
const int row = pListCtrl->InsertItem(0, _T("見積書_2026Q2.xlsx"));
pListCtrl->SetItemText(row, 1, _T("レビュー待ち"));
pListCtrl->SetItemText(row, 2, _T("248 KB"));
pListCtrl->SetItemText(row, 3, _T("2026/06/19"));
列ヘッダーを付けて、列ごとに値を取得・更新したいなら、最初からCListCtrlを使います。
CListBoxにもLBS_MULTICOLUMNはある
CListBoxは1列しか表示できない、というわけではありません。Win32のlist boxにはLBS_MULTICOLUMNスタイルがあり、項目を横方向に複数列で並べられます。
// 複数列の list box を作る例
// LBS_MULTICOLUMN はリソースの Multi-column(または Create のスタイル)で作成時に指定する。
// 作成後に ModifyStyle で付けることはできない。
m_listBox.SetColumnWidth(120);
ただし、この「複数列」は 同じ種類の文字列項目を横に流して表示するためのものです。Report Viewのように、1行の中に「名前」「状態」「サイズ」を持つサブアイテム構造ではありません。
| 見た目 | LBS_MULTICOLUMNのlist box | LVS_REPORTのlist view |
|---|---|---|
| 横方向の並び | 同じ種類の項目を横へ流す | 1行の中に別列を持つ |
| 列ヘッダー | なし | あり |
| 列ごとの取得 | 不可 | GetItemText(row, col) で可能 |
| 用途 | 候補の一覧表示の延長 | 表形式データ |
そのため、LBS_MULTICOLUMNで複数列に並べても、列ごとに値を持つReport Viewの代わりにはなりません。
データモデルの違いをコードで見る
両者の差は、内部データの持ち方にも現れます。CListBoxは文字列の並び、CListCtrlは「行 + 列」の構造です。
CListBoxは文字列単位で完結する
m_listBox.AddString(_T("通知設定"));
m_listBox.AddString(_T("出力形式"));
const int sel = m_listBox.GetCurSel();
if (sel != LB_ERR)
{
CString label;
m_listBox.GetText(sel, label);
}
取得したいものは「何番目が選ばれているか」「その文字列は何か」です。項目ごとの説明IDを紐づけたいなら、SetItemData / GetItemDataを組み合わせます。
CListCtrlは列ごとに値を扱う
const int row = m_listCtrl.InsertItem(0, _T("会議メモ_設計変更.docx"));
m_listCtrl.SetItemText(row, 1, _T("共有済み"));
m_listCtrl.SetItemText(row, 2, _T("96 KB"));
m_listCtrl.SetItemText(row, 3, _T("2026/06/18"));
CString status = m_listCtrl.GetItemText(row, 1);
CString size = m_listCtrl.GetItemText(row, 2);
値が列ごとに分かれているので、列の値でのソート、表示の切り替え、列の追加、列単位の再描画も書けます。その代わり、初期化コードはCListBoxより長くなります。
通知メッセージと選択取得の違い
CListBoxからCListCtrlへ載せ替えると、選択が変わったときに届く通知メッセージと、選択中の行を取り出す関数が変わります。
| 観点 | CListBox | CListCtrl |
|---|---|---|
| 選択変更通知 | LBN_SELCHANGE | LVN_ITEMCHANGED |
| 選択行取得 | GetCurSel | GetFirstSelectedItemPosition + GetNextSelectedItem |
| 選択文字列取得 | GetText | GetItemText(row, col) |
メッセージマップでは、CListBoxはON_LBN_SELCHANGE、CListCtrlはON_NOTIFY(LVN_ITEMCHANGED, …) で受けます。
BEGIN_MESSAGE_MAP(CMyDlg, CDialogEx)
ON_LBN_SELCHANGE(IDC_LIST_BOX, &CMyDlg::OnLbnSelchangeListBox)
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_CTRL, &CMyDlg::OnLvnItemchangedListCtrl)
END_MESSAGE_MAP()
リソースだけ差し替えて通知ハンドラを古いまま残すと、画面は表示されても、選択を変えたときの処理が呼ばれません。
途中でCListBoxからCListCtrlへ載せ替えるときの差分
最初はCListBoxで足りると思っていても、後から「状態列も出したい」「更新日時で並べたい」となればCListCtrlへ載せ替えることがあります。
- リソース: LISTBOXをSysListView32に差し替える
- メンバー変数: CListBoxをCListCtrlに置き換える
- 初期化: AddString中心からInsertColumn + InsertItem + SetItemTextが中心になる
- 取得: GetTextからGetItemText(row, col) に置き換わる
- 通知: LBN_SELCHANGEからLVN_ITEMCHANGEDへ切り替わる
1つずつの変更は小さくても、選択の通知と値の取り出しを書いた箇所はすべて書き換えになります。あとで列が増えそうなら、最初からCListCtrlにしておくと書き換えずに済みます。
よくある誤解
誤解1: CListBoxは1列しか表示できない
LBS_MULTICOLUMNを使えば、項目を横方向に複数列で並べられます。ただし、1行の中に列ごとの値(サブアイテム)を持つ表にはなりません。
誤解2: 複数列に見えればCListBoxでもCListCtrlと同じ
見た目が横に並ぶだけでは同じになりません。列ヘッダー、セル単位の取得、行ごとの状態管理が欲しいならCListCtrlが必要です。
誤解3: とりあえずCListCtrlにしておけばよい
1項目 = 1文字列で十分な場面でCListCtrlのReport Viewを使うと、列定義とセル設定が必要になり、コード量も増えます。
動かないときの確認表
| 症状 | まず見る場所 | 原因になりやすいこと | 対処 |
|---|---|---|---|
| 右側に列ヘッダーが出ない | リソースのstyle | LVS_REPORTが付いていない | Report Viewのstyleを付ける |
| 選択変更で説明が更新されない | メッセージマップ | LBN_SELCHANGE / LVN_ITEMCHANGEDの接続漏れ | 通知の種類をコントロールに合わせる |
| 右側の行全体が選択されない | 拡張style | LVS_EX_FULLROWSELECTがない | SetExtendedStyleで追加する |
| 横並びのlist boxが期待どおりに見えない | styleと列幅 | LBS_MULTICOLUMNだけで列幅を設定していない | SetColumnWidthを使う |
| 大量データで重い | コントロール選定 | CListBoxで大量行を無理に扱っている | CListCtrlのLVS_OWNERDATAを検討する |
表の症状に当てはまらないときは、1件を文字列1つで表せるのか、1行に複数の値が要るのかをもう一度確かめ、使っているコントロールが合っているかを見直します。
まとめ
- CListBoxは1項目1文字列の選択肢に向いています。
- CListCtrlは1行に複数列を持つ表形式の一覧に向いています。
- 迷ったら、1件を文字列1つで持てるかどうかで選びます。
- CListBoxにもLBS_MULTICOLUMNはありますが、CListCtrlのReport Viewの代わりではありません。
- 列ヘッダー、サブアイテム、仮想リスト、大量データ対応が要るならCListCtrlを選びます。
- 後から列が増えそうなら、最初からCListCtrlにしておくと載せ替えずに済みます。
- 通知メッセージも違うので、載せ替え時はLBN_SELCHANGEとLVN_ITEMCHANGEDを見直します。
CListCtrlの列幅をコントロールの幅に合わせて決めるときは、枠やスクロールバーを含まないGetClientRectの幅を使います。GetWindowRectとの違いは「【MFC】GetWindowRect vs GetClientRect:スクリーン座標とクライアント座標」で確かめられます。
