Visual Studio 2022 で MFC のダイアログベースアプリを作り始めると、「ダイアログの追加」「Name の変更」「閉じたダイアログの再編集」「ボタンから新しいダイアログを開く」といった基本操作で意外と迷います。本記事では、VS2022 で複数ダイアログを扱うときにハマりやすいポイントを、実際の画面操作と C++ コード例を交えながら体系的に整理します。
MFC で複数ダイアログを作るときにハマる4つの課題
MFC のダイアログベースアプリで、2つ目・3つ目のダイアログを扱い始めると次のような疑問が一気に押し寄せます。
- 課題A:ダイアログの「Name(Misc→Name)」が変更できない。タブ名も似たような表示で見分けにくい。
- 課題B:2つ目以降のダイアログを追加しても「リソースだけ増えてクラスが紐づかない」。正しい追加の流れが分からない。
- 課題C:いったん閉じたダイアログを、再びデザイナ(リソースエディタ)で開く方法が分からない。
- 課題D:ボタンを押したときに新しいダイアログを開くコード(モーダル/モデルレス)の正しい書き方が分からない。
特に Visual Studio 2022 では、メニュー構成やアイコンが以前のバージョンから変わっており、「リソース ビューはどこに行ったの?」と探すところからスタートしがちです。
ここからは、これらの課題を順番に「なぜそうなるか」「どう直せば良いか」の順に整理していきます。
「Name」は見た目のラベル、本当に変えるのは ID(課題A)
MFC のダイアログを選択してプロパティウィンドウを見ると、Misc → Name に何かしらの名前が表示されています。しかし VS2022 ではここが編集不可で、変更しようとしてもカーソルすら入りません。
ここで重要なのは、「Name はあくまでエディタ上の表示名であり、中身を識別しているのは ID である」という点です。ダイアログやコントロールを区別している本体は、次のようなマクロ名です。
- ダイアログの ID:
IDD_DIALOG1/IDD_SHOW_ADDITIONなど - ボタンの ID:
IDC_BUTTON1/IDC_SHOW_ADDITIONなど
まとめると、次のような関係になっています。
| 項目 | 役割 | 変更の可否 | 変更するべきか |
|---|---|---|---|
| Misc → Name | エディタ上の表示用ラベル(VS が自動管理) | 基本的に編集不可 | 直接いじらない |
| ID(IDD_… / IDC_…) | リソースを一意に識別する値 | プロパティウィンドウから変更可能 | ここを分かりやすい名前に変える |
| Caption | ダイアログのタイトルバーに表示される文字列 | 編集可能 | タブや画面の見分けに有効 |
ID を変更する具体的な手順
ダイアログの ID を変更する手順はとてもシンプルです。
- ダイアログ デザイナで、変更したいダイアログの余白をクリックして全体を選択します。
- プロパティウィンドウの ID 欄を探します。
IDD_DIALOG1などになっている箇所をIDD_SHOW_ADDITIONのように変更します。- 一度別のコントロールやリソースをクリックしてから戻ると、Name の表示も新しい ID に合わせて更新されます。
タブが似たような名前で見分けにくい場合は、ID に加えて Caption も分かりやすい日本語にしておくと管理がグッと楽になります。
- ID:
IDD_DLG_ADD/ Caption:「加算ダイアログ」 - ID:
IDD_DLG_MUL/ Caption:「乗算ダイアログ」
なお、ダイアログのテンプレートは内部的に「数値 ID」または「文字列 ID」で識別できますが、実務では IDD_* の数値 ID で統一しておく方が分かりやすく、サンプルコードとも整合が取れます。
ダイアログ“リソース”と“MFC クラス”をセットで作成する(課題B)
複数ダイアログで一番多いトラブルが「リソースは追加したのに、対応する CDialogEx 派生クラスが無い」という状態です。この状態では、ボタンからダイアログを開こうにも、呼び出すべきクラスが存在しません。
ポイントは、ダイアログを使うなら「リソース」と「MFC クラス」をセットで用意することです。VS2022 では以下の 2 パターンの作り方があります。
| 方法 | 特徴 | おすすめ度 |
|---|---|---|
| ① MFC Class から作る | クラスとダイアログリソースが一度に生成される | ◎(最も安全) |
| ② ダイアログリソースからクラスを追加する | 既存リソースに対してあとからクラスを割り当てる | ○(既存プロジェクトの拡張向き) |
おすすめ:MFC Class を追加してリソースも同時に作る
新しいダイアログを追加する場合は、次の手順が一番トラブルが少なくおすすめです。
- ソリューション エクスプローラーでプロジェクトを右クリックし、[追加] → [新しい項目] を選びます。
- テンプレート一覧から [Visual C++] → [MFC] → [MFC クラス] を選択します。
- クラス名を入力します(例:
CShowAdditionDlg)。 - Base class を
CDialogまたはCDialogExに変更します(最近のテンプレートならCDialogEx推奨)。 - Dialog ID の欄に、作成したいダイアログ ID(例:
IDD_SHOW_ADDITION)を入力します。 - [OK] を押すと、.h / .cpp と .rc 内のダイアログリソースが同時に生成されます。
このルートで作成すると、C++ のクラスとダイアログが最初から設計時に紐づいているため、あとから ID を合わせる作業に悩まされにくくなります。
すでにダイアログリソースだけ作ってしまった場合
「先にリソースビューからダイアログを追加してしまった」という場合も、あとからクラスを紐づけることができます。
- メニューから [表示] → [その他のウィンドウ] → [リソース ビュー] を開きます。
- リソース ビューで
プロジェクト名.rc → Dialogを展開し、対象のダイアログ(例:IDD_DIALOG1)を右クリックします。 - [Add Class…](または [クラス ウィザード]) を選択します。
- クラス名を入力し、Base class = CDialog / CDialogEx を選びます。
- Dialog ID = そのダイアログの ID(例:IDD_DIALOG1) を指定してクラスを作成します。
これで、既存のダイアログリソースと新しい MFC クラスが関連づきます。
ID を変更したときに必ず合わせるべき enum { IDD = … }
ダイアログの ID を後から変更した場合、対応する C++ クラスのヘッダファイルもセットで更新する必要があります。典型的には以下の部分です。
class CShowAdditionDlg : public CDialogEx
{
// ... 省略 ...
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_SHOW_ADDITION };
#endif
// ... 省略 ...
};
ここが以前の ID のままだと、
- ダイアログを実行すると別のテンプレートが開く
- デザイナとコードの対応関係が分かりづらくなる
といったトラブルの原因になります。ID を変えたら、プロパティウィンドウの ID と、この enum の両方を必ずセットで更新する習慣を付けておきましょう。
閉じたダイアログを再びデザイナで開く(課題C)
一度ダイアログをデザインし終わってタブを閉じると、「どうやって再び開くのか」が分からなくなることがあります。ポイントは、“ソースコードとして開く” のではなく “リソースとして開く”という意識です。
リソース ビューからダイアログを再オープンする手順
- メニューの [表示] → [その他のウィンドウ] → [リソース ビュー] を開きます。
- リソース ビューで
プロジェクト名.rcを展開し、その下の [Dialog] ノードを展開します。 - 開きたいダイアログの ID(例:
IDD_SHOW_ADDITION)を探します。 - その ID を ダブルクリックすると、ダイアログ デザイナが再びタブに表示されます。
もし Dialog ノードが何も表示していない場合は、
- そもそも該当プロジェクトの .rc を開いているか
- 別プロジェクトの .rc を見ていないか
を確認します。ソリューションに複数プロジェクトがある場合、同名の .rc が複数存在していることもあるので、どの .rc の配下を操作しているかに注意しましょう。
ボタンから新しいダイアログを開くコード(課題D)
ダイアログを追加してクラスも作成できたら、次は既存の画面から新しいダイアログを表示したくなります。ここでよくやってしまう誤りが、次のようなコードです。
// これは NG!
IDD_SHOW_ADDITION->ShowWindow(SW_SHOW);
IDD_SHOW_ADDITION はあくまで “数値マクロ” であり、ウィンドウオブジェクトではありません。やりたいことは、
- CShowAdditionDlg クラスのインスタンスを作る
- そのインスタンスに対して
DoModal()またはCreate()+ShowWindow()を呼ぶ
という 2 ステップです。
モーダルダイアログで開く(最もシンプル)
親ダイアログを操作できないようにして、新しいダイアログの操作が終わるまで待ちたい場合は モーダルダイアログを使います。コードは次のようになります。
// 例: 親ダイアログ CAboutDlg のボタンクリック ハンドラ
#include "ShowAdditionDlg.h" // 生成されたヘッダー
void CAboutDlg::OnBnClickedShowAddition()
{
CShowAdditionDlg dlg; // スタックにインスタンスを生成
dlg.DoModal(); // モーダル表示(閉じるまでここでブロック)
}
ポイントは、
CShowAdditionDlgのインスタンスを関数ローカル変数として宣言するDoModal()が戻ってきたときに自動的に破棄される(スタック変数なので delete 不要)
というシンプルなライフサイクルにあります。設定ダイアログや確認ダイアログなど、多くのケースでまずはこの形を選んでおくと安全です。
モデルレスダイアログで開く(親と同時に操作したいとき)
一方、親ダイアログと新しいダイアログを同時に操作したい場合は モデルレスダイアログにします。このときライフサイクル管理を正しく行わないと二重解放や不正アクセスにつながるので注意が必要です。
親のメンバーとして持つモデルレスダイアログ(おすすめパターン)
もっとも分かりやすく安全なのが、親ダイアログのメンバー変数として子ダイアログを保持する方法です。
// AboutDlg.h
#include "ShowAdditionDlg.h"
class CAboutDlg : public CDialogEx
{
// ... 省略 ...
private:
CShowAdditionDlg m_dlgShowAddition; // メンバーとして保持
afx_msg void OnBnClickedShowAddition();
DECLARE_MESSAGE_MAP()
};
// AboutDlg.cpp
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
// ... 他のハンドラ ...
ON_BN_CLICKED(IDC_SHOW_ADDITION, &CAboutDlg::OnBnClickedShowAddition)
END_MESSAGE_MAP()
void CAboutDlg::OnBnClickedShowAddition()
{
// まだウィンドウが作られていなければ Create
if (!::IsWindow(m_dlgShowAddition.m_hWnd)) {
m_dlgShowAddition.Create(IDD_SHOW_ADDITION, this); // this を親に
}
m_dlgShowAddition.ShowWindow(SW_SHOW); // 表示
m_dlgShowAddition.SetFocus(); // 必要ならフォーカスを与える
}
この方法のメリットは、
- new / delete を自分で書かなくてよい(メンバー変数なので親の寿命に連動)
- すでに開いている場合は
IsWindow()で判定して前面に出すだけでよい
というシンプルさにあります。モデルレスダイアログが 1〜2 個程度であれば、まずこの形から検討するのがおすすめです。
自己破棄するモデルレスダイアログ(new / delete パターン)
より柔軟な制御が必要なときは new で生成し、ダイアログ自身が delete this; する「自己破棄パターン」もよく使われます。
// AboutDlg.h
class CAboutDlg : public CDialogEx
{
// ... 省略 ...
private:
CShowAdditionDlg* m_pShowAddition = nullptr;
afx_msg void OnBnClickedShowAddition();
DECLARE_MESSAGE_MAP()
};
// AboutDlg.cpp
void CAboutDlg::OnBnClickedShowAddition()
{
// すでに開いていれば前面に出して終了
if (m_pShowAddition && ::IsWindow(m_pShowAddition->m_hWnd)) {
m_pShowAddition->SetForegroundWindow();
return;
}
// 新規作成
m_pShowAddition = new CShowAdditionDlg();
if (!m_pShowAddition->Create(IDD_SHOW_ADDITION, this)) {
delete m_pShowAddition;
m_pShowAddition = nullptr;
return;
}
m_pShowAddition->ShowWindow(SW_SHOW);
}
// ShowAdditionDlg.cpp(ダイアログ側)
void CShowAdditionDlg::PostNcDestroy()
{
CDialogEx::PostNcDestroy();
delete this; // モデルレスで自己破棄する典型パターン
}
このパターンを使うと、親から見たときに「閉じられたタイミング」が分かりにくくなるため、
- ダイアログが閉じられたときに
GetOwner()->PostMessage(WM_APP + 1, ...);などで親に通知する - 親はそのメッセージを受け取ったタイミングで
m_pShowAddition = nullptr;にしておく
といったケアが必要です。中〜大規模のアプリや、プラグイン的にダイアログを増やしていく設計で使われます。
モーダルとモデルレスの使い分け早見表
| 項目 | モーダル | モデルレス |
|---|---|---|
| 代表的な API | DoModal() | Create() + ShowWindow() |
| 親の操作 | 子が開いている間は操作できない | 親と子を同時に操作できる |
| ライフサイクル管理 | 関数のローカル変数で十分(簡単) | メンバー or ポインタで保持し、 寿命を設計する必要がある |
| 主な用途 | 設定画面、確認ダイアログなど | ツールウィンドウ、ログビューなど |
よくある落とし穴とチェックポイント
ここまでの内容を踏まえて、複数ダイアログを扱うときの「ありがちな落とし穴」と、そのチェックポイントを一覧にしておきます。
| 症状 | 原因になりがちなポイント | 確認すること |
|---|---|---|
| ダイアログを開くコードを書いたのに何も表示されない | クラスが存在しない/ID が一致していない | CShowAdditionDlg のクラスが作られているか/enum { IDD = … } がリソースの ID と一致しているか |
| タブに同じような名前のダイアログが並んで混乱する | ID / Caption が似すぎている | ID を短く分かりやすく、Caption を用途に合わせた日本語にする |
| ダイアログデザイナを再び開けない | ソースファイルから開こうとしている | 必ずリソース ビュー → Dialog → ID をダブルクリックで開く |
| モデルレスでクラッシュする/メモリリークする | ポインタの寿命管理があいまい | メンバー変数保持か、自己破棄パターンかを明確にし、どちらか一方に統一する |
最短で動かしたいときのチェックリスト
- 新しいダイアログは [追加] → [新しい項目] → [MFC クラス] から作成したか?
- Base class は
CDialogExになっているか? - Dialog ID は
IDD_*形式で分かりやすく命名されているか? - 親ダイアログのボタンに対して BN_CLICKED のハンドラをきちんと追加したか?
- モーダルなら
CShowAdditionDlg dlg; dlg.DoModal();と書いているか? - モデルレスなら
Create()+ShowWindow()を 同じインスタンスに対して呼んでいるか? - ダイアログ ID を途中で変更した場合、ヘッダの
enum { IDD = ... }も追随させたか?
用語の整理:Name / ID / Caption / Resource.h
最後に、MFC のダイアログ周りで登場する用語をざっくり整理しておきます。ここが頭の中でごちゃごちゃしていると、迷子になりやすいポイントです。
| 用語 | どこに出てくる? | 役割 | 実務上の扱い |
|---|---|---|---|
| Name(Misc → Name) | VS のプロパティウィンドウ | エディタが内部的に使う表示名 | 通常は気にしなくてよい。ID を変えると自動で追随する。 |
| ID(IDD_… / IDC_…) | プロパティウィンドウ/Resource.h | リソースを一意に識別するキー | ここを分かりやすい名前に命名するのが重要。 |
| Caption | ダイアログデザイナ/実行時ウィンドウのタイトルバー | ユーザーに見える画面タイトル | 画面の役割が分かる名前にしておくと、タブの見分けにも役立つ。 |
| Resource.h | プロジェクト直下のヘッダファイル | #define IDD_... や #define IDC_... の定義が並ぶ | VS が自動更新するので、手動編集は最小限にとどめる。 |
| .rc ファイル | リソース ビュー/テキストエディタ | ダイアログやメニューなど GUI リソースの定義本体 | 普段はリソースエディタから操作し、直接編集は慎重に行う。 |
| CDialog / CDialogEx 派生クラス | C++ コード(.h / .cpp) | ダイアログの振る舞いを実装するクラス | enum { IDD = IDD_XXX }; がリソースと一致しているか常に意識する。 |
実際の ID・クラス名に合わせたサンプルコードまとめ
ここまでの話を、ご質問でよく登場する ID / クラス名に合わせてサンプルにまとめると、次のような構成になります。
- ダイアログ ID:
IDD_SHOW_ADDITION - ダイアログクラス名:
CShowAdditionDlg - 親ダイアログのボタン ID:
IDC_SHOW_ADDITION
モーダルで開く場合
void CAboutDlg::OnBnClickedShowAddition()
{
CShowAdditionDlg dlg;
dlg.DoModal();
}
モデルレス(親のメンバーとして管理する場合)
// AboutDlg.h
#include "ShowAdditionDlg.h"
class CAboutDlg : public CDialogEx
{
// ...
private:
CShowAdditionDlg m_dlgShowAddition; // モデルレス用のメンバー
afx_msg void OnBnClickedShowAddition();
DECLARE_MESSAGE_MAP()
};
// AboutDlg.cpp
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
// ...
ON_BN_CLICKED(IDC_SHOW_ADDITION, &CAboutDlg::OnBnClickedShowAddition)
END_MESSAGE_MAP()
void CAboutDlg::OnBnClickedShowAddition()
{
if (!::IsWindow(m_dlgShowAddition.m_hWnd)) {
m_dlgShowAddition.Create(IDD_SHOW_ADDITION, this);
}
m_dlgShowAddition.ShowWindow(SW_SHOW);
}
まとめ:複数ダイアログ運用のコツ
MFC で複数ダイアログを扱うときに混乱しがちなポイントは、「Name と ID の役割」「リソースとクラスの紐づけ」「モーダルとモデルレスの違い」の 3 つに集約されます。
- 見た目を変えたいときは Name ではなく ID と Caption を変える。
- ダイアログを追加するときは「リソース」と「CDialog(Ex) 派生クラス」を必ずセットで作る。
- 閉じたダイアログを再編集するときは、リソース ビューから Dialog → ID をダブルクリックする。
- ダイアログを開くコードは必ず「インスタンスに対して」DoModal() または Create()+ShowWindow() を呼ぶ。
- ID を変えたら、クラス側の enum { IDD = … } も忘れずに更新する。
これらのルールさえ押さえておけば、Visual Studio 2022 の MFC プロジェクトでも、2つ目・3つ目のダイアログを迷わず追加・再編集・起動できるようになります。あとは、自分のプロジェクトに合わせて ID の命名規則やダイアログのライフサイクル設計を少しずつ整えていけば、長期的にも保守しやすい MFC アプリを育てていけるはずです。

コメント