Visual Studio 2017 Visual C++ による MFC デスクトップアプリ開発 (Dialog Base)

#はじめに
MFC とは Micorosft Foundation Class の略で、.NET Framework 登場前は Windows アプリ開発の主役でした。現在では MFC を使っての新規開発はもうないでしょうが、古いアプリを流用したり、保守したりということは時々あるかと思います。

しかし、.NET での開発が主流となって、MFC を扱える人もどんどん減っていると思われます。ここでは、Visual Studio 2017 を使って MFC によるデスクトップアプリ開発はこんな感じという程度の内容ですが、紹介したいと思います。


#プロジェクトの作成
MFC プロジェクトの作成機能はデフォルトでは Visual Studio 2017 に組み込まれていません。Visual Studio インストーラで追加する必要があります。

Visual Studio 2017 の Update 3 より前と後とではプロジェクト作成画面が大幅に変わっています。次の画面例は、Update 3 以降のものです。

「新しいプロジェクト」ダイアログで「MFC アプリケーション」を選択します。

![NewProject.png](https://qiita-image-store.s3.amazonaws.com/0/62802/2fc78029-b666-beec-5bf8-d06a79d4e7c8.png)



「アプリケーションの種類のオプション」ダイアログが開くので、「アプリケーションの種類」を決めます。

![MFCAppOption.png](https://qiita-image-store.s3.amazonaws.com/0/62802/531a7d81-fbab-45fe-2d7e-febbf28c5f31.png)


(注意) Visual Studio 2017 Update 2 までは、オフィス風、エクスプローラ風のアプリケーションテンプレートも含まれていましたが、Update 3 では削除されたようです。

- **シングルドキュメント**
メニューバー、ツールバー、ステータスバーを含む単一ウィンドウのアプリケーション
- **複数のドキュメント**
メニューバー、ツールバー、ステータスバーを含むフレームウィンドウに複数の子ウィンドウを表示するのアプリケーション
- **ダイアログベース**
１つのダイアログを持つアプリケーション
- **複数のトップレベルドキュメント**
Visual Studio 風のアプリケーション




ここでは一番構造が単純な「ダイアログベース」を選択します。「ダイアログベース」を選んだ場合、ここで「完了」ボタンをクリックしても構いません。一般には、次のような画面が続きます。

![MFCAppDocTempalte.png](https://qiita-image-store.s3.amazonaws.com/0/62802/46797861-a81f-5caa-8366-5a9cc7ee98a2.png)

![MFCAppUI.png](https://qiita-image-store.s3.amazonaws.com/0/62802/b9de2561-f01a-c869-424d-d8f52b0aafb1.png)

![MFCAppHigh.png](https://qiita-image-store.s3.amazonaws.com/0/62802/3c1f051c-abe2-9555-2601-81290cc130a1.png)

![MFCAppClassOption.png](https://qiita-image-store.s3.amazonaws.com/0/62802/aa4ccde0-fd64-553a-2c39-11fa3877b45e.png)

プロジェクトが作成されると、ソリューションエクスプローラに次のようなツリーが表示されます。

![SolutionExplorer.png](https://qiita-image-store.s3.amazonaws.com/0/62802/2bb9b3a6-58d0-ea3a-8081-a76f03f33c45.png)


#ダイアログベースのアプリについて
ダイアログベースのアプリはダイアログをメインウィンドウにしたアプリケーションで、Visual Basic 風にコントロールを貼り付けて画面を作ることができます。

しかしながら、あくまで MFC の制限範囲内での話で、コントロールのプロパティをプロパティエディタで指定できるのはほんの一部で、プロパティの多くはコードで書く必要があります。これは、画面のデザインで設定するのはあくまで「リソース」(静的な画面のデータ) だけで、動的に決める必要のある項目はコードで書く必要があるためです。

画面のデザインは、ソリューションエクスプローラのリソースビューで Dialog の中の IDD_MFCAPPLICATION1_DIALOG をダブルクリックすると表示されます。

![MFCAppDialog1.png](https://qiita-image-store.s3.amazonaws.com/0/62802/4aedefab-5d13-23fe-d5c3-63ab2cbe493d.png)


このダイアログにツールボックスから必要なコントロールを貼り付けることで画面のデザインが可能です。

![Dialog1Design.png](https://qiita-image-store.s3.amazonaws.com/0/62802/36a6eaa5-e86d-8685-488c-d5fb2f42965e.png)




#サンプルアプリケーション
ここでは、ボタンをダイアログに貼り付けて、ボタンをクリックするごとにクリック回数を表示する単純なアプリを作成してみます。

![MFCDialogButton1.png](https://qiita-image-store.s3.amazonaws.com/0/62802/f0e54f88-9d9e-8c07-c394-b17617201141.png)


プロパティエディタをイベントページに切り替えて、ボタンがクリックされたときのイベントハンドラをコードに追加します。

![Dialog1ButtonHandler.png](https://qiita-image-store.s3.amazonaws.com/0/62802/707322ed-8b3f-6951-a075-1dc1201f636b.png)


イベントハンドラのコード編集を行います。スタティックコントール (.NET での Label) にクリックごとにクリック回数を増加させ、その値を表示します。次の画像はその実行例です。

![MFCSampleApp1.png](https://qiita-image-store.s3.amazonaws.com/0/62802/331f4c53-ffd2-74b3-22ce-e5e7769b60e3.png)


*ヘッダファイル*

修正箇所はクリック回数 m_Count を追加しただけです。

```C++
// MFCApplication1Dlg.h : ヘッダー ファイル
//

#pragma once


// CMFCApplication1Dlg ダイアログ
class CMFCApplication1Dlg : public CDialog
{
// コンストラクション
public:
	CMFCApplication1Dlg(CWnd* pParent = NULL);	// 標準コンストラクター

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_MFCAPPLICATION1_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV サポート


// 実装
protected:
	HICON m_hIcon;
	INT m_Count;  // クリック回数

	// 生成された、メッセージ割り当て関数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnBnClickedButton1();
};
```

*C++ ファイル*

修正箇所はボタンクリック・イベントハンドラの中身を記述しただけです。

```C++
// MFCApplication1Dlg.cpp : 実装ファイル
//

#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// アプリケーションのバージョン情報に使われる CAboutDlg ダイアログ
class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV サポート

// 実装
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMFCApplication1Dlg ダイアログ
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=NULL*/)
	: CDialog(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_Count = 0;
}

void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CMFCApplication1Dlg::OnBnClickedButton1)
END_MESSAGE_MAP()

// CMFCApplication1Dlg メッセージ ハンドラー

BOOL CMFCApplication1Dlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// "バージョン情報..." メニューをシステム メニューに追加します。

	// IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	//  Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE);			// 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE);		// 小さいアイコンの設定

	// TODO: 初期化をここに追加します。

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

void CMFCApplication1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// ダイアログに最小化ボタンを追加する場合、アイコンを描画するための
//  下のコードが必要です。ドキュメント/ビュー モデルを使う MFC アプリケーションの場合、
//  これは、Framework によって自動的に設定されます。

void CMFCApplication1Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 描画のデバイス コンテキスト

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// クライアントの四角形領域内の中央
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// アイコンの描画
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// ユーザーが最小化したウィンドウをドラッグしているときに表示するカーソルを取得するために、
//  システムがこの関数を呼び出します。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

// Button1 イベントハンドラ
void CMFCApplication1Dlg::OnBnClickedButton1()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	WCHAR szCount[10];
	m_Count++;
	wsprintf(szCount, _T("%d"), m_Count);
	SetDlgItemText(IDC_STATIC, szCount);
	Invalidate();
	UpdateWindow();
}
```

-