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

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

設定画面にリストを置きたいとき、CListBoxCListCtrl のどちらを選ぶべきかで迷うことがあります。見た目は似ていますが、1 項目をどう表現したいかの考え方が違います。

この記事では、1 項目 1 文字列の選択肢と、1 行に複数列を持つ一覧を並べて、判断基準を確認します。後半では、LBS_MULTICOLUMN の位置づけ、通知メッセージ、途中で載せ替えるときの差分まで見ます。


目次

最初に見る判断表

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

迷ったら、1 件を 1 文字列で持てるか、それとも 1 行に複数フィールドを持たせたいか、という観点で分けると判断しやすくなります。


検証環境

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

CListCtrl の例は Report View、CListBox の例は通常の単一選択リストです。見た目の比較だけでなく、下部に「なぜその UI で足りるか」も出す構成で確認しています。


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

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

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

この画面で見てほしいのは、左は項目名だけで意味が完結しているのに対し、右は 1 行の中に状態、サイズ、更新日時が並んでいることです。ここが両者の違いの中心です。

  • 左は「出力形式」「通知設定」のように、1 項目 = 1 文字列です。
  • 右は「ファイル名 + 状態 + サイズ + 更新日時」という 1 行 = 複数フィールドです。
  • 下部の判断メモに、LBS_MULTICOLUMN の位置づけも出しています。

CListBox が向く場面

CListBox は、候補を並べて 1 つ選ばせる場面に向いています。選択中の項目を取りたいだけなら、コードも短く保てます。

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 に選択中の候補が入る
}

このように、1 項目 1 文字列で完結するなら、CListBox のほうが実装意図と API が素直に対応します。


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 を作る例
m_listBox.ModifyStyle(0, LBS_MULTICOLUMN);
m_listBox.SetColumnWidth(120);

ただし、この「複数列」は 同じ種類の文字列項目を横に流して表示するためのものです。Report View のように、1 行の中に「名前」「状態」「サイズ」を持つサブアイテム構造ではありません。

見た目LBS_MULTICOLUMN の list boxLVS_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 より長くなります。


通知メッセージと選択取得の違い

載せ替え時に見落としやすいのが通知メッセージです。選択変更を拾う方法も違います。

観点CListBoxCListCtrl
選択変更通知LBN_SELCHANGELVN_ITEMCHANGED
選択行取得GetCurSelGetFirstSelectedItemPosition + GetNextSelectedItem
選択文字列取得GetTextGetItemText(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 へ載せ替えることがあります。

  • リソース: LISTBOXSysListView32 に差し替える
  • メンバー変数: CListBoxCListCtrl に置き換える
  • 初期化: 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 は少し大げさです。列定義とセル設定が必要になり、コード量も増えます。


動かないときの確認表

症状まず見る場所原因になりやすいこと対処
右側に列ヘッダーが出ないリソースの styleLVS_REPORT が付いていないReport View の style を付ける
選択変更で説明が更新されないメッセージマップLBN_SELCHANGE / LVN_ITEMCHANGED の接続漏れ通知の種類をコントロールに合わせる
右側の行全体が選択されない拡張 styleLVS_EX_FULLROWSELECT がないSetExtendedStyle で追加する
横並びの list box が期待どおりに見えないstyle と列幅LBS_MULTICOLUMN だけで列幅を設定していないSetColumnWidth を使う
大量データで重いコントロール選定CListBox で大量行を無理に扱っているCListCtrlLVS_OWNERDATA を検討する

画面上の見た目だけでは分かりにくいときは、この 1 件をどう表現したいかに立ち返ると切り分けやすくなります。


まとめ

  • CListBox は 1 項目 1 文字列の選択肢に向いています。
  • CListCtrl は 1 行に複数列を持つ表形式の一覧に向いています。
  • 判断軸は「1 件を 1 文字列で持てるか、複数フィールドが要るか」です。
  • CListBox にも LBS_MULTICOLUMN はありますが、CListCtrl の Report View の代わりではありません。
  • 列ヘッダー、サブアイテム、仮想リスト、大量データ対応が要るなら CListCtrl を選びます。
  • 後から列が増えそうなら、最初から CListCtrl を選ぶほうが手戻りを減らせます。
  • 通知メッセージも違うので、載せ替え時は LBN_SELCHANGELVN_ITEMCHANGED を見直します。
目次