【MFC】オブジェクトを格納するリストを作りたい

公私ともにコードを書く機会が減ってしまったのですが、
最近ひょんなことから昔書いてた処理を再度作ってみることになりました。

かなり古いタイプですが、備忘録程度。

概要説明

今回作るもの:MFCによるオブジェクトリスト管理アプリ

今回は、Visual Studio 2026を使用して、MFC(Microsoft Foundation Class)の基本が詰まった小さなアプリケーションを作成します。

機能概要

画面上の各コントロール(エディット、コンボ、チェック)に入力された内容を一つの「オブジェクト」としてまとめ、それをリスト構造で保持・表示するプログラムです。

技術的なとっかかり

現代では標準ライブラリ(std)やモダンなフレームワークが主流ですが、今回はあえて「昔業務でよく使われていた、手触り感のあるMFCらしい書き方」をベースに解説します。(というか、自分が述べられるのは古い知識ばかりです)

複雑なフレームワークに頼らず、「Windowsアプリがどう動いているか」を実感できる内容を目指します。

準備

プロジェクトについて

MFCプロジェクトで、ダイアログベースを選択します。

今回は「ObjectListSample」という名前にしました。

リソース画面

エディットボックス、コンボボックス、チェックボックスを用意しました。

別に3種も必要なく、他のコントロールでもよいですがわかりやすかったので採用。

後々のため、リストコントロールも配置しています。
リストコントロールはプロパティから、ビューをレポートにしています。

後は決定ボタン、表示ボタン。
処理の関係上作りました。

因みに先出しすると、ビルドしたら最終的にこんな感じにさせます。

配置とIDに関しては以下の通り。

IDD_OBJECTLISTSAMPLE_DIALOG DIALOGEX 0, 0, 320, 242
STYLE DS_SETFONT | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 9, "MS UI Gothic", 0, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,209,221,50,14
    PUSHBUTTON      "キャンセル",IDCANCEL,263,221,50,14
    LTEXT           "数字",IDC_STATIC_NUM,30,30,15,8
    LTEXT           "選択",IDC_STATIC_COMBO,100,30,15,8
    LTEXT           "チェック",IDC_STATIC_CHECK,170,30,20,8
    EDITTEXT        IDC_EDIT_NUM,30,60,40,14,ES_AUTOHSCROLL | ES_NUMBER
    COMBOBOX        IDC_COMBO,100,60,48,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
    CONTROL         "チェック",IDC_CHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,170,60,35,10
    PUSHBUTTON      "決定",IDC_BUTTON_DECIDE,250,60,50,14
    CONTROL         "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,30,90,269,114
    PUSHBUTTON      "表示",IDC_BUTTON_DISP,34,212,50,14
END

新規クラス

クラスの追加をします。今回は、
・クラス:CObjectSample
・ヘッダーファイル:Object.h
・cppファイル:Object.cpp
にしました。

お好みで作ってください(これはあまり真似しない方がよいかも)

余談ですが、なぜクラス名とファイル名が少し違うかですが。
実は最初クラスをCObjectにして処理を書いてしまってました。
途中で「アホー!!」って自分を責めましたね。
久々にコード書くから失態やらかす。

さて、今回の”みそ”、オブジェクトを格納するリストについて。
ヘッダーで定義します。

#pragma once

// 先行定義
class CObjectSample;

typedef CTypedPtrList<CPtrList, CObjectSample*> CSampleObjList;

class CObjectSample
{
private:
	// メンバ変数
	CString m_strNum;	// エディットボックス
	CString m_strCombo;	// コンボボックス
	CString m_strCheck;	// チェックボックス

public:
	CObjectSample();	// コンストラクタ
	~CObjectSample();	// デストラクタ(今回は用なし)

	// セット関数
	void SetNum(CString strNum);
	void SetCombo(CString strCombo);
	void SetCheck(CString strCheck);

	// ゲット関数
	CString strGetNum();
	CString strGetCombo();
	CString strGetCheck();
};

6行目ですね。CTypePtrListによるメモリ管理です。
ポインタをリストで扱う、C++ならではの管理手法を再現します。

CObjectSampleクラスをnewして作ったオブジェクトのポインタを格納します。
typedefについては、CTypePtrList<…>と毎回書くのは大変なので、CSampleObjectListという短い名前で呼べるようにしているだけです。

一応業務中に知った知識では、リストからデータを取り出す際に自動的にCObjectSample*型として扱えるため、バグが混入しにくくメンテナンスの高いコードだとか。(本当なのかは未だわからず…)

各関数を、Object.cppファイルで実装します。
大したことはしません。メンバ変数初期化と、セットゲット関数処理実装です。

#include "pch.h"
#include "Object.h"

/**
 * @brief コンストラクタ
 * @return なし
 * @details メンバ変数初期化
 */
CObjectSample::CObjectSample()
{
	m_strNum = _T("");
	m_strCombo = _T("");
	m_strCheck = _T("");
}

/**
 * @brief デストラクタ
 * @return なし
 */
CObjectSample::~CObjectSample()
{
	// 特に何もしない
}

/**
 * @brief エディットボックス状態セット関数
 * @param CString strNum エディットボックスのテキスト
 * @return なし
 */
void CObjectSample::SetNum(CString strNum)
{
	this->m_strNum = strNum;
}

/**
 * @brief エディットボックス状態ゲット関数
 * @param なし
 * @return メンバ変数(エディットボックスの状態)
 */
CString CObjectSample::strGetNum()
{
	return this->m_strNum;
}

/**
 * @brief コンボボックス状態セット関数
 * @param CString strCombo コンボボックスのテキスト
 * @return なし
 */
void CObjectSample::SetCombo(CString strCombo)
{
	this->m_strCombo = strCombo;
}

/**
 * @brief コンボボックス状態ゲット関数
 * @param なし
 * @return メンバ変数(コンボボックスの状態)
 */
CString CObjectSample::strGetCombo()
{
	return this->m_strCombo;
}

/**
 * @brief チェックボックス状態セット関数
 * @param CString strCheck チェックボックスの状態
 * @return なし
 */
void CObjectSample::SetCheck(CString strCheck)
{
	this->m_strCheck = strCheck;
}

/**
 * @brief チェックボックス状態ゲット関数
 * @param なし
 * @return メンバ変数(チェックボックスの状態)
 */
CString CObjectSample::strGetCheck()
{
	return this->m_strCheck;
}

これで、オブジェクトを作れたり、リストにオブジェクトを格納できたりします。

ダイアログ側処理

ヘッダーに追加

今回作ったクラスのダイアログ実装処理について処理を追加します。
ObjectListSampleDlg.hで定義を追加。
先のクラスのリストも定義したいためincludeも忘れずに。


// ObjectListSampleDlg.h : ヘッダー ファイル
//

#pragma once
#include "Object.h"

// CObjectListSampleDlg ダイアログ
class CObjectListSampleDlg : public CDialogEx
{
// コンストラクション
public:
	CObjectListSampleDlg(CWnd* pParent = nullptr);	// 標準コンストラクター
	~CObjectListSampleDlg();// デストラクタ

// 略

public:
	afx_msg void OnBnClickedButtonDecide();
	afx_msg void OnBnClickedButtonDisp();

private:
	CSampleObjList m_objList;	// CObjectSampleクラスのオブジェクトを格納するリスト
};

必要ないところは略しましたが、
6行目のinclude、デストラクタ、後はボタン押下時に走る関数2つと、
先ほどのリストのメンバ変数です。

デストラクタ&OnInitDialog

いよいよ実装。
ObjectListSampleDlg.cppです。

先にデストラクタとOnInitDialogだけ。
よく解放忘れるんですよね。実は今回作ってた時も忘れてました。
C++を使っていた人間とは思えない能力。
AIに尋ねて「解放忘れてるよ」と指摘されました。

/**
 * @brief デストラクタ
 * @return なし
 * @details リストの中身解放
 */
CObjectListSampleDlg::~CObjectListSampleDlg()
{
	// リストの中身をすべて解放
	while (!m_objList.IsEmpty())
	{
		delete m_objList.RemoveHead();
	}
}

BOOL CObjectListSampleDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 略

	// TODO: 初期化をここに追加します。
	CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO);
	pComboBox->AddString(_T("ABC"));
	pComboBox->AddString(_T("HIJ"));
	pComboBox->AddString(_T("OPQ"));

	CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST);
	pListCtrl->InsertColumn(0, _T("数字"), LVCFMT_LEFT, 200);
	pListCtrl->InsertColumn(1, _T("コンボボックス"), LVCFMT_CENTER, 200);
	pListCtrl->InsertColumn(2, _T("チェックボックス"), LVCFMT_CENTER, 200);

	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

OnInitDialogで、コンボボックスとリストコントロールだけ初期化入れました。
ここはお好みで。

決定ボタン押下時

続いて決定ボタン押下時の処理。
ここで、オブジェクトを作って、リストに格納する処理を作りました。

/**
 * @brief 決定ボタン押下時の処理
 * @return なし
 * @details エディットボックス、コンボボックス、チェックボックスの状態取得後、オブジェクト生成→リストに追加
 */
void CObjectListSampleDlg::OnBnClickedButtonDecide()
{
	// エディットボックスの数字
	CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_NUM);
	CString strTextEdit;	// テキストを格納します
	pEdit->GetWindowText(strTextEdit);	// テキスト取得

	// コンボボックスの選択
	CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO);
	int iIndex = pComboBox->GetCurSel();	// 選択されているインデックスを取得
	CString strTextCombo; 
	pComboBox->GetLBText(iIndex, strTextCombo);	// インデックスからテキストを取得
	
	// チェックボックスの状態
	CButton* pButton = (CButton*)GetDlgItem(IDC_CHECK);
	CString strCheck;
	if(pButton->GetCheck() == BST_CHECKED)
	{
		strCheck = _T("チェックあり");
	}
	else
	{
		strCheck = _T("チェックなし");
	}

	CObjectSample* obj = new CObjectSample();	// CObjectクラスのオブジェクトを作成
	obj->SetNum(strTextEdit);		// エディットボックスのテキストをセット
	obj->SetCombo(strTextCombo);	// コンボボックスのテキストをセット
	obj->SetCheck(strCheck);		// チェックボックスの状態をセット

	m_objList.AddTail(obj);	// リストにオブジェクトを追加

	// コントロール初期化
	pEdit->SetWindowText(_T(""));	// エディットボックスのテキストを初期化
	pComboBox->SetCurSel(-1);		// コンボボックスの選択を初期化;
	pButton->SetCheck(BST_UNCHECKED);	// チェックボックスの状態を初期化

}

29行目まではコントロール状態取得しているだけです。
因みに入力チェックなどは一切行ってません。
今回はそこまで求めるモノではないので。

重要なのは31~36行目。

オブジェクト生成後、各値をセット、さらにリストへ格納しています。

最後はまぁ親切程度にコントロール初期化です。

表示ボタン押下時

最後に表示ボタン押下時。
何するかというと、今までリストに格納したデータをリストコントロールに表示させます。

/**
 * @brief 表示ボタン押下時の処理
 * @return なし
 * @details リストの中身をリストコントロールに表示
 */
void CObjectListSampleDlg::OnBnClickedButtonDisp()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	CListCtrl* pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST);

	// リスト初期化
	pListCtrl->DeleteAllItems();

	LVITEM lvItem{};
	lvItem.mask = LVIF_TEXT;
	CString strTemp = _T("");
	lvItem.iItem = 0;

	POSITION pos = m_objList.GetHeadPosition();
	while (pos != NULL)
	{
		// GetNextは「現在の要素を返し、posを次の要素に進める」関数です
		CObjectSample* pCurrent = m_objList.GetNext(pos);

		lvItem.iSubItem = 0;	// 列番号
		strTemp = pCurrent->strGetNum();	// エディットボックスの状態を取得
		lvItem.pszText = strTemp.GetBuffer();
		pListCtrl->InsertItem(&lvItem);

		lvItem.iSubItem = 1;	// 列番号
		strTemp = pCurrent->strGetCombo();	// コンボボックスの状態を取得
		lvItem.pszText = strTemp.GetBuffer();
		pListCtrl->SetItem(&lvItem);

		lvItem.iSubItem = 2;	// 列番号
		strTemp = pCurrent->strGetCheck();	// チェックボックスの状態を取得
		lvItem.pszText = strTemp.GetBuffer();
		pListCtrl->SetItem(&lvItem);

		lvItem.iItem++;	// 行番号
	}
}

リストコントロールの使い方は以前記事にしたので参照ください。

19行目でリストの先頭位置取得。
その後リストの中身分ぐるぐるループ回します。
(22行目のコメント、書いた記憶ないなぁと思ったらAIが自動補完してくれてました。)

これにて終了。

動作確認

まずはこんな感じで。

決定ボタンを押すと、裏でリストに格納されます。
後コントロールも初期化。
続けて後2つほど登録。

最後に表示ボタンを押すと

ちゃんと表示されました。

終わりに

かなり久々の更新。

Visual Studio 2026を使い、伝統的(?)な手法でMFCアプリを作成しました。

30分程度で動くものが作れるMFCは、
ちょっちしたツール作成やプロトタイピングにおいて、
今でも非常に強力な武器じゃないかなぁと個人的には思っています。

なお、既にエンジニアから身を引いているモノの感想です。

↓今回のソースコード↓

GitHub - Tatsumath/Practice: Only note for me
Only note for me. Contribute to Tatsumath/Practice development by creating an account on GitHub.

コメント

タイトルとURLをコピーしました