設定画面にリストを置きたいとき、CListBox と CListCtrl のどちらを選ぶべきかで迷うことがあります。見た目は似ていますが、1 項目をどう表現したいかの考え方が違います。
この記事では、1 項目 1 文字列の選択肢と、1 行に複数列を持つ一覧を並べて、判断基準を確認します。後半では、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 の例は通常の単一選択リストです。見た目の比較だけでなく、下部に「なぜその UI で足りるか」も出す構成で確認しています。
まず実行画面の全体像を見る
最初に全体の比較画面を見ると、左は「設定候補を 1 つ選ぶ」だけ、右は「ファイル一覧を複数列で見る」用途だと分かります。

この画面で見てほしいのは、左は項目名だけで意味が完結しているのに対し、右は 1 行の中に状態、サイズ、更新日時が並んでいることです。ここが両者の違いの中心です。
- 左は「出力形式」「通知設定」のように、1 項目 = 1 文字列です。
- 右は「ファイル名 + 状態 + サイズ + 更新日時」という 1 行 = 複数フィールドです。
- 下部の判断メモに、
LBS_MULTICOLUMNの位置づけも出しています。
CListBox が向く場面
CListBox は、候補を並べて 1 つ選ばせる場面に向いています。選択中の項目を取りたいだけなら、コードも短く保てます。

| 向いている例 | 理由 |
|---|---|
| 出力形式の選択 | 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 に選択中の候補が入る
}
このように、1 項目 1 文字列で完結するなら、CListBox のほうが実装意図と API が素直に対応します。
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 を作る例
m_listBox.ModifyStyle(0, LBS_MULTICOLUMN);
m_listBox.SetColumnWidth(120);
ただし、この「複数列」は 同じ種類の文字列項目を横に流して表示するためのものです。Report View のように、1 行の中に「名前」「状態」「サイズ」を持つサブアイテム構造ではありません。
| 見た目 | LBS_MULTICOLUMN の list box | LVS_REPORT の list view |
|---|---|---|
| 横方向の並び | 同じ種類の項目を横へ流す | 1 行の中に別列を持つ |
| 列ヘッダー | なし | あり |
| 列ごとの取得 | 不可 | GetItemText(row, col) で可能 |
| 用途 | 候補の一覧表示の延長 | 表形式データ |
この違いがあるので、複数列の見た目だからといって CListBox でも同じにはならない点に注意してください。
データモデルの違いをコードで見る
両者の差は、内部データの持ち方にも現れます。CListBox は文字列の並び、CListCtrl は「行 + 列」の構造です。
CListBox は文字列単位で完結する
m_listBox.AddString(_T("通知設定"));
m_listBox.AddString(_T("出力形式"));
const int sel = m_listBox.GetCurSel();
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 |
|---|---|---|
| 選択変更通知 | LBN_SELCHANGE | LVN_ITEMCHANGED |
| 選択行取得 | GetCurSel | GetFirstSelectedItemPosition + GetNextSelectedItem |
| 選択文字列取得 | GetText | GetItemText(row, col) |
たとえば現在の比較ダイアログでも、左は ON_LBN_SELCHANGE、右は 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へ切り替わる
差し替えの規模自体は大きくありませんが、通知メッセージとデータ取得の考え方が変わるため、思ったより広く影響します。列が増えそうなら、早めに CListCtrl を選ぶほうが手戻りは少なくなります。
よくある誤解
誤解 1: CListBox は 1 列しか表示できない
厳密には違います。LBS_MULTICOLUMN を使えば横方向に複数列で並べられます。ただし、表形式のサブアイテム管理とは別物です。
誤解 2: 複数列に見えれば CListBox でも CListCtrl と同じ
見た目が横に並ぶだけでは同じになりません。列ヘッダー、セル単位の取得、行ごとの状態管理が欲しいなら CListCtrl が必要です。
誤解 3: とりあえず CListCtrl にしておけばよい
1 項目 = 1 文字列で十分な場面なら、CListCtrl は少し大げさです。列定義とセル設定が必要になり、コード量も増えます。
動かないときの確認表
| 症状 | まず見る場所 | 原因になりやすいこと | 対処 |
|---|---|---|---|
| 右側に列ヘッダーが出ない | リソースの 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 件をどう表現したいかに立ち返ると切り分けやすくなります。
まとめ
CListBoxは 1 項目 1 文字列の選択肢に向いています。CListCtrlは 1 行に複数列を持つ表形式の一覧に向いています。- 判断軸は「1 件を 1 文字列で持てるか、複数フィールドが要るか」です。
CListBoxにもLBS_MULTICOLUMNはありますが、CListCtrlの Report View の代わりではありません。- 列ヘッダー、サブアイテム、仮想リスト、大量データ対応が要るなら
CListCtrlを選びます。 - 後から列が増えそうなら、最初から
CListCtrlを選ぶほうが手戻りを減らせます。 - 通知メッセージも違うので、載せ替え時は
LBN_SELCHANGEとLVN_ITEMCHANGEDを見直します。
