【MFC】CListBox vs CListCtrl: どっちを使うべきか?

【MFC】CListBox vs CListCtrl: どっちを使うべきか?

設定画面にリストを置くとき、CListBoxとCListCtrlのどちらを使うかで迷うことがあります。CListBoxは1項目を1つの文字列として持ち、CListCtrlは1行にファイル名・状態・サイズのような複数の値を列に分けて持ちます。

1項目が文字列1つで足りるならCListBox、1行に複数の値を並べて列ごとに読み書きしたいならCListCtrlを使います。CListBoxにもLBS_MULTICOLUMNで複数列に並べるスタイルがありますが、列ごとに別の値を持つ表にはなりません。選択が変わったときに届く通知メッセージも違うので、途中で載せ替えるときはメッセージマップも書き換えます。


目次

最初に見る判断表

やりたいこと向くコントロール主なAPI判断のポイント
候補を1つ選ばせたいCListBoxAddString、GetCurSel、GetText1項目 = 1文字列で足りるなら素直
1行に状態、サイズ、更新日などを並べたいCListCtrlInsertColumn、InsertItem、SetItemTextサブアイテムとヘッダーが必要
列ヘッダー付きの表を作りたいCListCtrlLVS_REPORTCListBoxのmulticolumnでは代替しにくい
大量データを仮想リストで表示したいCListCtrlLVS_OWNERDATA、SetItemCountEx行数が増えるならlist view系が強い
横に流れる複数列の文字列リストを作りたいCListBoxも可LBS_MULTICOLUMN、SetColumnWidthヘッダーやセル単位管理は持たない

迷ったときは、1件を文字列1つで表せるか、1行に複数の値を持たせたいかで選びます。


検証環境

OSWindows 11
IDEVisual Studio 2026
ワークロードC++ によるデスクトップ開発
MFC / ATL有効
ビルド構成x64 / Debug
比較対象CListBoxとCListCtrlを並べたダイアログ

CListCtrlの例はReport View、CListBoxの例は通常の単一選択リストです。


まず実行画面の全体像を見る

最初に全体の比較画面を見ると、左は「設定候補を1つ選ぶ」だけ、右は「ファイル一覧を複数列で見る」用途だと分かります。

CListBoxとCListCtrlを左右に並べ、用途の違いを比較した実行画面

左は項目名だけで意味が通じます。右は1行の中に状態、サイズ、更新日時が並んでいて、ファイル名だけでは足りません。

  • 左は「出力形式」「通知設定」のように、1項目 = 1文字列です。
  • 右は「ファイル名 + 状態 + サイズ + 更新日時」という1行 = 複数フィールドです。
  • 下部のメモのとおり、CListBoxをLBS_MULTICOLUMNで複数列に並べても、Report Viewのように1行に列ごとの値を持つことはできません。

CListBoxが向く場面

CListBoxは、候補を並べて1つ選ばせる場面に向いています。選択中の項目は、GetCurSelとGetTextの2つで取り出せます。

CListBoxで1項目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 に選択中の候補が入る
}

候補の追加はAddString、選ばれた位置はGetCurSel、その文字列はGetTextと、やりたいことと関数が1対1で対応します。


CListCtrlが向く場面

CListCtrlは、1行の中に複数の情報を持たせたいときに使うコントロールです。Report Viewにすると、列ヘッダー付きの表として扱えます。

CListCtrl 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 boxLVS_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へ載せ替えると、選択が変わったときに届く通知メッセージと、選択中の行を取り出す関数が変わります。

観点CListBoxCListCtrl
選択変更通知LBN_SELCHANGELVN_ITEMCHANGED
選択行取得GetCurSelGetFirstSelectedItemPosition + GetNextSelectedItem
選択文字列取得GetTextGetItemText(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を使うと、列定義とセル設定が必要になり、コード量も増えます。


動かないときの確認表

症状まず見る場所原因になりやすいこと対処
右側に列ヘッダーが出ないリソースのstyleLVS_REPORTが付いていないReport Viewのstyleを付ける
選択変更で説明が更新されないメッセージマップLBN_SELCHANGE / LVN_ITEMCHANGEDの接続漏れ通知の種類をコントロールに合わせる
右側の行全体が選択されない拡張styleLVS_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:スクリーン座標とクライアント座標」で確かめられます。

目次