MFCTips


ここでは、VC++プログラミングで便利そうなTipsをまとめています。ほとんど自分用。VC++6.0用です。これ以外のバージョンでは状況がまったく違うかもしれません。


Dialogバーの使い方

ダイアログバーは、コントロールバーのひとつですが、ダイアログと同じように リソースを編集してレイアウトを決める事ができます。しかもフレームウインドウに 貼り付いたり、浮かせたりする事も出来るので、何かと便利に使えますが、MFCに用意してあるクラス(CDialogBar)が貧弱で、ダイアログバーのクラス自体でダイアログバーのメッセージを受け取れない、DDX(ダイアログデータエクスチェンジ)がない…など、ほとんど使い物にならないのが実状です。ここでは、ダイアログバーを非常に簡単に、且つ美しく使う方法を説明します。

実は、codeguruというサイトに、非常に素晴らしいコードが掲載されています。それが、CInitDialogBarクラスです。詳しくはこちら。このクラスを使うと、上の制限が全くなくなり、しかもClassWizardも完全にだます事ができ、ダイアログをインプリメントするときと全く同じように扱う事ができます。本当にすばらしいです。

1.まずこちらに行って、CInitDialogBarクラスを頂いてきて(Copy&Paste)、InitDialogBar.h及び、InitDialogBar.cppを作成し、プロジェクトに追加します。自分のコード用に#include行を書き換えるのも忘れずに。

2.紹介されているバグがありますので、これにしたがって、CInitDialogBar::Createを書き換えます。これを忘れると、CInitDialogBar::OnInitDialogBar(普通のダイアログのOnInitDialogに当たる)が二回呼ばれる事になって、おかしな事になります。やることは、CInitDialogBarの中で呼んでいるCreateを、ベースクラスのもの(CDialogBar::Create)に書き換えるだけです。(引数同じ)

3.リソースを作成します。そこでは、挿入→Dialog→IDD_DIALOGBARを選択します。

4.作成ウインドウを表示したまま、Class Wizardを表示すると、新しいクラスを作成するのか聞かれます。ここで新しいクラスの作成を選びます。新しいクラスの基本クラスは、CDialogにしておきます。

5.クラスが作成されたら、そのクラスのヘッダファイル及びCPPファイルを検索し、CDialogをCInitDialogBarに書き換えてしまいます。さらに、ヘッダファイルはじめの方に、#include InitDialogBar.hを付け加えておきます。

6.新しいクラスのコンストラクタを書き換えます。新しいクラスが、CHogeDialogBarとすると、HogeDialogBar.hの

CHogeDialogBar(CWnd* pParent = NULL); // 標準のコンストラクタ

を、

CHogeDialogBar();

に、HogeDialogBar.cppの、

CHogeDilaogBar::CHogeDialogBar(CWnd* pParent /*=NULL*/)
: CDialog(CHogeDialogBar::IDD, pParent)
{
//{{AFX_DATA_INIT(CHogeDialogBar)
//}}AFX_DATA_INIT
}

を、

CHogeDilaogBar::CHogeDialogBar()
{
//{{AFX_DATA_INIT(CHogeDialogBar)
//}}AFX_DATA_INIT
}

にしてしまいます。

7.新しいクラスに、関数OnInitDialogBarを定義します。

BOOL CHogeDialogBar::OnInitDialogBar()
{
	CInitDialogBar::OnInitDialogBar(); // これを書くのを忘れずに

	// ここでダイアログの場合のOnInitDialogと同じように初期化を行えます。

	return TRUE;
}

8.普通のダイアログと同じように、ダイアログバーの各コントロールの設定を行います。DDX(ダイアログデータエクスチェンジ)も可能なため、独自のコントロールも挿入可能です。上の例では、色の変わるボタンは独自コントロールです。もちろん、UpdateData()の呼び出しで自動的に変数に値が代入されるようにする事もできます。

9.ボタン関係は、このままだと勝手に無効になってしまい、灰色に表示されクリックする事が出来ません。これの解決法は、ここに載っています。要は、各ボタンにCCmdUIハンドラを定義してあげればいいです。この例は、IDがIDC_HOGEBUTTON01と、IDC_HOGEBUTTON02のボタンを加えた場合です。OnUpdateButtonを書き換えれば、ある条件のときだけ有効にする事ももちろんできます。

HogeDialogBar.cpp内

//}}AFX_MSG_MAP
ON_UPDATE_COMMAND_UI(IDC_HOGEBUTTON01, OnUpdateButton)
ON_UPDATE_COMMAND_UI(IDC_HOGEBUTTON02, OnUpdateButton)
END_MESSAGE_MAP()
void CHogeDialogBar::OnUpdateButton(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(TRUE); // いつも有効
}

HogeDialogBar.h内

//}}AFX_MSG
afx_msg void OnUpdateButton(CCmdUI *pCmdUI);
DECLARE_MESSAGE_MAP()

10.ダイアログバーを実際に生成します。他のToolBarやStatusBarと同じように、CMainFrame::OnCreateで生成します。

まずメンバとしてCMainFrameクラスに加えます。

MainFrm.h内

CHogeDialogBar m_wndHogeDlgBar;

MainFrm.cpp内OnCreate関数内

if (!m_wndHogeDlgBar.Create(this, IDD_HOGEDIALOGBAR,
CBRS_LEFT|CBRS_TOOLTIPS|CBRS_FLYBY|CBRS_GRIPPER, IDD_HOGEDIALOGBAR))
{
	TRACE0("Failed to create DlgBar\n");
	return -1; // 作成に失敗
}
m_wndHogeDlgBar.EnableDocking(CBRS_ALIGN_RIGHT | CBRS_ALIGN_LEFT); // 右と左にくっつく

DockControlBar(&m_wndHogeDlgBar);

11.バーをフレームウインドウからはずせるようにしたなら、『表示』メニュー等にバーの表示/非表示項目があるほうがいいでしょう。メニューを追加し、ClassWizardで関数を追加します。

void CMainFrame::OnViewHogeDlgBar() 
{
	ShowControlBar(&m_wndHogeDlgBar, 
	(m_wndHogeDlgBar.GetStyle() & WS_VISIBLE)==0, FALSE);
	RecalcLayout();
}

void CMainFrame::OnUpdateViewHogeDlgBar(CCmdUI* pCmdUI) 
{
	pCmdUI->SetCheck((m_wndHogeDlgBar.GetStyle() & WS_VISIBLE) != 0); 
}

アプリケーションクラスに簡単にアクセス

いちいちアプリケーションオブジェクトへのポインタを得るために、AfxGetApp()と書くのがうざったい場合は、アプリケーションクラスのヘッダファイルの、クラス定義が終わったあとに、

extern CHogeApp theApp; // アプリケーションクラスがCHogeAppの場合

とかいておけば、コード中からtheAppだけでアクセスできます。

ダイアログベースのアプリケーションでの小さなアイコン

ダイアログベースのアプリケーションをClassWizardから作成すると、デフォルトでは、ちゃんとアイコンを32*32と16*16のふたつとも作成しても、16*16のアイコンが使われず、32*32のアイコンの縮小したものになってしまいます。これを解消するには、ダイアログクラスの、OnInitDialog関数内の、

SetIcon(m_hIcon, TRUE); // 大きいアイコンを設定
SetIcon(m_hIcon, FALSE); // 小さいアイコンを設定

の部分の下の行をコメントアウトしてしまいます。これで、16*16のアイコンも、自分で作成したものになります。

挿入したクラスをClassWizardに認識させる

頂いてきたクラスなど、ClassWizardから作成しなかったMFCクラスは、プロジェクトに加えてもちゃんと認識されず、ClassWizardからコントロールのクラスの変更する方法が使えません。その場合は、プロジェクトにソースを加えた後、一度VCを終了させて、ソースがあるフォルダ内にある設定ファイル(???.aps, ???.ncb, ???.clw, ???.opt)を削除してしまいます。そのあと、もう一回プロジェクトファイル(???.dsw)をダブルクリックし、ClassWizardを開くと、もう一回clwファイルを作成するか聞かれるので、はいと答えます。これでちゃんと加えたクラスも認識されるようになります。

この方法は、いろいろやっていたら調子が悪くなったときにも有効です。

256色アイコンのパレットについて

256色アイコンでは、パレットの0番の色を黒(R=0,G=0,B=0)にしておかないと、透明色が正確に表示されないようです。(少なくともWin98SEの場合)

任意の太さで、色反転したフレームを描く方法

選択範囲を示すのに使う色反転の四角形はよく使いたくなる所ですが、線の太さをかえるとなると少し工夫が必要です。WindowsのペンオブジェクトにはNULLペン(何もしないペン)はあるのですが、ブラシオブジェクトには何もしないブラシがありません。そのため、次のようにします。

CPen pen(PS_SOLID, penwidth, RGB(255, 255, 255)); // ペンの太さpenwidthの白ペン
CBrush brush(RGB(0, 0, 0)); // 黒いブラシ
dc.SaveDC();
dc.SelectObject(&pen);
dc.SelectObject(&brush);
dc.SetROP2(R2_XORPEN); // XORで描く
dc.Rectangle(&rect);
dc.RestoreDC(-1);

こうすると、外側の線の部分は白(0xFFFFFF)とのXORで色が反転、内側は黒(0x000000)とのXORで何も変わらないようになります。

MRU(最近使ったファイル)を状況に応じて無効化する

SDI/MDIのプロジェクトを生成すると、デフォルトで作成される「最近使ったファイル」ですが、ある条件下ではファイルのオープンを禁止したいときなど、一時的にDisableにしたい場合もあるかと思いますが、普通にやってもうまくいきません。(少なくとも自分は)たとえば、ID_FILE_MRU_FILE1 - 16までのON_UPDATE_COMMAND_UIを登録してみるとか…

これをうまく回避する方法を見つけました。まず、「最近使ったファイル」を、メニューエディタでサブメニューに移動させます。こうなっているアプリケーションも結構多いので、そんなに違和感は無いと思います。

こういう構造にした場合、(最近のバージョンは直ったのかもしれませんが…)MFCにはポップアップメニューに対して呼ばれたON_UPDATE_COMMAND_UIの場合を考慮していないというバグがあります。(⇒参照)このサイトによると、この問題の修正方法としては、ID_FILE_MRU_FILE1に対するON_UPDATE_COMMAND_UIをクラスウィザードで生成し、その中でポップアップメニューに対して呼ばれたかどうかを判断し、もしそうでなければ親クラスのOnUpdateRecentFileMenuを呼ぶようにします。

このコードに少し手を加えて、もし「最近使ったファイル」をある条件でDisableにしたい場合、ポップアップメニュー自体をDisableにしてしまえばいいわけです。最終版のID_FILE_MRU_FILE1のON_UPDATE_COMMAND_UIハンドラは以下になります。

void CTestApp::OnUpdateFileMRU(CCmdUI* pCmdUI) 
{
	if (pCmdUI->m_pSubMenu != NULL) // updating a submenu?
	{
		pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
		MF_BYPOSITION | 
		(bEnable ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));

		return;
	}

	CWinApp::OnUpdateRecentFileMenu(pCmdUI);
	return;
}

bEnableを好きな条件に書き換えればOKです。ポップアップメニューに対しては、CCmdUI::Enable()は効果が無いことに注意してください。

GUIアプリケーションをコンソールアプリケーションに変更する

VC++で作成したダイアログアプリケーションやMDI・SDIアプリケーション内で"printf"などの標準出力に書き出す関数を呼び出しても、実際は何も起きません。VC++で、"コンソールアプリケーション"として生成した場合には、コンソールが割り当てられていますが、GUIアプリの場合、そのようなことが無いので何もおきないようです。ただ、GUIを持っているがコンソールから呼び出して、結果を標準出力に書き出すなど、ちょっとおかしなことをやりたい場合は困ってしまいます。この場合、無理矢理にコンソールアプリケーションにしてしまうことで、解決できます。

まず、VC++メニューのプロジェクト⇒設定を選択して、プロジェクトの設定ダイアログを表示します。

C/C++のタブを選択し、コンパイルオプションに_CONSOLEを追加します。

リンクのタブを選択し、プロジェクトオプションのsubsystemのところをconsoleに変えます。

適当な場所(CPPファイル内)に、_tmain関数を作成します。

extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	// WinMain形式に文字列を組み替える
	// 文字列数カウント
	int nCnt = 0;
	int nLen = 0;
	int nRes = 0;
	int i;

	for(i=1; i<argc; i++){
		nCnt += _tcslen(argv[i]);
	}
	nLen = nCnt + argc;
	LPTSTR lpCmdLine = new TCHAR[nLen];
	lpCmdLine[0] = _T('\0');
	nCnt = 0;
	for(i=1; i<argc; i++){
		_tcscpy(&lpCmdLine[nCnt], argv[i]);
		nCnt += _tcslen(argv[i]);
		if(i == argc - 1){
			lpCmdLine[nCnt] = _T('\0');
		}else{
			lpCmdLine[nCnt] = _T(' '); // スペース挿入
		}
		nCnt++;
	}

	nRes = AfxWinMain(::GetModuleHandle(NULL), NULL, lpCmdLine, SW_SHOW);
	delete[] lpCmdLine;
	return nRes;
}

はじめの方で引数をいじっているのは、WinApp用のコマンドライン引数(lpCmdLine)を作成するためです。GetCommandLine()で取得すると、実行ファイルのパスがはじめに入ってしまうので、それを避けるために無理矢理こうやっています。もし引数が必要ないなら、AfxWinMainにGetCommandLine()の返り値をそのまま渡してもかまいません。

これで、 無事にコンソールアプリになりました。なお、3D表示を有効にしている場合、デバッグビルドではInitInstance関数内で呼ばれるEnable3dControlsでアサートします。この関数は呼び出さないようにしておきます。

参考⇒codeguruの投稿


ここには、自分がいろいろ苦労したおもしろい技について、主に自分のために書き加えていきたいと思います。もし何かつまった場合は、codeguruも参考にしてください。問題の解決法がきっと見つかります。