第二話 をを、こういった物は勝手にやれてくれるんだ&GDI+を組み込んでみる

MFCの動作開始地点

さて前回作ったソリューション、実行時の開始位置、Cで言う所のvoid main()に当たるメソッドはどこでしょう?
正解はAppクラス(プロジェクト名APP)InitInstance()メソッド。今回のソリューションだとBOOL CChromakeyApp::InitInstance()ですな。え、WinMain()?確かにMFCで作ったプロジェクトをトレースするとMFCフォルダ内のWinMain()辺りから始まるけど弄っちゃダメです、他のソリューションまで挙動変わってしまいます。職場でやったら十中八九殴られます。そういったWin32的な物を包み隠した(ラップした)のがMFCなんですから。

以上、MFCフォルダ内を弄らせないための脅しでした。

まずはGDI+の起動、終了から

クロマキーツール作るのに最低限無いとダメな機能、画像インタフェースを仕込んでみましょう。自前で書くのは嫌なので既存の物から探しましょう。(自前で書いても良いけど今回の趣旨に反してるし)画像インタフェースは色々(LEADTOOLとかLibPngとか)あるのですが、今回は楽したいのでGDI+を使います。
プロジェクト名のCPPファイルとヘッダファイルを開いて下さいな。まずはヘッダファイルから、ハイライト部を追加して下さい。(ちゃんと括弧内に記述するように)

// Chromakey.h : PROJECT_NAME アプリケーションのメイン ヘッダー ファイルです。
//

#pragma once

#ifndef __AFXWIN_H__
#error "PCH に対してこのファイルをインクルードする前に 'stdafx.h' をインクルードしてください"
#endif

#include "resource.h"	// メイン シンボル
#include <gdiplus.h>


// CChromakeyApp:
// このクラスの実装については、Chromakey.cpp を参照してください。
//

using namespace Gdiplus;

class CChromakeyApp : public CWinAppEx
{
public:
	CChromakeyApp();

// オーバーライド
public:
	virtual BOOL InitInstance();

// 実装

DECLARE_MESSAGE_MAP()

protected:
	GdiplusStartupInput gdiSI;
	ULONG_PTR gdiToken;

};

extern CChromakeyApp theApp;

これに関してはGDI+のおまじないなので気にしないように。
続いてCPPファイルより抜粋でハイライト部を追加して下さい。(ちゃんと括弧内に記述するように)

BOOL CChromakeyApp::InitInstance()
{
	// アプリケーション マニフェストが visual スタイルを有効にするために、
	// ComCtl32.dll Version 6 以降の使用を指定する場合は、
	// Windows XP に InitCommonControlsEx() が必要です。さもなければ、ウィンドウ作成はすべて失敗します。
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	// アプリケーションで使用するすべてのコモン コントロール クラスを含めるには、
	// これを設定します。
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	CWinAppEx::InitInstance();

	AfxEnableControlContainer();

	// 標準初期化
	// これらの機能を使わずに最終的な実行可能ファイルの
	// サイズを縮小したい場合は、以下から不要な初期化
	// ルーチンを削除してください。
	// 設定が格納されているレジストリ キーを変更します。
	// TODO: 会社名または組織名などの適切な文字列に
	// この文字列を変更してください。
	SetRegistryKey(_T("アプリケーション ウィザードで生成されたローカル アプリケーション"));

	// GDI+立ち上げ
	GdiplusStartup(&gdiToken, &gdiSI, NULL);

	CChromakeyDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	{
		// TODO: ダイアログが <OK> で消された時のコードを
		// 記述してください。
	}
	else if (nResponse == IDCANCEL)
	{
	// TODO: ダイアログが <キャンセル> で消された時のコードを
	// 記述してください。
	}

	// GDI+終了
	GdiplusShutdown(gdiToken);

	// ダイアログは閉じられました。アプリケーションのメッセージ ポンプを開始しないで
	// アプリケーションを終了するために FALSE を返してください。
	return FALSE;
}

知らなきゃ知らないで困りませんが、InitInstance()で追加したのはなんなのか解説行きましょう。たいした事やってはいないんですけど。(まともに解説するとAppクラスもユーザインタフェーススレッドでWinMainからスレッド呼び出しされている云々まで踏み込む事になりかねないので)
キモはこれだけです。平たく言えばここで先ほど弄ったダイアログの表示を行っているだけなんですが。

CChromakeyDlg dlg; ここでダイアログクラスの領域構築(クラス変数を作る。int やcharで変数作るのと同じ)
m_pMainWnd = &dlg; 構築した領域をアプリケーションに登録。(アプリケーションで管理している領域のいずれからもアクセス可能に)
INT_PTR nResponse = dlg.DoModal(); ダイアログをモーダル表示し、表示終了時の結果を取得する。(ダイアログ終了時の戻り値)

いきなりクラスが出たりと訳判りませんね?そもそもintやcharにHWND、構造体は知ってるが、CChromakeyDlgって何よ?でしょう。
先ほど作ったリソース、実はCChromakeyDlgクラスにくっついているのです。
クラスというのは処理のひな形の固まりみたいな物で、このクラスの場合勝手にWindowと付随するコントロールをメモリに構築してくれるありがたいクラスです。(MFCには他にもファイルテーブルアクセスクラスとか、スレッドクラスとかTCP/IPクラスとか色々あります)
これを使うと人間様は領域を作ったら、表示してねと指示するだけで後はMFCが良きに計らってくれる訳ですね。(ひな形部分でCreateWindow()やらしてくれる訳ね)

ファイルダイアログを呼び出してファイルネームを取得しよう

では初めての処理実装です。ファイルネーム取得処理を実装しましょう。今回はファイルダイアログを使ってみます。(ファイルドラッグでやるとクライアントの受けはよろしいですが、解説面倒なので)
前回の要領でウィンドウにButtonを貼り付けてプロパティを以下のようにして下さい。
ID IDC_BUTTONLOAD
Caption ファイル読み込み
次にボタンの処理を実装します。常識的にクリックしたらファイルダイアログが呼び出されるようにしましょう。俺はダブルクリック派だとか言わないように。Win32だとWndProc()に実装しますが、MFCの場合こうします。
Buttonを右クリックし、イベントハンドラの追加を選択。
001

ダイアログが出たらメッセージの種類をBN_CLICKED選択して追加して編集をクリック。CChromakeyDlg.CPPにこのメソッドが出来ていたら成功*1です。

void CChromakeyDlg::OnBnClickedButtonload()
{
// TODO: ここにコントロール通知ハンドラ コードを追加します。
}
このメソッドにブレークポイントを付けて実行してみましょう。ボタンをクリックしてブレークポイントに引っかかるのが確認出来ましたね?
002

次にファイル選択ダイアログの呼び出し処理を実装しましょう。WindowsにはファイルダイアログがDLLに存在しているのでそれを呼び出します、都合良くMFCは呼び出し部もラッパーされてクラス化されているのでラクチンです。

// ファイル読み込みボタン
//
void CChromakeyDlg::OnBnClickedButtonload()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。

	// カレントフォルダ退避
	char cPath[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, cPath);

	CFileDialog dlg( TRUE,
			NULL,,
			NULL,
			OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
			"bmp File|*.bmp|Jpg File|*.jpg;*.jpeg|Png File|*.png|");

	if(IDOK == dlg.DoModal()){
		CStringW strFile(dlg.GetPathName());
		// ここに画像読み込み処理を実装予定
	}

	// カレントフォルダに戻す
	SetCurrentDirectory(cPath);

}
ビルドして実行しファイル読み込みボタンをクリックするとファイル選択ダイアログが表示されるはずです。
003

わからんであろう箇所を上から順に説明

GetCurrentDirectory() 現在のフォルダパスを待避
CFileDialog dlg ファイル選択ダイアログクラス構築
if(IDOK == dlg.DoModal()) ファイル選択ダイアログ呼び出し
CStringW strFile(dlg.GetPathName()); ファイル選択ダイアログクラスからファイルネーム取得
SetCurrentDirectory(); フォルダを処理前に戻す

このCFileDialogクラスがファイルダイアログの制御一切合切を行います。CFileDialogの仕様に関してはググってください。dlg()内に色々とパラメータを設定してますが、これはコンストラクタ引数と言い、クラスの初期化時にこの値を参考にクラスを初期化します。今回の設定方法だと読み込みモードでBMP、JPG、PNG拡張子のファイルを選択出来るファイルダイアログを構築出来ます。
CStringWはUNICODE型の文字列クラス(MFCというかATL)で、マルチバイトをUNICODEに変換したりも出来るありがたいクラスです(*2)。まぁ、領域確保の要らない可変長のchar配列と思ってくれればOK。GDI+はUNICODEしか使えないのでここで変換します。これでファイルネームを取得する手段が出来ました。さて次回はクラスを作ってみましょう。

(*1)失敗した場合はVSを終了してソリューションフォルダのNCBファイル、(あったら)CLWファイル、DEBUG、RELEASEフォルダを削除してVSを再起動して下さい。大抵直ります。

(*2)CStringと言うマルチバイトの文字列クラスもあり、UNICODEをマルチバイトに変換出来る。MFCでは文字コードが簡単と書いたのはこれが理由ですな。