MFC 実践プログラミング ヒント集
目次

Visual C++ & MFCとは /* 胸踊る冒険の始まり */

Visual C++は Microsoftの C/C++の統合開発環境です。

MFCは Visual C++で使うライブラリー集のようなものです。 InfoViewerで検索すると英文が表示されたので訳してみると、

「MFC(Microsoft Foundation Class Library)はWin32APIをサポートする Microsoft Windowsや他のプラットフォームで動作するアプリケーション をコーディングするためのアプリケーションワークフレーム(以降、ワークフレームと記す)です。 ワークフレーム(骨組み)はドキュメントやウィンドウ、ダイアログボックス、ツールバー等のような ありふれた(共通)オブジェクトを代表する多くのC++クラスのグループとして インプリメント(実行)されます。」

些か正確に訳せたか疑問ですが、遠からず当たっているでしょう(笑)。 まあ、実践ではこんな定義はどーでも良いことですね。

求められる知識 /* 挫折を乗り越えて */

さて、「Visual C++でアプリケーションを作るぞ!」と思っても、「おや?」と直ぐに Visual Basic と同じでないことに気付かれるでしょう。そう、Visual Basicと違って物すごく知識を自分に貯えて からではないとアプリケーションの作成が非常に難しい製品です。

主に必要とされる知識は、MFCについてです。
どのクラスがどう継承されていて、どのような機能を持っているか。 また、それを理解するにはC++(多少のWindowsプログラミング)の知識が不可欠です。 加えて、それを利用するにはどうしたら良いかや、 Visual C++の AppWizardが自動生成するコードを把握している必要があります。

MFCを利用して高度なアプリケーションは簡単に作成できるようになりましたが、反面、 沢山のことを知っていなければならなくなりました。

慌ててもしょうがありません。相手は可愛い顔(?)した巨大な怪物です。 気長にじっくりとその生態を調査して行きましょう。

AppWizard /* 新しい船出 */

AppWizardにより、自分の開発したいアプリケーションの大筋の性格が決定します。 大抵が後から手動で性格を変えることも十分に可能です。しかしながら、それなりの 経験がなければ非常に難しい作業となります。
特にタイトルバーに表示される[ステップ3/6]の複合ドキュメントのチェックは 良く考える必要があるでしょう。これらは後からの変更に非常に苦労します。 詳しくは、OLEのページのここをご覧ください。

ちなみに、一旦、開発環境(Visual Studio)を閉じて開発を再開するときは、[ファイル]-[ワークスペースを開く]メニューから始めます。

生成されるファイル /* 宝の地図 */

AppWizardにより生成されるファイルは、アプリケーションの種類によって異なります。 以下に特徴的なファイルを挙げます。xxxは開発者が 入力したプロジェクト名で、 クラス名の変更を行わなかった場合の名前です。

SDI
xxx.cpp xxx.hアプリケーションクラス CXxxApp
MainFrm.cpp MainFrm.hフレームクラス CMainFrame
xxxDoc.cpp xxxDoc.hドキュメントクラス CXxxDoc
xxxView.cpp xxxView.hビュークラス CXxxView

MDIはSDIの構成ファイルに次のファイルが加わります。
MDI
ChildFrm.cpp ChildFrm.hフレームクラス CChildFrame

コンテナをサポートするとMDIまたはSDIの構成ファイルに次のファイルが加わります。
コンテナ
CntrItem.cpp CntrItem.hコンテナアイテムクラス CXxxCntrItem

フルサーバをサポートするとMDIまたはSDIの構成ファイルに次のファイルが加わります。
フルサーバ
IpFrame.cpp IpFrame.h埋め込みフレームクラス CInPlaceFrame
SrvrItem.cpp SrvrItem.hサーバーアイテムクラス CXxxSrvrItem

コンテナサーバーをサポートすると上記のコンテナをサポートした場合とフルサーバをサポートした場合の 両方が加わります。

SDIもMDIもサポートしない場合(ダイアログ)は、次のファイルの構成です。
非SDI&非MDIサポート
Xxx.cpp Xxx.hアプリケーションクラス CXxxApp
XxxDlg XxxDlg.hダイアログクラス CXxxDlg

アプリケーションの開始 /* CXxxApp::InitInstance() */

EXE型である実行モジュールを作成した場合、 フレームワークではWinMainを記述しません。
WinMainはクラスライブラリによって提供され、アプリケーションの起動時に呼び出されます。 WinMainをカスタマイズするには、WinMainが呼び出すCWinAppのメンバ関数(例:InitInstance等)をオーバーライドして行います。

ワークフレームは最初にアプリケーションクラスのInitInstance関数を呼び出します
この関数を見ると、ウィンドウを作る準備やその他の機能を有効にするためのコードが生成されていることがわかると思います。
Windowsでは同じプログラムの複製を複数同時に実行することができます。 この関数はプログラムの複製が実行されるたびに(初回も含めて)実行されるインスタンスの初期化を行います。


ドキュメントとビュー /* 食べ物とお皿 */

ドキュメント-ビューアーキテクチャ。はじめは面食らってしまいます。
これは、データとデータ表現を分離するもののようです。データの加工を専らドキュメントクラスが、 データ表示を専らビュークラスが受け持ちます。ですから、例えばドキュメントクラス1つに、 2つの異なるビュークラスが結び付いて、1つのデータの見せ方を2通りにすることもできます。
AppWizardが生成するソースは、SDIかMDIです。SDIの場合は、ドキュメントクラスとビュークラスが 1つずつしかありません。MDIは、その組が複数ある感じです。
MDIですと、1子ウィンドウを生成する度にドキュメントクラス1つとそれに結びついたビュークラスが1つ できて行きます。

ドキュメント-ビューの関係が理解できていないと、次のようなコーディングをして 無理矢理ウィンドウを生成してしまいがちです(私がそうでした(笑))。これでは、特別な理由が無い限り MFCの恩恵に授かれませんね。
  1. CFrameWndクラスを継承した自分のクラスを作成します(CMyFrame)
  2. CMyFrame* pFrm = new CMyFrame;
    pFrm->Load(IDR_xxx); /* IDR_xxxは[Resource View]の[Menu]で定義します */
    pFrm->ShowWindow(SW_SHOW);
    pFrm->UpdateWindow();

どこでグローバルな変数を定義するか /* 頼もしい仲間AfxGetAppたち */



アプリケーション全体でユニークな情報を保持する変数の場合は、 アプリケーションクラス(CXxxAppクラス)の publicで定義します。
このクラスで定義した publicなメンバ変数やメンバ関数は、他のクラスから次の関数を通してアクセスします。
CXxxApp *pApp = (CXxxApp*)AfxGetApp();
pApp->m_xxx = 100;     /* アプリケーションクラスのメンバ(public)m_xxxやfun()にアクセスできます */
pApp->fun();


ドキュメント単位でユニークな情報を保持する変数の場合は、 ドキュメントクラス(CXxxDocクラス)の publicで定義します。
このクラスで定義した publicなメンバ変数やメンバ関数は、そのドキュメントクラスに結びついている ビュークラスからではお馴染みのGetDocument() を通してアクセスします。では、他のドキュメントクラスに結びついているビュークラスからは、 どうやってアクセスするのでしょうか。それには次のような関数を利用します。

サンプル1
POSITION pos = GetFirstDocPosition();
while (pos != NULL) { 
    CXxxDoc* pDoc = (CXxxDoc*)GetNextDoc(pos);
    .
    .
    }
これらの関数はドキュメントテンプレート(CDocTemplateクラス)のメンバで、 そのテンプレートに関連付けられているドキュメントのリストの最初のドキュメントの位置を GetFirstDocPosition関数で取得します。この POSITION値を GetNextDoc関数の引数に渡して、 テンプレートに関連付けられたドキュメントのリストを順次取得するものです。
では、ドキュメントテンプレートはどこから取得すれば良いのでしょうか?ソース中を探してみると アプリケーションクラスのInitInstance関数内に次のようなコーディングを見つけることができます。
MDIの場合
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
    IDR_MFCTYPE,
    RUNTIME_CLASS(CMfcDoc),
    RUNTIME_CLASS(CChildFrame),
    RUNTIME_CLASS(CMfcView));
AddDocTemplate(pDocTemplate);
MFCの継承図(あのA3のつるつるした紙)を眺めると、CMultiDocTemplateクラスは CDocTemplateクラスから 派生していることがわかります。ですので、他のクラス(ここではビュークラス)から参照できるように、 pDocTemplateのローカル宣言をやめて、アプリケーションクラスのpublicなメンバ変数(m_pDocTemplate)として定義し直します これにより、サンプル1のコーディングは具体的に次のようになるでしょう。
サンプル2
CXxxApp* pApp = (CXxxApp*)AfxGetApp();
POSITION pos = pApp->m_pDocTemplate->GetFirstDocPosition();
while (pos != NULL) { 
CXxxDoc* pDoc = (CXxxDoc*)pApp->m_pDocTemplate->GetNextDoc(pos);
    .
    .
    CString aaa = pDoc->GetTitle());    /* 例 ドキュメントのタイトル名を得る関数を発行する */
    CString bbb = pDoc->m_uservalue;    /* 例 ドキュメントクラスのメンバ変数にアクセスする */
    }

では、ドキュメントクラスのメンバをアプリケーションクラスからはアクセスする手段は?
上記と同じ手段でできると思います。(でも普通はそんなことする必要性は発生しないと思いますが・・)


ビュー単位でユニークな情報を保持する変数の場合は、CXxxViewクラスのpublicで定義します。
このクラスで定義した publicなメンバ変数やメンバ関数は、他のクラスからアクセスする手段はあるのでしょうか?
私は思い当たりません。


よくオーバーライドして使う関数 /* 性格を変える */


MFCのクラスにはオーバーライド可能な関数が多くあります。 よくオーバーライドして使う関数を列挙してその利用方法を示します。


Xxx::PreCreateWindow
ウィンドウが作成される前に CREATESTRUCT構造体を変更するために、このメンバ関数をオーバーライドします。

MDIの子ウィンドウ最大化
MDIの子ウィンドウをはじめから最大化して表示させたい場合は次のようにします。
BOOL CXxxView::PreCreateWindow(CREATESTRUCT& cs)  // ビュークラス
{
    cs.style |= (WS_MAXIMIZE | WS_VISIBLE);
    return CView::PreCreateWindow(cs);
}
この場合、何故か WS_VISIBLEが必要になようです。 なかなかこれに気が付かなくて多くの時間を費やしてしまったという苦い経験がありました(笑)。 ちなみに cs.styleの変更前の値は 0x40cf8000です。

ウィンドウを常に最上位に
拡張ウィンドウスタイルに WS_EX_TOPMOST というものがあります。
あなたのメインウィンドウを常に最上位に持ってくる(他のウィンドウが上に重ならない)ためには、 (MDIの場合は親の)フレームクラスに追加します。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)  // フレームクラス
{
    cs.dwExStyle |= WS_EX_TOPMOST;
    return CMDIFrameWnd::PreCreateWindow(cs);
}
その他、拡張ウィンドウスタイルには WS_EX_TRANSPARENT(背景の透明化) や WS_EX_ACCEPTFILES(ドラッグ&ドロップされたファイルを受け入れ可能)等があります。



CView::OnInitialUpdate
ビューが初期表示される前で、ドキュメントが最初にビューに関連付けられた後にフレームワークが呼び出します。
SDIとMDIで多少呼ばれ方が異なる点に注意が必要です。MDIでは新たに子ウィンドウが開くときに1度呼ばれるだけですが、 SDIでは[ファイル]-[開く]メニューの操作等の度に呼ばれます。1度きりの初期処理しか許さないロジックを書くとき工夫が必要です。

スクロールのサポート
CScrollViewクラスから派生した場合などは、AppWizardがこの関数内に次のコードを生成しています。
CSize sizeTotal;
// TODO: このビューのサイズの合計を計算します。
sizeTotal.cx = sizeTotal.cy = 100;
SetScrollSizes(MM_HIENGLISH, sizeTotal);
SetScrollSizes関数はマッピングモードとスクロールビューの全体の大きさ等を設定します。 sizeTotalは通常、ローカルで宣言されていては使い物にならないので CXxxViewクラスのメンバ変数に宣言し直します。



マッピングモード
マッピングモードには次のようなものがあります(一部)。

マッピングモード論理単位y軸の正方向
MM_TEXT1 ピクセル下方向
MM_HIMETRIC0.01mm上方向
MM_TWIPS1/1440インチ上方向
MM_HIENGLISH0.001インチ上方向
MM_LOMETRIC0.1mm上方向
MM_LOENGLISH0.01インチ上方向
※論理座標はページ座標と同じ意味です。
※デバイス座標はクライアント座標と同じ意味です。原点はウィンドウ領域の左上、単位はピクセルです。 つまり、MM_TEXTと同じ。
※1twip単位は1/20ポイント
※ポイントは計測単位で約1/72インチ
※1インチは約25.4mm。1cmは0.39インチ。
※1インチ当たりのピクセル数を知りたい場合は、次のメンバ関数を使用します。
CDC::GetDeviceCaps(LOGPIXELSX)
CDC::GetDeviceCaps(LOGPIXELSY)
※OLEコンテナをサポートする場合は MM_HIMETRICが便利のようです。



CView::OnPrepareDC
画面表示のために OnDraw関数が呼び出される前、 および印刷または印刷プレビューで各ページで OnPrint関数が呼び出される前に、 フレームワークが呼び出します。
マッピングモードの設定 pDC->SetMapMode(MM_xxx)をよくここに書きます。スクロールをサポートしてないのであれば、 OnInitialUpdate関数に書くよりここに書いた方が良いようです。



CScrollView::OnPrepareDC
CViewScrollクラスを基本クラスにしている場合は、AppWizardが記述した、
CScrollView::OnPerpareDC(pDC, pInfo);
より以降からコーディングを記述しなければならない
ようです。 これはスケールを変更するコーディングを行う場合に重要です。
※ただのCViewクラスを基本クラスにしている場合は、この限りではありません。



よく使う単純値型クラス /* 知っておくと超便利 */


CString
CStringオブジェクトは可変長の文字列から構成されます。CStringクラスは、BASIC 言語に類似した文法を使って、 CStringオブジェクトを操作する多くの関数や演算子を提供しています。 簡易化されたメモリ管理と合わせて、連結演算子や比較演算子により、 通常の文字配列よりも CStringオブジェクトのほうが扱いやすくなっています。
CStringクラスのお陰で、strcpyやstrcatそれにstrchr、sprintfと言った 標準関数は全然使う必要が無くなりました。詳しくは、InfoViewerを参照して下さい。

例:Format
sprintfがCスタイルの文字配列に書式化されたデータを設定するのと同じ方法で、 書式化されたデータを CStringに書き込みます。
short x = 1;
short y = 1;
CString cb;
cb.Format("(%d,%d)", x, y);    // cb="(1,1)"

LPCTSTR型との関係
LPCTSTR型はUnicodeを使用していなければ const char*(定数ポインタ)と同じです。 このあたりの意味がはっきりわからない方はここをご覧下さい。

Unicode
Unicodeとは。(InfoViewerからの引用です)

"ワイド文字"とは、2バイトの多言語対応文字コードのことです。 専門分野で使う記号や出版業界で採用している特殊文字なども含めて、 世界規模の現代コンピュータ業界で使用する文字はすべて、Unicode仕様に従えば、 ワイド文字として一律に表現することができます。 ワイド文字はすべて 16ビット幅に固定されているため、 ワイド文字を採用すると各国の文字セットを使ったプログラムが簡単になります。
Windows NTプラットフォームでは、MFCはワイド文字のエンコードに Unicode規格をサポートしています。 Windows 95では、Unicodeは現時点でサポートされていません。1997.4.1

LPCTSTR型をCStringオブジェクトへ変換するには、
CString(LPCTSTR lpsz)のコンストラクタを利用して簡単に行えます。
CString	str(lpsz);    // lpsz = "ABC"
CStringオブジェクトをLPCTSTR型へ変換するには、
単純にCStringオブジェクトをLPCTSTR型にキャストします。
但し、キャストによって得られた LPCTSTR型のポインタは、 CStringオブジェクトがスコープから外れるか、何らかの理由で内容が変化すると。 無効になることに注意しなければなりません。

LPTSTR型との関係
LPTSTR型はchar*(非定数ポインタ)と同じです。
CStringオブジェクトをLPTSTR型へ変換するには、
GetBufferとReleaseBufferを使います。
LPTSTR p = s.GetBuffer(1024);  // 例えばバッファを 1024バイト確保しておきます。
strcpy( p, "Hello" );          // pを介して CStringバッファに直接アクセスできるようになります。
s.ReleaseBuffer();             // 余分なメモリを解放します。pは無効になります。
但し、GetBufferとReleaseBufferの間で他のCString操作を行ってはいけません。
もし、内容の変更を行わないことが確かならば、GetBufferの引数は1でも良いです。つまり、ポインタを取得したい場合です。
LPTSTR p = s.GetBuffer(1);
    .
    . この間に p を使って見るだけならOKです。
    .
s.ReleaseBuffer();



CPoint
CPointクラスは、POINT構造体と同様のものです。 CPointオブジェクトやPOINT構造体を操作するメンバ関数を持っています。
関数の引数にPOINT構造体を使えるところであればどこでも使えます。



CRect
CRectクラスは、RECT構造体と同様のものです。 CRectオブジェクトやRECT構造体を操作するメンバ関数を持っています。
関数の引数にRECT構造体や、LPCRECT、LPRECTが渡せるところであればどこでも使えます。



CSize
CSizeクラスは、SIZE構造体と同様のものです。
関数の引数にSIZE構造体を使えるところであればどこでも使えます。
また、SIZE構造体のデータメンバはCSizeのデータメンバとしてもアクセスできます。


メニュー /* ご注文をどうぞ */

フローティングポップアップメニュー

お馴染みの右クリックで現れるメニューです。
[ResourceView]の[Menuの挿入]で表示したいメニューを作成します。
親のメニュー項目(垂れて出るメニュー項目ではないやつ)には適当な名称を入れ、 子のメニュー(垂れるメニュー項目)に目的のIDと名称を入れます。親のメニュー項目は実際には表示されません。
次にClassWizardの[メッセージマップ]でビュークラスかフレームクラスにWM_CONTEXTMENUをマップします(OnContextMenu関数)。 ビュークラスのマップとフレームクラスのマップは共存できます。
次にOnContextMenu関数に次のコーディングを追加します。
void CXxxFrame::OnContextMenu(CWnd* pWnd, CPoint point) 
    {
    CMenu    menu;
    menu.LoadMenu(IDR_MENU1);    /* IDR_MENU1はResourceViewで追加したメニュー */
    menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
    // DestroyMenu()はCMenuデストラクタから自動的に呼び出されます。
    }

ダイアログボックス /* 手間のかからないウィンドウ */



ダイアログクラス (手順1/2)

AppWizardで[ダイアログベース]を選択した場合、そのダイアログは自動的にクラスが作成されています。
しかし、[ダイアログベース]に限らず[SDI]でも[MDI]でも、新たにダイアログの作成([Resource View]で[Dialigの挿入])を行った場合は、 自動的にそのクラスは作成されません。必ずしもクラスの作成が必要だとは限りませんが、 大抵の場合、クラスの作成を行ったほうがコーディングが楽だと思います。

クラスの作成方法は、グラフィクエディタで新規のダイアログを表示しておいて、そのダイアログをダブルクリックします。 [基本クラス]は CDialigを選択します。(プロパティーシート(OLEで説明予定)の場合は別のクラスを指定します)



ダイアログデータメンバ (手順2/2)

ダイアログボックスを作成した後、[ClassWiard]の[メンバ変数]タブで、 各コントロールにデータメンバを対応させます。コントロールIDをクリックして[変数の追加]ボタンを選択し、メンバ変数名、カテゴリ、変数の種類を定義します。
これにより、各データメンバに直接アクセスすることができるので、 SetWindowTextやGetWindowTextと言ったAPIを使用しなくて済みます。
CXxxDlg    dlg;
dlg.m_strTel = "0852-12-3456";    // 例えばテキストボックスに。
if (dlg.DoModal() == IDOK)
    strChangeTel = dlg.m_strTel;
但し、ダイアログクラスのメンバ関数内から、 このデータメンバ変数に値をセットしても直ぐにコントロールにその内容は反映されません。
ダイアログクラスの内部からのアクセスで値を反映させたい場合は、 次のように GetDlgItemでコントロールIDからコントロールまたは子ウィンドウへのポインタを取得します。
CStatic*    p = (CStatic*)GetDlgItem(IDC_zzz);
p->SetWindowText("AAA");
例ではスタティックボックス(ラベル)の内容(キャプション)を変更しています。
CStaticの部分は目的のコントロールの種類に合わせて変更します。



モーダル

モーダルダイアログボックスは、ユーザーがダイアログボックスを閉じるまで他のウィンドウと対話できません。
DoModal()を呼ぶことで実現します。ダイアログボックスの動作終了時に、この関数から戻ります。



モードレス

モードレスダイアログボックスは、ユーザーがダイアログボックスを閉じなくても他のウィンドウと対話できます。
Create()を呼ぶことで実現します。パラメータにダイアログテンプレートリソースとして、テンプレート名かテンプレートID(例:IDD_DIALOG1)を指定します。Create()はダイアログボックスを作成後すぐに戻ります。
ダイアログボックスの表示は、ダイアログの[プロパティ]の[その他のスタイル]の[可視]をチェックしておけば、Create()の発行と同時に表示されます。 [可視]をチェックしていなければ、ShowWindowで表示させます。
ダイアログボックスを破棄する(消滅させる)ときは、DestroyWindow()を呼びます。
CXxxDlg    dlg;
dlg.Create(IDD_SORT_DIALOG);    // 例えば処理中のメッセージを表示。
fun_sort();	
dlg.DestroyWindow();
また、ダイアログの方から親ウィンドウに自分自身の終了を通知したい場合があります。
その場合は、ダイアログクラスからユーザメッセージをSendし、親ウィンドウクラスでそのメッセージを受信します。 そんなまどろっこしいことをせずに、ダイアログクラス内で親ウィンドウクラスのメンバ関数を呼び出して、終了を直接通知する方法もあります。 しかし面倒でもメッセージを飛ばして終了を通知する方が一般的だと思います。(但し、むやみやたらとメッセージを飛ばすことを推奨しているわけではありませんが)
ユーザメッセージの飛ばし方と受信方法はここを参照して下さい。



小技 ESCキー等を無効にしF5キーで入力待ち

ダイアログボックスにボタンを配置せずに、ファンクションキーの5番を押された場合に限りダイアログボックスを閉じる1つの方法です。 また、ESCキーとEnterキーの入力を無効にしています。
[ClassWiard]でPreTranslateMessageをインプリメントして次のようにします。
BOOL CAboutDlg::PreTranslateMessage(MSG* pMsg) 
{
    BOOL	fthrought = 0;
    if (pMsg->message == WM_KEYDOWN) {
        if (pMsg->wParam == 0xd || pMsg->wParam == 0x1b) {  // ENTER, ESC
            Beep(570,1000);
            fthrought = 1;
            }
        else if (pMsg->wParam == 0x74)                      // FP5
            PostMessage(WM_CLOSE);
        else
            Beep(570,1000);
        }

    if (!fthrought)
        return CDialog::PreTranslateMessage(pMsg);
    else
        return 0;
}



コントロール /* 知っていれば何でもないこと */

まずは、16ビット時代からあったコントロールについて見て行きましょう。
Visual Basicでは簡単に配置できて簡単に使えるのに、VC++ではなんだか面倒。そんなことは無いようです。 ポイントを知っていれば、非常に簡単に扱えます。



グループボックス

コントロールをグループ化するに使います。




ラジオボタン (ボタン型のラジオボタンも可能)

複数のラジオボタンを配置し、そのうち1つか選択させたく無い場合。
まず、グループボックスを配置し、その上に複数のラジオボタンを配置します。
複数のラジオボタンのうち、先頭のラジオボタンのみ[プロパティ]の[グループ]をチェックします。 残りのラジオボタンの[グループ]にはチェックを付けてはなりません。
すると、[ClassWizard]の[メンバ変数]のコントロールに定義したIDが現れます。このIDに[変数の追加]を行います。変数の型は int型です。
あとは、コーディングでこの変数に、グループ化されたラジオボタンのデフォルトにしたいラジオボタンのインデックス(0から始まります)を代入します。 また、ユーザによって選択されたラジオボタンのインデックスも、この変数を参照すれば得ることができます。
dlg.m_iRadio = 0;            /* ラジオボタンに m_iRadioと言う変数の追加を行ってあります */
int nResponse = dlg.DoModal();
if (nResponse == IDOK) {
    if (dlg.iRadio == 0)
        .
    else if (dlg.iRadio == 1)
        .
    }
それから、[プロパティ]の[スタイル]で[プッシュボタン型]を選択すれば、視覚的にボタン型でラジオボタンの動作が可能になります。




スタティックボックス

ユーザへの表示に使用するコントロールです。いわゆるラベルです。
ResouceViewで作成するとコントロールがデフォルトでIDC_STATICになっています。 このIDを変更することで、ClassWizardでメンバ変数を追加したり、GetDlgItemでポインタを取得したりできます。




エディットボックス

お馴染みのユーザの文字入力を受け付けるコントロールですね。 使い方はもうすっかりご存知だと思います(コーディング例)ので、ここではちょっと変わった使い方を紹介します。
それは、エディットボックスをスタティックボックスのようにユーザへの表示に使用する方法です。 まず、いつものように[ClassWizard]でIDに[変数の追加]を行います。 次に、エディットボックスの[プロパティ]の[一般]の[タブストップ]のチェックを外し、[スタイル]の[読み取り専用]のチェックを付けます。 好みにより[スタイル]の[境界線]のチェックを外します。
[タブストップ]のチェックを外しておかないと表示された文字が範囲指定された状態になってしまいます。




リストボックス

リストボックスの場合も、配置してからいつものように[ClassWizard]でIDに[変数の追加]を行います。
初期表示の内容は、次のようにコーディング上で行います。
CTXxxDlg dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK) {
    CString    str = dlg.m_strLst;    /* リストボックスに m_strLstと言う変数の追加を行ってあります */
    .
    .

BOOL CXxxDlg::OnInitDialog()
    .
    .
    CListBox*    plst = (CListBox*)GetDlgItem(IDC_LIST1);
    fstream      ifs( "d:\\data.lst", ios::in );  /* ファイルのエラー処理は省略しています */
    char    cb[100];
    while (ifs.eof() == 0) {
        ifs.getline(cb,80);
        plst->InsertString(-1, cb);
        }
    plst->SetCurSel(0);
    .
    .
CListBoxクラスへのポインタに GetItemDlgを使って指定したコントロール(例ではIDC_LIST1がエディットボックスのID)へのポインタをセットします。 それから、CListBoxクラスのメンバ関数の InstartStringを使って表示文字列を挿入し、SetCurSelで挿入した先頭の表示文字列を反転表示させています。
このコーディング例では d:\data.lstのファイルの内容をエディットボックスの初期表示にしています。

リストボックスで選択された内容は、IDに割り付けた変数(例ではdlg.m_strLst)に入っています。




コンボボックス

コンボボックスは3種類のスタイルを持ちます。標準、ドロップダウン、ドロップダウンリストです。各スタイルの特性を次に示します。
スタイルドロップダウンリストの状態新規文字列
標準常に垂れている許可
ドロップダウン↓ボックスで垂れる許可
ドロップダウンリストフォーカスが来ると垂れる禁止(リスト中から選択のみ)
スタイルは[スタイル]タブにある[タイプ](コンボボックス)から選択します。 予めセットしておきたい項目があれば[データ]タブの[リストボックスの入力項目]に記述しておきます。各項目の区切りはCtrlキーを押しながらEnterキーで行います(改行されます)。

次に、ドロップダウンの高さ(垂れ具合)を設定します。
グラフィックエディタのコンボボックスコントロールの絵の↓ボックスをクリックします。 すると枠が現れます。この枠を下方向に引っ張って垂れ具合を調整します。デフォルトのままだと高さが0なのでリスト項目が見えませんので、必ずこの操作を行いましょう。

次に、いつものようにコンボボックスコントロールに[変数の追加]を行います。
以上で完了です。
CTXxxDlg dlg;
dlg.m_combo1 = "aaa";
int nResponse = dlg.DoModal();
if (nResponse == IDOK) {
    CString    str = dlg.combo1;    /* コンボボックスに dlg.combo1と言う変数の追加を行ってあります */
    .
    .
また、プログラム中でリスト項目を変更した場合があります。その場合は次のようなコーディングをします。
void CXxxDlg::zzz() 
{
    .
    .
    CComboBox*    pcmb = (CComboBox*)GetDlgItem(IDC_COMBO1);
    pcmb->AddString("ABC");
これにより"ABC"と言う文字列がリスト項目に追加されます。ドロップダウンリストスタイルでも可能です。
この他に削除(DeleteString)等の操作もできます。詳細はInfoViewerを参照して下さいね。




タブコントロール

はじめは、タブコントロールとプロパティーシートは同じ物かと思いましたが、 全然違うものですね。コーディング方法も全く異なります。
ここでは、プロパティーシートと同じような動作をするタブコントロールを作成してみます。つまり、各タブに違うコントロール群を配置させます。

例では2つのタブを持っています。

(最後にm_pg1,m_pg2DestroyWindow()しなくちゃいけないかな? InfoViewerを読むとそんなことが書いてあるけど・・) コーディングは難しくないと思いますが、ダイアログのプロパティの設定をきちんとしてないとうまく動作しませんね。




プロパティシート

プロパティシートはモーダルとモードレスで外観とコーディング方法が多少異なります。
またウィザードプロパティページもメンバ関数1つの呼び出しで簡単に作成できます。



モーダルプロパティシート

モーダルプロパティシートは[OK][更新][キャンセル]([ヘルプ])のボタンがデフォルトで配置されます。

最も簡単なコーディングは次の通りです。
例では2つのタグ(ページ)を持っています。
CPropertySheet    sheet("設定");
CUserPage1    page1;        // CPropertyPageを継承
CUserPage2    page2;        // CPropertyPageを継承
sheet.AddPage(&page1);
sheet.AddPage(&page2);
sheet.DoModal();
このコーディングをする前に、まず[Resouce View]で次のようにダイアログを作成します。
  • "ページ"となる複数のダイアログを[Dialogの挿入]で作成します。それぞれのダイアログに好みのコントロールを配置します。
  • このそれぞれのダイアログのプロパティを変更します。
    [スタイル]タブにある[スタイル](コンボボックス)を[チャイルド]、[境界線](コンボボックス)を[なし]に変更します。 加えて[スタイル]タブの[タイトルバー]をチェックし、[その他のスタイル]タブの[無効]をチェックして下さい
    ※[一般]タブの[キャプション]がページのタブに表示される文字列となります。
  • このそれぞれのダイアログのクラスを作成します(ダイアログクラスを参照)。この時、必ず、基本クラスにCPropertyPageを指定します
    "ページ"になるダイアログ同士のサイズの関係は自由です。MFCが適当に調整してくれるようです。
  • 必要に応じて、"シート"となるダイアログに配置したコントロールに[変数の追加]を行って下さい。

話しをコーディングに戻しますが、CPropertyPageオブジェクトは、AddPageでプロパティシートのページリストに組み込まれます。

実践的なコーディングでは、 ClassWizardの[クラスの追加]で基本クラスをCPropertySheetにしたユーザクラスを追加します。 このメンバにCPropertyPageから派生したユーザクラス("ページ"となるダイアログクラス)を追加し、 AddPageをコンストラクタで済ませてしまうのが良いと思います。

CUserSheet    sheet("設定");
sheet.DoModal();

// 新規クラス
class CUserSheet : public CPropertySheet
{
     .
     .
public:
    CUserPage1    m_page1;    // "ページ"となるダイアログクラス
    CUserPage2    m_page2;    // "ページ"となるダイアログクラス
    .
    .

CUserSheet::CUserSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
        :CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
    AddPage(&m_page1);
    AddPage(&m_page2);
}

CUserSheet::CUserSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
        :CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
    AddPage(&m_page1);
    AddPage(&m_page2);
}
このままではプロパティシートの[更新]ボタンは選択不可になっています。 これを選択可能にするには"ページ"のSetModified(TRUE)を発行します。但し[更新]ボタンは全ての"ページ"で共有されています。 つまり、どこかの"ページ"でSetModified(TRUE)が発行されれば[更新]ボタンは選択可能になります。

ユーザが[更新]ボタンを選択したときの処理は、どれか1つの"ページ"のOnApply()で行います。 複数の"ページ"でOnApplyをインプリメントしてはいけません。[更新]ボタンが共有されるように、OnApplyも全ての"シート"で共有されます。
OnApplyはClassWizardでインプリメントします。




ウィザードプロパティシート

ウィザードプロパティシートを作成する場合は、上記の手順のコーディングに少々、関数を追加するだけです。
SetWizardMode()をDoModalの前に発行します。これにより外観が、"ページ"のタブがなくなりボタンが [次へ][戻る][キャンセル]に変わります。
[戻る]ボタンを[完了]ボタンに変更するにはSetWizardButtons(PSWIZB_BACK|PSWIZB_FINISH)を発行します。
sheet.SetWizardMode();
if (sheet.DoModal() == ID_WIZFINISH) {    // [完了]ボタンを押した場合、ID_WIZFINISHが返ります
    .
    .
    }



モードレスプロパティシート

モードレスプロパティシートはデフォルトでボタンは配置されません。(必要ならば自分で作成しなければなりません)

初めに「モーダルプロパティシート」の「実践的なコーディング」のところで説明したような プロパティシートのユーザクラスのコンストラクタを少々変更します。
/* コメントアウト
CUserSheet::CCUserSheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
}
*/

//CUserSheet::CUserSheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
//	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
CUserSheet::CUserSheet(CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet("xxx", pParentWnd, iSelectPage)
{
    AddPage(&m_page1);
    AddPage(&m_page2);
}
CPropertySheetの第1パラメータに指定する名前はモードレスプロパティシートでは役に立たなくなってしまうので外してしまいます。
そして、ClassWizardでPostNcDestroyをインプリメントして次のコーディングを追加します。
void CUserSheet::PostNcDestroy()
{
    CPropertySheet::PostNcDestroy();
    delete this;
}
これでプロパティシートが終了するときに、自分自身を消滅させます。

次にミニフレームウィンドウを継承するユーザクラスを作成します。
ClassWizardの[クラスの追加]で基本クラスをCMiniFrameWndを選択して作成します(例ではCUserMiniFrameと命名)。 CUserSheetクラスをCUserMiniFrameのメンバ変数にします。 また、初めはCUserMiniFrameのコンストラクタはprotectedで生成されているので、これをpublicに変更します。
class CUserMiniFrame : public CMiniFrameWnd
{
    DECLARE_DYNCREATE(CUserMiniFrame)
//protected:    /* publicに変更する */
public:
    CUserMiniFrame();        // 動的生成に使用されるプロテクト コンストラクタ。

// アトリビュート
public:
    CUserSheet*    m_psheet;
続いて、ClassWizardでWM_CREATEをインプリメントし、次のようなコーディングを追加します。
int CUserMiniFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMiniFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO: この位置に固有の作成用コードを追加してください

    m_psheet = new CUserSheet(this);
    if (!m_psheet->Create(this, WS_CHILD | WS_VISIBLE, 0)) {
        delete m_psheet;  // エラー発生時
    }
    /* // ミニフレームをプロパティーシートの大きさに合わせて
       // 自動的にリザイズさせたい場合はこのブロックを有効にして下さい。
    else {
        CRect rcClient, rcWindow;
        m_psheet->GetWindowRect(rcClient);
        rcWindow = rcClient;
        CalcWindowRect(rcWindow);
        SetWindowPos(NULL, rcWindow.left, rcWindow.top,
                     rcWindow.Width(), rcWindow.Height(),
                     SWP_NOZORDER | SWP_NOACTIVATE);
        m_psheet->SetWindowPos(NULL, 0, 0,
                               rcClient.Width(), rcClient.Height(),
                               SWP_NOZORDER | SWP_NOACTIVATE);
        }
    */

    return(0);
}
これでミニフレームウィンドウがCreateされるときに、プロパティシートクラスを生成しています。

さて、モードレスプロパティシートが完成しました。後は表示させるだけです。 次のようなコーディングでミニフレームウィンドウにくっ付いたプロパティシートが表示されます。
// 親のフレームウィンドウクラスで定義で・・
public:
    CUserMiniFrame*    m_pFrame;
    .
    .


// モードレスプロパティシートを呼び出したいメッセージハンドラ関数内で・・
m_pFrame = new CUserMiniFrame;
if (!m_pFrame->Create(NULL, "プロパティシートのタイトルになります",
                      WS_POPUP | WS_CAPTION | WS_SYSMENU /* | WS_VISIBLE ここに書くと一瞬残像がでるのでダメ */,
                      CRect(0,0,0,0) /* 後で自動的にリサイズさせない場合は必ず適当なサイズをセットして下さい */,
                      this)) {
    MessageBox("Error!");
    }
m_pFrame->ShowWindow(SW_SHOW);
ウィンドウ破壊時のm_pFrameのdeleteは不要です。



Windows95共通コントロール /* 知っていれば何でもないこと II */

単品でも使えますが、コントロールをダイアログに取り込んで利用するのがカッチョイイと思います。 前半で単品で使う方法を示します。後半でカッチョイイ方法を示します。



コモンファイルダイアログボックス (ファイルの指定)

Windows95でお馴染みのファイル操作のダイアログボックスですね。
あの画面を表示するのは簡単です。
CFileDialog    dlgFile(TRUE, ".mdb", NULL,
                       OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
                       "Access Files (*.mdb)|*.mdb|All Files (*.*)|*.*||" );
if (dlgFile.DoModal() == IDOK)
    CString  str = dlgFile.GetPathName(); /* ユーザが選択したファイル名(パス付き) */
CFileDialogのコンストラクタで(Createで構築する場合も同様に)第1パラメータをTRUEにすると[ファイルを開く]ダイアログボックスを構築し、 FALSEにすると[ファイル名を付けて保存]ダイアログボックスを構築します。 などなどパラメータの詳細についてはInfoViewerを参照して下さい。
第4パラメータのOFN_xxxについては次の機会に和訳を載せたいと思っています。




コモンカラーダイアログボックス (色の設定)

Windows95でお馴染みの色を設定するダイアログボックスですね。
COLORREF    m_clrText = RGB(0xff,0,0);  /* 赤を初期値にしました */
CColorDialog dlgColor(m_clrText);
if (dlgColor.DoModal() == IDOK) {
    m_clrText = dlgColor.GetColor();    /* ユーザが選択したRGB値 */
    }




コモンフォントダイアログボックス (フォントの選択)

Windows95でお馴染みのフォントを選択するダイアログボックスですね。
次の例は画面用のフォントを選択しています。
CFont* pFont = GetFont(); LOGFONT lf; if (pFont != NULL) pFont->GetObject(sizeof(LOGFONT), &lf); else ::GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &lf); CFontDialog dlg(&lf, CF_SCREENFONTS|CF_INITTOLOGFONTSTRUCT); if (dlg.DoModal() == IDOK) { m_font.DeleteObject(); if (m_font.CreateFontIndirect(&lf)) SetFont(&m_font); /* ユーザが選択したフォントに切り替えます */ }


コモンダイアログボックスのネスト

2種類の方法があるようです。次に示す方法のうち前者は、見た目、あなたが作成したダイアログの中にコモンダイアログが埋め込まれた形になります。 後者は、コモンダイアログのテンプレートを直接編集(一旦コピーしますが)するものです。
前者の方法がうまく行かない場合で、後に記述するコモンダイアログの種類であれば後者の方法が取れます。



コモンファイルダイアログボックス タイプの場合

ダイアログは次のような呼び出しで表示できるでしょう。
CXxxFileDialog dlg(TRUE);
dlg.DoModal();


コモンプリントダイアログボックス タイプの場合

コモンプリントセットアップダイアログボックスの場合でも同様ですが、 あなたが作成したダイアログに見た目、埋め込むまれるような作り方はできないようです(現在までの調査の結果)

ダイアログは次のような呼び出しで表示できるでしょう。
CXxxPrintDialog dlg(FALSE);
dlg.DoModal();
第1パラメータがFALSEになっているはプリンタダイアログを表示させたいからです。


コントロールを持つビュー /* MDIやSDIのクライアント領域にダイアログが張り付いた感じ */

MDIやSDIのクライアントにResourceViewのDialogで配置したダイアログを貼り付けます。
この方法ですと、コントロールのクラスをCreateしてSetWindowPosで配置してやるよりずっと楽ですね。 但し、ウィンドウのサイズを変更しても各コントロールの配置の間隔は変化しませんが・・

AppWizardからですと最後の[ステップ6/6]でビュークラスの基本クラスをCFormViewに変更するだけで完了です。

CFormViewを基本クラスにして独自のビューを新たに追加する場合は、 ダイアログのプロパティは次のようにします。

[スタイル]タブにある[スタイル](コンボボックス)を[チャイルド]、[境界線](コンボボックス)を[なし]に変更します。 加えて[スタイル]の[タイトルバー]と[その他のスタイル]タブの[可視]のチェックを外します。

コントロールバー /* さまざまなバー */


ダイアログバー

ダイアログバーとはこんなもの(図の中央部)です。ツールバーとは違います。



ダイアログバーは子ウィンドウです。デザインはグラフィックエディタで行うので、好みのコントロールを配置できます。
では、早速、作成方法です。

[Resource View]で[Dialogの挿入]を選択してコントロールを配置します。
次にダイアログのプロパティを変更します。必ず、[スタイル]タブにある[スタイル](コンボボックス)を[チャイルド]、[境界線](コンボボックス)を[なし]に変更します。 加えて[その他のスタイル]タブの[可視]のチェックが外れていることを確認します。絶対![可視]のチェックを外して下さい!(実行時にトラップしますから)
ダイアログクラスは作成しなくて良いです。作っても効果がありません。理由は以下を読み続けて頂くとわかると思います。
次に(親ウィンドウの)フレームクラスにCDialogBarクラスのメンバ変数を宣言します。
class CMainFrame : public CMDIFrameWnd
{
    .
    .
protected:
    CDialogBar    m_dlgbar;
    .
    .
次に親ウィンドウのフレームクラスのOnCreateでCreateします。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    .
    .
    if (!m_dlgbar.Create(this, IDD_DIALOG1, WS_VISIBLE|CBRS_TOP, AFX_IDW_TOOLBAR)) {
        TRACE("Failed to create dialogbar\n");
        return -1;        // 作成に失敗
	}

    // TODO: もしツール チップスが必要ない場合、ここを削除してください。
    m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
        CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
    .
    .
第2パラメータには先程作成したダイアログID(例ではIDD_DIALOG1)を、第3パラメータにはダイアログバーの配置スタイルを、 第3パラメータには0xe800〜0xe820の範囲の値を指定します。
以上で完了です。

しかし、コントロールにボタンを配置した場合、このままではそのボタンは常にディセーブルのままとなります。 これを回避するには手動でメッセージマップとイベントハンドラ関数を追加します。
例の如く手動で追加するのでAFX_の外側に記述します。(例ではボタンのIDをIDC_MYBUTTON1、関数名をOnMyButton1にしました)
// [MainFrm.h内]
    .
    .
// 生成されたメッセージ マップ関数
protected:
	//{{AFX_MSG(CMainFrame)
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
	//}}AFX_MSG
	afx_msg void OnMyButton1();
	DECLARE_MESSAGE_MAP()


// [MainFrm.cpp内]
    .
    .
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
    ON_WM_CREATE()
    //}}AFX_MSG_MAP
    ON_COMMAND(IDC_MYBUTTON1, OnMyButton1)
END_MESSAGE_MAP()
    .
    .


void CMainFrame::OnMyButton1()
{ 
    .
    .
}
また、ボタンのディセーブルの制御がしたければ、次のメッセージマップとイベントハンドラ関数を追加します。
afx_msg void OnUpdateXxx(CCmdUI* pCmdUI);

ON_UPDATE_COMMAND_UI(DC_XXX, OnUpdateXxx)

void CMainFrame::OnUpdateXxx(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(TRUE or FALSE);
}
また、エディトコントロール等に文字を設定したり取得したりしたい場合は、次のような記述で対応します。
CDialogBar* pBar = (CDialogBar*)AfxGetApp()->m_pMainWnd->GetDlgItem(AFX_IDW_TOOLBAR);
pBar->SetDlgItemText(IDC_EDIT1, "AAA");

char cb[101];   
pBar->GetDlgItemText(IDC_EDIT1, cb, 100);

以下はCreateのパラメータの補足です。
第2パラメータのCBRS_xxxは次の意味があります。
CBRS_TOPコントロールバーをフレーム ウィンドウの上辺に揃えます
CBRS_BOTTOMコントロールバーをフレーム ウィンドウの下辺に揃えます
CBRS_NOALIGN親ウィンドウのサイズが変更されてもコントロールバーは再配置されません
CBRS_LEFTコントロールバーをフレーム ウィンドウの左辺に揃えます
CBRS_RIGHTコントロールバーをフレーム ウィンドウの右辺に揃えます
第3パラメータについてはちょっとこの辺、私は理解不足ですが、Afxres.hに次の記述がありますので参考にして下さい。
// Standard control bars (IDW = window ID)
#define AFX_IDW_CONTROLBAR_FIRST        0xE800
#define AFX_IDW_CONTROLBAR_LAST         0xE8FF

#define AFX_IDW_TOOLBAR                 0xE800  // main Toolbar for window
#define AFX_IDW_STATUS_BAR              0xE801  // Status bar window
#define AFX_IDW_PREVIEW_BAR             0xE802  // PrintPreview Dialog Bar
#define AFX_IDW_RESIZE_BAR              0xE803  // OLE in-place resize bar

// Note: If your application supports docking toolbars, you should
//  not use the following IDs for your own toolbars.  The IDs chosen
//  are at the top of the first 32 such that the bars will be hidden
//  while in print preview mode, and are not likely to conflict with
//  IDs your application may have used succesfully in the past.

#define AFX_IDW_DOCKBAR_TOP             0xE81B
#define AFX_IDW_DOCKBAR_LEFT            0xE81C
#define AFX_IDW_DOCKBAR_RIGHT           0xE81D
#define AFX_IDW_DOCKBAR_BOTTOM          0xE81E
#define AFX_IDW_DOCKBAR_FLOAT           0xE81F

// Macro for mapping standard control bars to bitmask (limit of 32)
#define AFX_CONTROLBAR_MASK(nIDC)   (1L << (nIDC - AFX_IDW_CONTROLBAR_FIRST))


ツールバー

ツールバーは悩みなく利用されていると思います。グラフィックエディタの[ToolBar]の編集だけで完了しますね。 [ツールバーボタンプロパティ]の[ID]には、メニュー項目かキーボードアクセラレータで定義したIDを割り付けますね。

では、メニュー項目にもキーボードアクセラレータにも定義されていないIDを割り付けた場合はどうするのでしょうか。

ダイアログバーの説明をお読みになかれた方には、もうお分かりかも知れませんね。
ツールバーもボタンですから、これを利用するにはメッセージマップとイベントハンドラ関数を手動で追加します。 やり方はダイアログバーのこのあたりを参考にして下さい。

全く蛇足のなにものでもありませんが、更新コマンドUIメッセージハンドラ(CCmdUI)でメニューと同様にツールバーも表示状態が変化します。
void CXxxView::OnUpdateZzz(CCmdUI* pCmdUI) 
{
    // TODO: この位置に command update UI ハンドラ用のコードを追加してください
    // nCheck 設定するチェック状態を指定します。0 はチェックなし(アップ)、1 はチェック付き(ダウン)、2 は不定(明るい霞)です。
    // pCmdUI-&gtSetCheck(nCheck);
    // bOn アイテムを有効(通常)にするには TRUE を、無効(霞)にするには FALSE を指定します。
    // pCmdUI-&gtEnable(bOn);
}

※ツールバーボタンの更新コマンドUIメッセージハンドラは、メッセージハンドラはアプリケーションのアイドル処理中にも呼び出されるようなので、 継続的に更新されます。


グラフィックエディタの[ToolBar]を使用せずにツールバーを実装する方法があります。 但し、[Bitmap]のIDR_MAINFRAMEのtoolbar.bmpは用意されていなければなりません。
MainFrm.cppに次のような構造体に値を与えます。値の順番はtoolbar.bmpの絵の並びと合っていなければなりません。 ID_SEPARATORは順番と関係ありませんので自由に挿入できます
static UINT BASED_CODE buttons[] =
{
    ID_FILE_NEW,
    ID_FILE_OPEN,
    ID_FILE_SAVE,
        ID_SEPARATOR,
    ID_EDIT_CUT,
    ID_EDIT_COPY,
    ID_EDIT_PASTE,
        ID_SEPARATOR,
    ID_FILE_PRINT,
    ID_APP_ABOUT,
};
同じくMainFrm.cppのOnCreateのツールバーの生成部分を次のように修正します。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    .
    .
if (!m_wndToolBar.Create(this) ||
    !m_wndToolBar.LoadBitmap(IDR_MAINFRAME) ||
    !m_wndToolBar.SetButtons(buttons, sizeof(buttons)/sizeof(UINT)))
    {
        TRACE0("Failed to create toolbar\n");
        return -1;    // 作成に失敗
    }
何もこんな不便なことを紹介しなくてもいいじゃないかと思われるかも知れませんが、 ID_SEPARATORの挿入とCToolBarクラスを継承した自家製クラスを作成することで、 ツールバーの中にコントロールを配置することができます。 見た目上、ダイアログバーとツールバーを合わせたコントロールバーになります。
詳細は、またいつか・・ね(^_^;


ステータスバー

ステータスバーはメッセージ区画状態インジケータ区画にわかれています。 メッセージ区画には[ResourceView]の[Menu]あるいは[String Table]で定義したキャプションが表示されています。 状態インジケータ区画はCapsLockキーやNumLockキー等の状態が表示されています。

ステータスバーの更新コマンドUIメッセージハンドラも、 アプリケーションのアイドル処理中に呼び出されるので継続的に更新されます。 ですからリアルタイムに表示する必要があるステータスを表示したい場合に有効があります。

表示内容を変更するにはMainFrm.cppに次のような修正を行います。
static UINT indicators[] =
{
    ID_SEPARATOR,           // ステータス ライン インジケータ
    ID_INDICATOR_TEXT,      // 追加しました(ユーザ定義)
    ID_SEPARATOR,           // 追加しました(システム定義)
    ID_INDICATOR_KANA,
    ID_INDICATOR_CAPS,
    ID_INDICATOR_NUM,
    ID_INDICATOR_SCRL,
    ID_INDICATOR_MOUSE,     // 追加しました(ユーザ定義)
};
最初のID_SEPARATORはメッセージ区画(平坦表示)を示しています。それ以降は状態インジケータ区画(くぼみ表示)になります。
追加できるIDはシステムが用意している次のものか、ユーザが[ResourceView]の[String Table]で追加したものです。 例ではユーザ定義のID_INDICATOR_TEXTとID_INDICATOR_MOUSEとシステム定義のID_SEPARATORをもう一つ、追加しています。
[Afxres.h内]
// Mode indicators in status bar - these are routed like commands
#define ID_INDICATOR_EXT                0xE700  // extended selection indicator
#define ID_INDICATOR_CAPS               0xE701  // cap lock indicator
#define ID_INDICATOR_NUM                0xE702  // num lock indicator
#define ID_INDICATOR_SCRL               0xE703  // scroll lock indicator
#define ID_INDICATOR_OVR                0xE704  // overtype mode indicator
#define ID_INDICATOR_REC                0xE705  // record mode indicator
#define ID_INDICATOR_KANA               0xE706  // kana lock indicator

#define ID_SEPARATOR                    0   // special separator value
ここまでのコーディングで8つに区分けされたステータスバーが現れます。
追加したID_SEPARATORの区画は無表示のくぼみ、ID_INDICATOR_TEXTとID_INDICATOR_MOUSEの区画は [String Table]で定義した[キャプチャ]の内容がくぼみで表示されます。

次に、表示文字や表示状態(非表示・くぼみ・突起)を変更する方法を示します。
ユーザ定義のIDを使用しているものはメッセージマップとイベントハンドラ関数を手動で追加します。 これで表示状態が制御できます。
[MainFrm.h内]
    .
    .
// 生成されたメッセージ マップ関数
protected:
    //{{AFX_MSG(CMainFrame)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
        // メモ - ClassWizard はこの位置にメンバ関数を追加または削除します。
        //        この位置に生成されるコードを編集しないでください。
    //}}AFX_MSG
    afx_msg void OnUpdateText(CCmdUI* pCmdUI);
    DECLARE_MESSAGE_MAP()
};


[MainFrm.cpp内]
    .
    .
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame)
        // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
        //        この位置に生成されるコードを編集しないでください。
    ON_WM_CREATE()
    //}}AFX_MSG_MAP
    ON_UPDATE_COMMAND_UI(ID_INDICATOR_TEXT, OnUpdateText)
END_MESSAGE_MAP()

void CMainFrame::OnUpdateText(CCmdUI* pCmdUI)
{
        // pCmdUI->Enable(TRUE or FALSE)    /* 文字の表示と非表示 */
        // 次の例はShiftキー押すと表示されます
	// pCmdUI->Enable(::GetKeyState(VK_SHIFT) < 0);

	// pCmdUI->SetCheck(0);  /* くぼみ */
	// pCmdUI->SetCheck(1);  /* 突起 */
	// pCmdUI->SetCheck(2);  /* 1と同じ */
}
次は表示文字を変更します。 例ではメッセージ区画のID_SEPARATORと追加したID_SEPARATORの区画とID_INDICATOR_MOUSEに、リアルタイムにマウスポインタの位置を表示しています。
表示文字はSetPaneTextで変更します。
void CXxxView::OnMouseMove(UINT nFlags, CPoint point)  // クラスがフレームクラスでないことに注目
{
	CMainFrame* pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;
	CStatusBar* pStatusBar = (CStatusBar*)pFrame->GetDescendantWindow(AFX_IDW_STATUS_BAR);
	//CStatusBar* pStatusBar = &pFrame->m_wndStatusBar;  // NG △ m_wndStatusBarがprotectedだから。publicに変更すればOKになる。

	CString cb; cb.Format("%d,%d", point.x, point.y);

	pStatusBar->SetPaneText(0, cb);    /* メッセージ区画(インデックスは0番) */
	pStatusBar->SetPaneText(2, cb);    /* 後から追加したID_SEPARATORはインデックスが2番目だから */

	int nIndex = pStatusBar->CommandToIndex(ID_INDICATOR_MOUSE);
	pStatusBar->SetPaneText(nIndex, cb);
}
お気付きのようにID_SEPARATORで追加を行うと構造体との位置とSetPaneTextの第1パラメータを一致させなければなりません。 ですから、メッセージマップの追加と言う作業がありますが、ユーザ定義のIDを作成した方がメンテナンスの面で良いと思います。

最後にメッセージ区画にフレームワークによる自動的なメニュープロンプトの表示をやめる方法を示します。
CMainFrameクラスのOnCreateのステータスバーの生成部分に次のようにパラメータを追加します。
if (!m_wndStatusBar.Create(this, WS_CHILD|WS_VISIBLE|CBRS_BOTTOM, ID_USER_STATUS_BAR) ||
    !m_wndStatusBar.SetIndicators(indicators,
      sizeof(indicators)/sizeof(UINT)))
    {
        TRACE0("Failed to create status bar\n");
        return -1;      // 作成に失敗
    }
ID_USER_STATUS_BARは[String Table]で定義します。 デフォルトではAFX_IDW_STATUS_BARが使用されるので、これを置き換えるだけです。
勿論、この場合はソース中のAFX_IDW_STATUS_BARをID_USER_STATUS_BARに置き換えて利用します。


表示と非表示

ツールバーやステータスバーの表示と非表示の切り替えは、標準的にAppWizardによりメニューに追加されます。 しかし、後から追加したこれらのコントロールバーについては、コードを後から追加しなければならない場合があります。
次にツールバーを例にしたコーディング例を示します。
例ではビュークラスに記述していますが、メインフレームクラスに追加するのが一般的だと思います。
void CXxxView::OnZzz() 
{
    CMainFrame* pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;
    CStatusBar* pStatusBar = (CStatusBar*)pFrame->GetDescendantWindow(AFX_IDW_STATUS_BAR);

    pStatusBar->ShowWindow((pStatusBar->GetStyle() & WS_VISIBLE) == 0);
    pFrame->RecalcLayout();    // CFrameWnd オブジェクトのコントロール バーを再配置します。
}


動的なサブクラス化 /* メッセージがウィンドウに送られる前にゲット */

サブクラス化するとウィンドウやコントロールの詳細な制御ができるようになります。

ボタンにビットマップ表示
ResourceViewによりダイアログに貼り付けたボタンにビットマップを表示ます。
そのダイアログのクラスで次のようにメンバ変数を定義します。
private:
    CBitmapButton    m_btn;
ダイアログの場合はOnInitDialog()で、ビューの場合はOnInitialUpdate()で SubclassDlgItemをコールしサブクラス化します。例ではダイアログの場合で示します。ビューの場合も同様にします。
BOOL CXxxDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
    if (!m_btn.SubclassDlgItem(IDC_BUTTON1, this))  // ビューの場合は m_btn.m_hWnd == NULLが
        return(FALSE);                              // 成り立つときのみコールします。

    m_btn.LoadBitmaps("DownBMP","UpBMP");
    m_btn.SizeToContent();
    .
    .
}
SubclassDlgItemの第1パラメータにはサブクラス化するコントロールIDを、 第2パラメータにはそのコントロールの親ウィンドウを指定します。

LoadBitmapsはResourceViewのBitmapで作成しておいた"DownBMP"と"UpBMP"というリソース名のビットマップです。 ボタンのダウン状態とアップ状態のビットマップを指定しています。 リソース名はResourceViewのBitmapでIDのところでダブルクォーテーションで括った名称を記述します。 別に、リソース名でなくてもリソースIDでもかまいません。LoadBitmapsは両方に対応しています。 詳しくはInfoViewerをご参照下さい。

SizeToContentはビットマップの大きさに合わせてコントロールのサイズを調整してくれる便利なメンバ関数です。

ビットマップを表示するボタンの[プロパティ]で、[スタイル]タブの[オーナー描画]にチェックを入れておきます。 または、LoadBitmapsをコールする直前に、
m_btn.SetButtonStyle(BS_PUSHBUTTON | BS_OWNERDRAW);
をコールしておきます。

アプリケーションの終了 /* メッセージループを終了させる */

アプリケーションのリターンコードを変更する
アプリケーションクラスにExitInstance関数をオーバーライドします。
デフォルトだと、
    return CWinApp::ExitInstance();
が返値になっています。 この行のreturn命令を消して、その下に新たにreturn命令を記述してリターンコードを返すようにします。

みなみに、この関数はワークフレームから呼び出されるべきものですから、他から呼んではいけません。


メニュー以外から終了させる方法
アプリケーションの終了は[ファイル]-[アプリケーションの終了]を選べば良いのですが、 例えば、マウスのダブルクリックでアプリケーションを終了させたい(そんな奴はいないと思うが例だから)場合、 とても悩んでしまいます。

アプリケーションクラスのInitInstance()内の
CMainFrame* pMainFrame = new CMainFrame;
の pMainFrameを public(m_pMainFrame)にして、ビュークラスで、
delete m_pMainFrame;  // NG ×
などとやったら、やっぱりヤバイと思います。 それでWindowsプログラミングの経験で思い付くのが「WM_CLOSEのポスト」。
CXccApp* pApp = (CXxxApp*)AfxGetApp();
pApp->m_pMainFrame->PostMessage(WM_CLOSE);
これなら、アプリケーションクラスのExitInstance関数も終了前に呼ばれるので良いようです。 但し、SendMessageを使った方が良いのか良く分かりません。 また、親ウィンドウをわざわざグローバルにする必要も無いかもしれませんね。 APIまたは関数で親ウィンドウハンドルくらい取って来れそうですし。(GetParentOwner()とか)

もうちょっと考えて見ると、WM_CLOSEは DestroyWindow()を発行する引き金ですから、
GetParentOwner()->DestroyWindow();
これでも良いのかなーって。どうなんでしょうね?


タイマ /* 一定時間毎に何かさせたい */

例えば、1秒毎に画面を再描画させたいとか、1時間毎にメッセージを表示したいとか、 そのような場合にタイマ機能を使います。
方法はいくつかのやり方があります。取りあえず、最も簡単な(一般的かな?)コーディングを次に示します。

SetTimerメンバ関数とOnTimerハンドラ
この方法は、アプリケーションクラス内では使用できませんが、その他のクラスでは簡単に使用できます。
例えば、ダイアログの OnInitDialog()内で、SetTimerメンバ関数を呼びます。これで指定した時間間隔でWM_TIMERメッセージが発行されるようになります。 次に[ClassWizard]から WM_TIMERメッセージを選択し OnTimerハンドラを作成します。先ほどの WM_TIMERはこの OnTimerで捕まえることができるようになります。
CXxxDlg::OnInitDialog()
    {
    .
    .
    SetTimer(1, 1000, NULL);
    .
    .
}

void CXxxDlg::OnTimer(UINT nIDEvent) 
{
    m_cnttime++;
    if ( m_cnttime >= 3600 ) {
        Beep(560,1000);
        m_cnttime = 0;
        ShowWindow(SW_SHOWNORMAL);
    }

    CDialog::OnTimer(nIDEvent);
}
SetTimerの第1パラメータは OnTimerの nIDEventに渡るタイマの識別子です。ですから、複数のタイマをサポートできます。 第2パラメータは WM_TIMERを発行する間隔をms単位で指定します。この例では1秒間隔になっています。 第3パラメータは NULLで良いと思います。
OnTimerの中身は私が適当にコーディングしたものです。1時間毎に、ピッと鳴らして、このアプリケーションのウィンドウを復元しています。 また、1時間は3,600,000msなのでこれでは、SetTimerの第2パラメータの UINT型に指定できません。 そこで、1秒毎に変数をインクリメントして1時間が経過するのを監視しています。

WM_TIMERの発行を停止させるには、KillTimer(int nIDEvent)メンバ関数を使用します。
nIDEventに停止させたいタイマの識別子を指定します。

※Windowsのタイマは100ms以下を指定すると精度が低下するそうです。

レジストリとINIファイル /* 覚え書き */

アプリケーション固有の情報をレジストリかINIファイルに貯えます。
AppWizardでアプリケーションを作成すると、CXxxApp::InitInstance()の中に次の命令が入っています。
SetRegistryKey(_T("xxxxxxxxx"));
これが記述されていると情報はレジストリに貯えられます。これをコメントアウトすると、 WindowsディレクトリにINIファイル(プロジェクト名+.ini)を自動生成してそこに情報を貯えます。
レジストリの場合ですと、位置は[HKEY_CURRENT_USER]-[Software]-[xxxxxxxxx]-[プロジェクト名]になるようです。
次にセッション名と項目名の書き込みと読み込みの例を示します。
CString strSection    = "My Section";          /* [セッション名] */
CString strStringItem = "My String Item";      /* 項目名 -文字型- */
CString strIntItem    = "My Int Item";         /* 項目名 -数値型- */

CWinApp* pApp = AfxGetApp();

pApp->WriteProfileString(strSection, strStringItem, "test");    /* 文字型書き込み */
CString strValue;
strValue = pApp->GetProfileString(strSection, strStringItem);   /* 文字型読み込み */

pApp->WriteProfileInt(strSection, strIntItem, 1234);            /* 数値型書き込み */
int nValue;
nValue = pApp->GetProfileInt(strSection, strIntItem, 0);        /* 数値型読み込み */
ちなみに、通常のファイルの読み書きのように、オープンやクローズのようなコーディングは不要です。


継続フレームの作成

蛇足かもしれないなぁと思いながらも、継続フレーム (アプリケーションの終了時のウィンドウの位置やザイズが次の起動時に復元されるウィンドウ)の作成例を示します。

ダイアログとSDI、MDIとでは実装するクラスがもともと違っているので、全く同じようには作れません。 それで大は小を兼ねる? と言うことで一番ややこしそうなMDIの場合を考えてみます。MDIで作れれば、ダイアログやSDIでは楽勝でしょうから。

まず、ClassWizardでActivateFrameをインプリメントします。ここでレジストリまたはINIファイルから前回のウィンドウの位置とサイズを読み出し、セットします。
ActivateFrame関数はウィンドウが描画される前に呼ばれるようなので、 ウィンドウの位置やサイズの変更などをここで行ってしまいます。特典として無駄なちらつきを押さえることができます。
問題になるのがインプリメントするフレームクラスです。SDIの場合は1つしかないので選択の余地はありませんが、 MDIの場合は親と子で2つあります。いろいろやってみたのですが、 MDIの場合は子のCChildFrameクラスにインプリメントすることにしました。
void CChildFrame::ActivateFrame(int nCmdShow) 
{
    CPrewApp* pApp = (CPrewApp*)AfxGetApp();
    if ( pApp->m_fstart) {
        pApp->m_fstart = FALSE;
        CString strrc = pApp->GetProfileString(pApp->m_strSection, pApp->m_strRectItem);
        if (!strrc.IsEmpty()) {
            CRect    rc(atoi((const char*)strrc), atoi((const char*)strrc+5), atoi((const char*)strrc+10), atoi((const char*)strrc+15));
            WINDOWPLACEMENT    wp;
            wp.rcNormalPosition = rc;
            // 子のクラスにいるので親を呼び出す(苦肉の策)
            GetParentOwner()->SetWindowPlacement(&wp);
            }
        }
    CMDIChildWnd::ActivateFrame(nCmdShow);
}
m_fstartはアプリケーションクラスのコンストラクタでTRUEがセットされています。
m_strSectionとpApp->m_strRectItemはセクション名と項目名がセットされています。
この例では、初めてアプリケーションを起動した場合、当然、情報が貯えられていませんので位置とサイズはOSに委ねられます。

次にウィンドウの位置とサイズをアプリケーションの終了時に貯える方法です。ClassWizardで親のフレームクラスにOnDestroyをインプリメントします。
void CMainFrame::OnDestroy() 
{
    CMDIFrameWnd::OnDestroy();

    CPrewApp* pApp = (CPrewApp*)AfxGetApp();
    WINDOWPLACEMENT    wp;
    if (GetWindowPlacement(&wp)) {
        CString    cb;
        cb.Format("%04d %04d %04d %04d", wp.rcNormalPosition.left, wp.rcNormalPosition.top, wp.rcNormalPosition.right, wp.rcNormalPosition.bottom);
        pApp->WriteProfileString(pApp->m_strSection, pApp->m_strRectItem, cb);
    }
}
なお、上記のコーディングは、ウィンドウの最小化や最大化の状態で終了される場合を考慮していません。 Get/SetWindowPlacement APIでそれらの情報の取得や設定ができるので、工夫してみて下さい。

コレクションクラス /* 使わないと損する超便利なクラス */

コレクションとは、動的配列や線形リストなどなど、動的に紐付けされた箱のことです。(正確な定義はどこかで調べてね) プログラマなら1度や2度は自作したことがあると思います。これが標準装備されているのですから、使わないてはないでしょう。

CObListコレクションクラス
このクラスは、CObjectクラスから派生したクラスのオブジェクトへのポインタの順序付きリストをサポートします。
次に例を示します。内容は10個のCEditクラス(MFC)をクライアント領域に配置しています。
class CXxxYyy : public CYyy
    .
    .
    CObList m_list;
    .

void CXxxYyy::zzz()
{
	CEdit* pedit;
	short	i;
	for	(i = 0 ; i < 10 ; i++) {
		pedit = new CEdit();
		m_list.AddTail((CObject*)pedit);
	}

	CRect	rc(0,0,200,50);
	i = 0;
	POSITION	pos = m_list.GetHeadPosition();
	while (pos != NULL) {
		pedit = (CEdit*)m_list.GetNext(pos);
		pedit->Create(WS_VISIBLE | WS_CHILD | /* WS_BORDER |*/ ES_AUTOHSCROLL | ES_AUTOVSCROLL  | ES_MULTILINE | ES_WANTRETURN,
                              rc, this, 200+m_list.GetCount()-1);
		CString cb; cb.Format("No.%d", i++);
		pedit->SetWindowText(cb);
		rc.OffsetRect(0,50);
	}
}

// デストラクタ
CXxxYyy::~XxxYyy()
{
	while (!m_list.IsEmpty())
	{
		delete m_list.GetHead();
		m_list.RemoveHead();
	}
}
CEditクラスを1つずつ生成してそのポインタをリスト(m_list)の最下部に追加(AddTail)して行きます。
forとwhileの2つのループがありますが、これはCObListの繰り返し処理を説明するためにあります。本来はforループ1つでこと足ります。 forループの部分でリストに生成したCEditのポインタを追加し、whileループの部分でそれを先頭から1つずつ参照しています。
デストラクタでは、リストの先頭のあるCEditのポインタを参照し、それを解放(newしたのでdeleteする)し、 リストされている先頭のCEditのポインタをリストから外しています。 結局最後は、すべてのCEditが解放され、リストが空っぽになります。

たまたまリストされるクラスにCEditを選びましたが、ユーザ定義のクラスでももちろん使えます。 そのときは必ずCObjectをpublicで継承したクラスを作成して下さい。


別アプリケーションの起動 /* マルチタスクだから・・ */

ウィンドウズ・アプリケーションをきれいに(一時的にDOS画面がでない)起動する方法と、 DOSの世界では問題にならなかった、他プロセスとの同期の取り方などを取り上げます。


ウィンドウズ・アプリケーションの起動

Visual Basicならば、Shellに相当する機能でしょうか。 別のWindowsアプリケーションを起動する方法です。
WinExec("notepad.exe c:\\config.sys",SW_SHOW);
WinExecの単純なパラメータでは物足りない場合は次の方法があります。
STARTUPINFO si;
PROCESS_INFORMATION pi;

memset(&si,0,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_SHOW;
BOOL bRes = CreateProcess(NULL,szCmd,NULL,NULL,FALSE,0,NULL,szDir,&si,&pi);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
szCmdに起動させたいコマンド(ファイル名 パラメータ..)を指定します。パスを付けても良いです。
szDirに新しいプロセスのカレントディレクトリを指定します。NULLで省略することもできます。


同期を取る
ご存知のようにWindowsアプリケーションはマルチタスクですので、上記のように別のアプリケーションを起動させた後も、処理は進んで行きます。 しかし、別のアプリケーションが終了したのを確認してから、処理を続行したい場合もあります。
そのような場合は、シグナルを利用すると良いと思います。
STARTUPINFO si;
PROCESS_INFORMATION pi;

memset(&si,0,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_SHOW;
BOOL bRes = CreateProcess(NULL,"notepad.exe",NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);
CloseHandle(pi.hThread);

if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_OBJECT_0)
    AfxMessageBox("AAA");    /* 起動したnotepad.exeがユーザにより終了されると実行されます */
else
    AfxMessageBox("ERROR");
CloseHandle(pi.hProcess);
WaitForSingleObject APIで指定したオブジェクト(プロセス)がシグナルになるのをひたすら(INFINITE)待ちます。
第2パラメータに0以上の値を指定する(ミリ秒単位)とその間だけシグナルを待ちます。タイムアウトになるとWAIT_TIMEOUTを戻します。 0を指定すると直ぐに戻ってくるので、シグナルの状態を返値で判定できます。

但し、上記のようなコーディングはメインスレッドに書くべきではないと思います。 ちゃんとメインスレッドからワーカスレッドを生成してその中に書くべきものだと思います。
(スレッドの生成方法はここを参照して下さい)

ユーザメッセージ /* ユーザが定義するメッセージとメッセージハンドラ */

定義と利用
昔ながらのWindowsプログラマならユーザ独自のメッセージを定義して、それを飛ばして受信することなど簡単なことでした。 しかしMFCではどうコーディングすれば良いのでしょうか。昔ながらのコーディングでも通用しますが、もっとクールなコーディング方法を紹介します。

※ユーザ定義用にWM_USERというシンボルが用意されています。 このシンボルから続くメッセージはそのアプリケーション内だけで有効なものです。他のアプリケーションには飛ばしてはいけません。 また、ユーザメッセージの値の範囲は0x0400(=WM_USER)〜0x7FFF内でなければなりません。


モードレスダイアログが親ウィンドウ(CXxxViewクラス)に自分が終了することをユーザメッセージで通知する 例を示しながら、ユーザメッセージの定義と利用方法を説明して行きます。
ClassWizardで自動的にとは行かないのですべて手動で行います。

まずアプリケーションクラスのヘッダーファイル、つまりアプリケーションのメインヘッダーファイルにユーザメッセージを定義します。 例では私の趣味でWM_USERに0x1000を加えた値を使用しています。
// [xxx.h(アプリケーションクラス)内]
    .
    .
#define    WM_USER_FINISH    (WM_USER + 0x1000)
ユーザメッセージ(例ではWM_USER_FINISH)を受信するクラス(例ではCXxxView)の BEGIN_MESSAGE_MAPマクロ(例ではCXxxViewの.cppにあります)に ON_MESSAGEマクロを追加します(例ではOnUserFinishという関数名にしました)。
InfoViewerの説明の一部ではAFX_MSG_MAPマクロの内側に記述してあるものがありますが、AFX_MSG_MAPマクロの外側に記述するのが良いと思います。

続いて、このメッセージのメッセージマップ関数(例ではCXxxYyyの.hにあります)をメンバ関数として宣言します。 この関数のパラメータと返値の宣言は何も考えずに次の例のようにします。
// [XxxYyy.cpp内]
    .
    .
BEGIN_MESSAGE_MAP(CXxxYyy, CYyy)
    //{{AFX_MSG_MAP(CXxxView)
        // メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
        //    この位置に生成されるコードを編集しないでください。
    //}}AFX_MSG_MAP
    ON_MESSAGE(WM_USER_FINISH, OnUserFinish)
	// 標準印刷コマンド
    ON_COMMAND(ID_FILE_PRINT, CYyy::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, CYyy::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, CYyy::OnFilePrintPreview)
END_MESSAGE_MAP()


// [XxxYyy.h内]
    .
    .
// 生成されたメッセージ マップ関数
protected:
    //{{AFX_MSG(CXxxYyy)
        // メモ -  ClassWizard はこの位置にメンバ関数を追加または削除します。
        //         この位置に生成されるコードを編集しないでください。
    //}}AFX_MSG
    afx_msg LRESULT OnUserFinish(WPARAM wParam = 0, LPARAM lParam = 0);
    DECLARE_MESSAGE_MAP()
};
※WM_USER_FINISHやそのメッセージ番号(WM_USER + 0x1000)、OnUserFinishの部分は、ユーザで自由に命名、定義できます。 また、メッセージ番号がダブらない限り複数定義できます。

これでWM_USER_FINISHメッセージを利用する準備ができました。
次にWM_USER_FINISHを実際に利用します。
// ダイアログ
CXxxDlg::CXxxDlg(CXxxView* pwnd) : CDialog(CXxxDlg::IDD)
{
	//{{AFX_DATA_INIT(CXxxDlg)
	//}}AFX_DATA_INIT
	m_pWnd = pwnd;
}

void CXxxDlg::OnOK() 
{
    // 親ウィンドウが生きていることを確認してポストします
    if (m_pWnd != NULL)
        m_pWnd->PostMessage(WM_USER_FINISH, 0x5678, 0x1234);    // ユーザメッセージ送信

    // 呼び出してはいけません。OnCancelなども同様!例では親ウィンドウでWM_USER_FINISHを受信したら、
    // このダイアログクラスをDestroyWindowしているからです!
    //CDialog::OnOK();
}


// 親ウィンドウ
CXxxYyy::CXxxYyy()
{
    m_pDlg = new CXxxDlg(this);    // 例では親ウィンドウのハンドルを渡しています

}

CXxxYyy::~CXxxYyy()
{
    delete m_pDlg;
}

// どこかからモードレスダイアログを生成表示。
    m_pDlg->Create(IDD_XXXDLG);


LONG CXxxYyy::OnUserFinish(UINT wParam, LONG lParam)    // ユーザメッセージ受信
{                                                       // wParem = 0x5678, lParam = 0x1234
    // メッセージを受信したときの処理を記述します。
    // 以下は例です。
    m_pDlg->DestroyWindow();

    return(0);
}

PostMessageとSendMessageの違い
メッセージを飛ばす機能です。PostMessageはメッセージを飛ばしたらすぐに処理が帰ってきますが、 SendMessageはメッセージを飛ばしメッセージを受信した関数の処理が終わるまで帰ってきません。
また、PostMessageはメッセージループを必要としませんが、SendMessageは必要とします。
SendMessageは特性上、同期を取りやすいのですが、反面、SendMessageがネストする場合は注意が必要です。 最悪の場合、永久ループでシステムリソースを食い尽くしてしまいますので。

メッセージ処理 /* VBで言うところのDoEventsを実現する */

Win32では長い長い処理を要する関数が処理中であっても、他のアプリケーションは動作していられます。 しかし長い長い処理をしているあなたのプログラムは固まったままになります。 つまり長い長い処理をしている間、WM_xxxメッセージは一切処理されません。

これを回避する方法に、スレッドを使う方法があります。 しかし、VBで実現されているように(VB v5.0はマルチスレッドに対応してない)、 一時的にメッセージ処理を行う方法([VB]DoEventsステートメント)もあります。

次のコーディングを長い長い処理をする関数内の(大抵はループ中に)付け加えてやります。
while(...) {  長い長いループ
    .
    . あなたの処理
    .
    MSG    msg;
    if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
        }
    }

スレッド /* 並行実行するユーザ定義関数 */

MFCはスレッドを2種類に分類しています。
メッセージループ(メッセージポンプ、受信したメッセージをメインプロシージャ関数やダイアログプロシージャに配信する簡単なループ) を持っているスレッドをユーザーインターフェーススレッド、持ってない方をワーカスレッドと呼びます。
メッセージループを持っていれば、ウィンドウを持つスレッド(プロセス的な性格を持つ感じ)を作れて、SendMessage APIが使用できます。 持っていなければ、それらができないし使えないことになります。
但し、PostMessageはメッセージループが無い関数内からでも発行できます。

ワーカスレッド
CWinThreadクラスを使用します。このオブジェクトは一般的にスレッドが存続している間だけ存在します。 つまり、スレッドが終了すれば自動的に消滅します。この動作を変更したいときはm_bAutoDeleteをFALSEにします。

スレッドのメインプログラムとなるグローバル関数を記述し、起動をかけるクラスにそれをフレンド関数として宣言します。
グローバル関数の返値はUINT、パラメータはLPVOIDを取ります。
AfxBeginThreadでグローバル関数をスレッドとして起動します。 AfxBeginThreadの第4パラメータに0を記述するか省略でスレッドがすぐに起動されます。
UINT Thread1( LPVOID pvParam )
{
    CXxxYyy*    pXxx = (CXxxYyy*)pvParam;
    CString str = pXxx->m_xxx;    // AfxBeginThreadの第2パラメータで与えられたクラスメンバにアクセスできる
    .
    .
    // 工夫:スレッドの終了が知りたい場合はユーザメッセージを飛ばします。
    AfxGetApp()->m_pMainWnd->PostMessage(WM_USER_ENDTHREAD1);

    return(0);
}

class CXxxYyy : public CYyy
{
    .
    .
    friend UINT Thread1(LPVOID);


// CXxxYyyクラスのどこかの関数で・・
CWinThread* pThread = AfxBeginThread((AFX_THREADPROC)Thread1, 
                                      (LPVOID)this,      // このクラスをパラメータとして渡しています
                                      THREAD_PRIORITY_NORMAL);

次はm_bAutoDeleteをFALSEした場合のコーディング例です。
CWinThread*の後始末をユーザで行わなければなりませんが、こちらの方が実用的ではないでしょうか。
次の記述をメンバ変数に加えます。それをコンストラクタで初期化します。
// メンバ変数
CWinThread*    m_pThread;

// コンストラクタで初期化
m_pThread = NULL;
AfxBeginThreadをサスペンドで指定し、m_bAutoDeleteを変更し、サスペンドを解除します。
if(( m_pThread = AfxBeginThread((AFX_THREADPROC)Thread1, 
                                (LPVOID)this,
                                THREAD_PRIORITY_NORMAL,
                                0,
                                CREATE_SUSPENDED,  // 初期動作はサスペンドを指定
                                NULL) )) {
    m_pThread->m_bAutoDelete = FALSE;              // 自動消滅禁止を指定
    m_pThread->ResumeThread();                     // サスペンドを解除!
    }
CWinThread*の後始末は、例えばCXxxYyyのデストラクタに次のような記述をします。
if (m_pThread) {
    DWORD dwCode;
    // スレッドの状態をdwCodeに返すAPI
    LONG ret = ::GetExitCodeThread( m_pThread->m_hThread, &dwCode );
    if ( ret && dwCode == STILL_ACTIVE ) {
        .
        .
        // スレッドが動作中。
        // スレッドを終了させる何か手段を講じているならばこのブロックに記述。
        m_pCancel->SetEvent();                                    // 例えばスレッド中のWaitを解除後、
        WaitForSingleObject( m_pThread->m_hThread, INFINITE );    // そのスレッドが死ぬのを待つ
        .
        .
        }
    delete m_pThread;
    }

ユーザーインターフェーススレッド
(^_^;) ある程度実用的なサンプルを載せようとしていましたら、複雑なサンプルになりました。 別章でもう少し関連したテクニックを紹介した後、簡単に説明できるように工夫します。


同期を取る
MFCのお話しより、Win32のお話しになります。
同期を取るために3つの手段があります。それはミューテックス、セマフォ、イベントです。 (イベントはMFCの世界のイベント、あるいはVBでのイベントとは意味が違います)
取りあえず、その中で最も簡単でよく利用されるイベントで同期を取る方法を紹介します。

イベントはオン・オフの2値しかないもので、状態は「シグナル」か「非シグナル」と言う表現をします。
関数機能
SetEventイベントの状態をシグナルにセットする
ResetEventイベントの状態をノンシグナルにセットする
また、イベントには手動イベント自動イベントの2つのタイプがあります。 手動イベントはその名の通り、状態の変更はユーザが制御しなくてはなりませんが、 自動イベントは待っているスレッドがアクティブになると自動的にそのイベントを非シグナルにセットします。
イベントオブジェクトはMFCのCEventクラスで作成すると便利です。 このクラスのコンストラクタの第2パラメータで手動・自動イベントを指定できます。省略時は自動イベントです。
次に例を示します。
// stdafx.h中に追加します
#include <ltafxmt.h>

// メンバ変数として定義します
public:
    CEvent*    m_pCancel;

// 生成
m_pCancel = new CEvent;
if ( m_pCancel ) {
    // 非シグナルにセット
    m_pCancel->ResetEvent();
    // スレッド起動
        .
        .

// 使い終わったらdeleteします
delete m_pCancel;

冒頭にでてきた3種類の同期オブジェクトは次のAPIを共通に利用できます。
API機能
DuplicateHandleシステムオブジェクトへの新しいハンドルを作成する
GetHandleInformationシステムオブジェクトへのハンドルに関する情報を得る
MsgWaitForMultipleObjects1つ以上のグループのオブジェクトがシグナルになるもで、あるいはスレッドの入力キューに特定の型のメッセージが届くまでスレッドを一時停止する
WaitForInputIdle他のプロセスがアイドルになるまでスレッドを一時停止する。
SetHandleInformationシステムオブジェクトの設定を変更する
WaitForMultipleObjects[Ex]1つ以上のグループのオブジェクトがシグナルになるまでスレッドを一時停止する
WaitForSingleObject[Ex]あるオブジェクトがシグナルになるまでスレッドを一時停止する
次にWaitForMultipleObjectsの例を示します。WaitForSingleObjectの例はここを参考にアレンジして下さい。
// スレッドになっている関数で・・
HANDLE aHandles[1];
aHandles[0] = pi.hProcess;
aHandles[1] = m_pCancel->m_hObject;
if (WaitForMultipleObjects( 2, aHandles, FALSE, INFINITE ) == WAIT_OBJECT_0) {
    .
    どちらかがシグナルになった場合、このブロックを実行します
    第3パラメータにTRUEを指定すると2つともシグナルになった場合、このブロックを実行します
    .
    }
第1パラメータはハンドル数、第2パラメータにはハンドルの配列を指定します。 この例ではプロセスとイベントを指定しています。 このように、待つハンドルはミューテックス、セマフォ、イベント、プロセス、スレッド等混在して指定できます。

DLL /* ダイナミックリンクライブラリ */

DLLとは何か。詳しくは後日、述べます(^_^;


VBからの呼出し
Visual Basic(v5.0)からVisual C++(v5.0)で作成したDLLを呼ぶ方法を示します。

xxx.dll中に dllFuncと言う関数を作成してそれをVBで利用する例で説明します。
dllFuncは3つのパラメータを持ち、 第1、第2パラメータに指定された値を加算して、第3パラメータと返値に返します。

VB側のコーディングは次のようになります。

VB側では funcと言う名前で使用するつもりです。また、ByValに注目して下さい。第3パラメータには ByValを記述していません。
Declare Function func Lib "xxx.dll" Alias "dllFunc" (ByVal n As Integer, ByVal n2 As Integer, n3 As Integer) As Integer

    Dim ans as Integer
    Dim rep as Integer
    ans = func(2, 3, rep)    '' ansとrepに 5 が格納されます
VC側のコーディングは次のようになります。

constは必須と言う訳ではありません。開発中の単純なミスをコンパイラが教えてくれる程度のものです。
constの意味を詳しく知りたい場合はこのホームページのC++のページを参照して下さい。
extern "C" int __stdcall dllFunc(const int prm, const int prm2, int* const prm3)
{
    *prm3 = prm * prm2;
    return(*prm3);
}
それから .DEFファイルでこの関数を EXPORTS宣言します
.DEFファイルは既にAppWizardにより作成されていますので、[ファイル]-[開く]メニューから開きます。
; xxx.def : DLL 用のモジュール パラメータ宣言

LIBRARY      "xxx"
DESCRIPTION  'xxx Windows Dynamic Link Library'

EXPORTS
    ; 明示的なエクスポートはここへ記述できます
        dllFunc    @2
複数の関数をEXPORTSする場合は、次のように @ マークの直後の序数を増やしていきます。
EXPORTS
        dllFunc       @2
        dllGetData    @3
        dllSetData    @4
序数が2から始まる理由はわかりません(^_^;。 SDKに付いてきたサンプルは2から始まっていたのでそれに習っているだけです。

最後に、メイクしたDLL(拡張子DLL)を Windows\Systemへコピーします。
(VBアプリケーションが作成したDLLを参照できる場所であればどこでもOK)

データベース /* 実際に使ってみよう */



Microsoft ODBC


MS Accessの*.mdbにアクセスしてみます。
次の手順に従ってコーディングしてみて下さい。

手順1:データソースのインストール
Windows95ならばコントロールパネルの[32ビットODBC]を起動して、Microsoft Accessドライバが使えるか確認します。 (場合によっては[ユーザDSN]のタグをクリックしてその中の)[MS Access 7.0 Database]を選び[構成]ボタンを押します。 [ODBC Microsoft Access 97 セットアップ]のダイアログが開きますので、 [データソース名]や[説明]を適当に入力して、[選択]ボタンでアクセスしたいmdbファイルを選びます。
(mdbファイルは予めAccessで作成しておきます。データは入力しておく必要はありません)

手順2:AppWizard
[ステップ2/6]のデータベースのサポートは取りあえず、 [ヘッダーファイルのみインクルード]を選びます。

手順3:ClassWizard([新規クラスの追加])
レコードセットクラス(例:CRecSet)を作成します。[新規クラスの作成]で基本クラスを CRecordsetにします。 次に[データベースのオプション]のダイアログが開きますので、データベースのODBCを先程 適当に入力した名称を選びます。レコードセットタイプは取りあえず[ダイナセット]を選択します。

手順4:ClassWizard([変数の追加])
ClassWizardで作成したクラスの[メンバ変数]を確認すると、列名にデータベースの項目が表示され、 適当な名前のメンバが定義されているのがわかります。メンバ名はここで適切な名前に変更できます(例:m_column1〜5)。

手順5:コードのインプリメント
コーディング例を次に示します。再描画があるたびデータ内容を表示し直します。この例では処理効率が悪いのでコードを工夫して下さいね。
class CXxxDoc : public CDocument
{
public:
    CRecSet    m_Set;                   /* ClassWizardで作成したクラス(基本クラス:CRecordset) */
    .
    .
}

void CXxxView::OnInitialUpdate() 
{
    CView::OnInitialUpdate();

    m_pSet = &GetDocument()->m_Set;
}

void CXxxView::OnDraw(CDC* pDC)
{
    CXxxDoc* pDoc = GetDocument();
    CString    cb;

    short  row = 0
    m_pSet->Open();                     /* オープン */
    if (!m_pSet->IsEOF()) {             /* 空のレコードセットか? */
        m_pSet->MoveFirst();            /* 先頭へ移動 */
        while(!m_pSet->IsEOF()) {
            cb.Format("%ld %s %d %s %s", m_pSet->m_column1
                                       , m_pSet->m_column2
                                       , m_pSet->m_column3
                                       , m_pSet->m_column4
                                       , m_pSet->m_column5);
            pDC->TextOut(0, row, cb);
            row += 32;
            m_pSet->MoveNext();         /* 次のレコードへ */
            }
        }
    m_pSet->Close();                    /* クローズ */
}

レコードの追加
コーディング例を次に示します。OpenとCloseはどこかでまとめたいですね。
COdbc1Doc* pDoc = GetDocument();

m_pSet->Open();
if( !m_pSet->CanAppend() ) {
    AfxMessageBox( "追加できない");
    return;                    
    }
m_pSet->AddNew();
m_pSet->m_column2 = "新しいレコードの項目2";
    .
    .
if( !m_pSet->Update() )
     AfxMessageBox( "アップデートできない" );
m_pSet->Requery();
m_pSet->Close();

追加する場合は、AddNew()してUpdate()してRequery()。 既にあるレコードの変更は Edit()という関数を呼んでUpdate()。 レコードの削除は delete()という関数を呼んでMoveNext()でカーソル(カレントレコード)を次に移動させておきます。


Microsoft DAO


ODBCモード

Oracle7.3にアクセスしてみます。

AppWizardの[ステップ2/6]のデータベースのサポートは[ヘッダーファイルのみインクルード]を選びます。 ここでは新規クラスの追加等は行いません。お好みで CDaoRecordsetクラスを基本クラスして作成して下さい。

以下の例は必要最小限と思われるコーディングしか行っていません。
CDaoRecordsetクラスとCDaoDatabaseクラスを使用します。
class CXxxYyy : public CDocument
{
public:
    CDaoRecordset*   m_pRecordset;
    CDaoDatabase     m_Database;
    .
    .

コンストラクタ
    m_pRecordset = NULL

デストラクタ
    delete m_pRecordset;    // 実際は new されたのを確認して delete すべき。
    m_Database.Close();

まず、ダイナセットを構築します。

次のコードを実行すると、Open関数で[データベース選択]ダイアログが現れます。
新規の場合はその中の[マシンデータベース]タグの[新規作成]ボタンをクリックします。 すると[データソース新規作成]ダイアログが現れます。 [ユーザーデータソース(このマシンのみ適用)]を選択し[次へ]、 [セットアップするデータソースのドライバ…]に Oracle73 を選択し[次へ]、 [完了]ボタンを押します。

作成したデータソース名がある場合は、それを選択します。 すると[Oracleへのログオン]ダイアログが現れます。[ユーザー名]と[パスワード]を入力します。
void CXxxYyy::OpenDAO()
{
    /*
    * テーブル、ダイナセット、またはスナップ ショットから、
    * 新しいレコードセットを作成します。
    */
    try {
        m_Database.Open("");
        }
    catch(CDaoException* e) {
        AfxMessageBox("Error: DATABASE.Open");
        e->Delete();
        return;
        }

    CString connect = m_Database.GetConnect();

    // ここから共通 //

    /*
    * 「サンプル」と言うデータベースのダイナセットを構築します。
    */
    // SQL処理
    CString  query
    query = "select * from サンプル");
    m_pRecordset = new CDaoRecordset(&m_Database);
    try {	
        m_pRecordset->Open(dbOpenDynaset, query, dbReadOnly);
        }
    catch(CDaoException* e) {
        AfxMessageBox("Error: RECORDSET.Open");
        e->Delete();
        return;
        }
}

次のコーディングで接続されたデータベースに対して、フィールド名の取得やSQLの発行ができます。
/*
* フィールド名を取得します。
*/
void CXxxYyy::GetFieldItem()  // 共通 //
{
    CString    cb;
    short fnum = (short)m_pRecordset->GetFieldCount();
    cb.Format("フィールド(列)の数: %d", fnum);
    AfxMessageBox(cb);

    short i;
    CDaoFieldInfo    fi;
    for(i = 0 ; i < fnum ; i++) {
        m_pRecordset->GetFieldInfo(i, fi);
        short len = (short)max(fi.m_lSize, fi.m_strName.GetLength());
        cb.Format("(%d) フィールド名: [%s] 長: %d", i, fi.m_strName, len);
        AfxMessageBox(cb);
	}
}

/*
* データを取得します。
*/
void CXxxYyy::GetFieldValue()  // 共通 //
{
    /*
    * 一旦、クローズします。
    */
    m_pRecordset->Close();

    /*
    * 続いて、下記の条件でダイナセットを作成します。
    */
    // SQL処理
    CString  query
    query.Format("select * from %s order by %s", "サンプル", "社員番号");
    try {	
        m_pRecordset->Open(dbOpenDynaset, query, dbReadOnly);
        }
    catch(CDaoException* e) {
        AfxMessageBox("Error: GetFieldValue");
        e->Delete();
        return;
        }

    if (!m_pRecordset->IsBOF())
        m_pRecordset->MoveLast();

    /*
    * 最初のレコードの全てのフィールドの内容を表示します。
    */
    short fnum = (short)m_pRecordset->GetFieldCount();
    COleVariant   v;
    CString str;
    short i;
    for( i = 0 ; i < fnum ; i++) {
        v = m_pRecordset->GetFieldValue(i);
        switch (v.vt) {
            case VT_BOOL:
                if (v.boolVal == 0)
                    str = "False";
                else
                    str = "True";
                break;
            case VT_I2:
                str.Format("%d", (short)v.iVal);
                break;
            case VT_I4:
                str.Format("%d", v.lVal);
                break;
            case VT_R4:
                str.Format("%f", (double)v.fltVal);
                 break;
            case VT_R8:
                str.Format("%f", v.dblVal);
                break;
            case VT_BSTR:
                str = (LPCSTR)v.bstrVal;
                break;
            case VT_DATE:
                str = COleDateTime(v).Format();
                break;
            default:
                str = "Error: 未定義なタイプ";
            }
        CString    cb;
        cb.Format("(%d) 内容 [%s]", i, str);
        AfxMessageBox(cb);
    }
}
※COleVariantについての詳細はここのホームページのOLEのページをご参照下さい。



MDBモード

MS Accessの*.mdbにアクセスしてみます。

前述のODBCモードのコーディングのOpenメンバ関数の第1パラメータにデータソース名を与えます。
m_database.Open("d:\\db1.mdb");
GetConnect()はGetName()に置き換えます
後のコーディングはそのままで良いです



ちょっとしたこと /* 知らなければ結構つらい */

アプリケーションのインスタンスへのハンドルを取得する
3つの方法をあげます。
(1) AfxGetApp()->m_hInstance
(2) AfxGetInstanceHandle()
(3) AfxGetResourceHandle()


ウィンドウのHWNDを取得する
CXxxWnd* pXxx = (CXxxWnd*)AfxGetApp()->m_pMainWnd;
HWND hWnd = pXxx->m_hWnd;


HWNDからCWndオブジェクトへのポインタを取得する
CXxx* pXxx = (CXxx*)CWnd::FromHandle(hWnd);


親のウィンドウを取得する
CXxxWnd* pXxx = (CXxxWnd*)GetParent();


IDから子孫ウィンドウ検索
pXxxからの直系の子ウィンドウのみでなくすべての子ウィンドウツリーを検索します。
CYyy* pyyy = (CYyy*)pXxx->GetDescendantWindow(ID);


CWndからIDを取得する
pWndが示すコントロールのIDを取得します。
int id = pWnd->GetDlgCtrlID();


コントロールのIDからCWndを取得する
CWnd* pWnd = GetDlgItem(id);


デバイス座標から論理座標へ変換
例えば、クライアント領域をマウスでクリックした位置に点を表示する。
void CXxxView::OnRButtonDblClk(UINT nFlags, CPoint point) 
{
    CClientDC    dc(this);
    OnPrepareDC(&dc);
    CPoint    pt = point;
    dc.DPtoLP(&pt);
    MoveTo(pt);
    dc.LineTo(pt);
    CString cb;
    cb.Format("(%d,%d)", pt.x, pt.y);
    dc.TextOut(pt.x,pt.y,cb);
}


クライアント領域の背景塗り潰し
void CXxxView::OnDraw(CDC* pDC, CRect& rcBounds)
{
    pDC->FillRect( rcBounds, CBrush::FormHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
}


ダイアログの背景色と文字色の設定
ダイアログの背景色と文字色はデフォルトのままですとグレーと黒ですね。
これを変更するには、アプリケーションクラスのInitInstance関数の中で次のメンバ関数を呼びます。 例では背景色を赤、文字色を緑というエゲツナイ色に変更しています。
BOOL CXxxApp::InitInstance()
{
    .
    .
    SetDialogBkColor(RGB(0xff,0,0), RGB(0,0xff,0));


拡張子で開く(印刷する)アプリケーションを特定させる
アプリケーションクラスのInitInstance()中に、次の赤文字の部分のコーディングを追加すると、 アプリケーションで作成したデータファイルをエクスプローラ(ファイルマネージャ)内でダブルクリックして開けるようになります。
// メイン MDI フレーム ウィンドウを作成
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
    return FALSE;
m_pMainWnd = pMainFrame;

// ドラッグ/ドロップ のオープンを許可します
m_pMainWnd->DragAcceptFiles();

// DDE Execute open を使用可能にします。
EnableShellOpen();               // Here!
RegisterShellFileTypes(TRUE);    // Here!

// DDE、file open など標準のシェル コマンドのコマンドラインを解析します。
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
このコーディングは、AppWizardでは[ステップ3/6]の[複合ドキュメントのサポート]を[しない]以外の指定をし、 なおかつ、[ステップ4/6]にある[詳細設定]ボタンを押して、 [ドキュメントのテンプレート文字列]の[非ローカライズ文字列]の[ファイルの拡張子]を指定したとき 自動的に生成されるようです。
後からこの機能を追加するのであれば、複合ドキュメントのサポートとリソースの修正で行えます。 詳しくは別の章で説明します。


アプリケーション中でエクスプローラ内でファイルをダブルクリックして動作させたのと同じ効果を得たい
次のようなコーディングで可能です。パラメータの説明等は InfoViewerを参照して下さい。
ShellExecute(NULL, "open", "D:\\Personal\\book1.xls", NULL, NULL, SW_SHOWNORMAL);