AirparkLab
■プログラミングTIPS

このページは筆者がWindowsプログラミング(VC++4.0MFC)をしていて ハマッた点やなるほど!と思った点をメモしたものです。
このページの記事やサンプルコードなどの流用は自家用,業務用を 問わず行って構いませんが,責任は負えませんのでご了承ください。

【2006.03.08追記】
ついに新しいパソコンを買いました!念願の Visual C++ .NET 2003 も 買いました!やっと VC++4.0 から卒業です!(涙)
新しいIDEは使い勝手がいいですが,クラスウィザードがないのと, リスト形式のリソースプロパティに,もう一つ馴染めません。

VC++.NET2003で動作確認している新しいTIPSは "(VC7)" の記載がありますが, MFCですのでバージョンを問わず動くものが多いと思います。



 【目次】   
C/C++言語関係  Windows API関係  VC++/MFC関係  その他  リンク



 

【C/C++言語関係】

目次に戻る
 

【Windows API関係】

目次に戻る
 

【VC++/MFC関係】

目次に戻る
 

【その他】

目次に戻る
 

【リンク】 よくお世話になっているプログラミング解説ページ

目次に戻る



【C/C++言語関係】

 
◆ CからC++,C++からCの関数を呼び出す
これまでのソフト資産を活用するようなとき,C++からCの関数を呼びたい場合があります。 その場合は以下のようにすればOKです。 ==== cファイル ここから ==== void C_Function( void ) { /* This is C function */ } ==== cファイル ここまで ==== ==== cppファイル ここから ==== extern "C" void C_Function( void ) void CPP_Function( void ) { C_Function(); } ==== cppファイル ここまで ==== また,反対にCからC++の関数を実行することもできます! ==== cppファイル ここから ==== extern "C" void CPP_Function( void ) { /* This is CPP function */ } ==== cppファイル ここまで ==== ==== cファイル ここから ==== extern void CPP_Function( void ); void C_Function( void ) { CPP_Function(); } ==== cファイル ここまで ====
目次に戻る

 
◆ 仮想関数とオーバーライド
基本クラスから派生したクラスで基本クラスにある関数と同じ型の同名関数を宣言したら どうなるでしょうか?基本クラスでその関数を仮想関数宣言(virtual)しておかなければコンパイル が通らないでしょうか?結果は以下参照。

正解: 仮想関数宣言をしなくてもコンパイルは通ります。 #include "iostream.h" class cBase { int x; public: cBase( ){ cout << "cBase Construction\n"; }; ~cBase( ){ cout << "cBase Destruction\n"; }; void Test( ){ cout << "cBase Test\n"; }; }; class cChild : public cBase { int y; public: cChild( ){ cout << "cChild Construction\n"; }; ~cChild( ){ cout << "cChild Destruction\n"; }; void Test( ){ cout << "cChild Test\n"; }; }; void main( ) { cChild ob; ob.Test( ); cBase *p; p = &ob; p->Test( ); } 実行結果 cBase Construction cChild Construction cChild Test cBase Test cChild Destruction cBase Destruction ではcBaseクラスのTest( )を仮想関数宣言したら実行結果はどうなるでしょうか? cBase Construction cChild Construction cChild Test cChild Test cChild Destruction cBase Destruction 上記の結果から分かるように派生クラスオブジェクトを基本クラスのポインタで受けて実行した 場合に違いが現れます。仮想関数宣言していなければ基本クラスの関数が実行され、 仮想関数宣言しておくと派生クラスの関数が実行されます。 微妙な違いですがこれがオーバーライドの実態です。

なお引数の数や種類が異るなど型が違う同名の関数が基本クラスと派生クラスに存在する場合、 派生クラスオブジェクトを基本クラスのポインタで受けたものでは派生クラスの関数を 実行することができません。なぜならそれはオーバライドではなく異なる関数が派生クラスだけに 存在しているという意味になるからです。
これは基本クラスからは派生クラスで追加された変数や関数にはアクセスできない当たり前の 結果を現しています。そのような場合は最初から派生クラスのポインタで受けるべきですが、 派生クラスのポインタとしてキャストすることでアクセスすることも可能です。

ちなみに基本クラスオブジェクトを派生クラスのポインタで受けることはできません。 コンパイルエラーとなります。
目次に戻る

 
◆ 関数とオブジェクト
C++ではclass宣言で定義したオブジェクトも、アドレスではなく実体として関数の引数として 使用することができます。ただしint変数などと同様に引き渡された関数先では単なるコピーに すぎないので呼び出し元のオブジェクトには何ら影響を与えません。

面白いのは、コピーが作られる際にそのコピーオブジェクトのコンストラクタは実行されませんが、 呼び出した関数が終了する際にコピーのデストラクタが実行されるということです。 オブジェクトを初期化するコンストラクタは、コピー生成の場合には実行するとオブジェクトが 初期化されてしまいコピーではなくなってしまうので実行せず、オブジェクトが消滅する場合には デストラクタの実行が必要であるという考えのようです。

ただし注意しなければならないのはコンストラクタでメモリを割り当て、メンバ変数にその ポインタを保持し、デストラクタでそれを解放するというようなオブジェクトは、たとえコピーと いえど呼び先の関数で勝手にデストラクタが実行されてしまうとメンバ変数にポインタを保存して おいたメモリが解放されてしまうため不具合が発生してしまうことです。

このような特性を知った上でオブジェクトを関数の引数に用いるのは構いませんが、ミスを防ぐためにも オブジェクトを引数にする場合は実体でなくそのアドレス(ポインタ)で渡した方が無難だと思います。 ポインタで渡す場合、当然ですが関数実行の前後でコンストラクタやデストラクタが実行されることは ありません。

C++ではもう一つの方法があります。ポインタを使わず且つ引き渡し時のコンストラクタ・デストラクタを 実行させない方法があります。それは「参照」と呼ばれる方法で、実体によるコピーの引き渡し方法とポインタ によるアドレス渡しの中間のような特性のものです。

下記のように関数宣言時に演算子&を使って「参照」を宣言します。 void func( int &i ) { i = 100; } void main( ) { int n; func( n ); // n には100が入っている } こうすると関数func( )ではmain( )のnを直接操作するのと同じ効果があります。例ではint型の参照変数ですが、 もちろんオブジェクトでも可能です。
ポインタ渡しよりもコードがすっきりするのとアドレスを直接扱わないので比較的安全だとされており、 C++では結構使われているようです。

関数のオーバロードにしろ、参照にしろ、Cの時と比べてコンパイラ自身が賢くなっていることを 実感できますね。


目次に戻る

 
◆ newとdelete
newとdeleteについておさらいします。

演算子newとdeleteは配列(メモリ)の確保やオブジェクトの生成作成に使えます。

以下のようにしてint型変数の確保と解放ができます。 int *nNum = new int; delete nNum; // 通常 int nNum; で十分なのであまり意味はありません。 また以下のようにして初期値を与えることができます。 int *nNum = new int ( 1000 ); delete nNum; // 通常 int nNum = 1000; で十分なのであまり意味はありません。 配列(メモリ)の確保は以下のようにしてできます。 char *pBuf = new char[256]; delete [] pBuf; // ※ []を必ず記述することに注意 // 上記の場合 char bBuf[256]; で十分なのであまり意味はありませんが、 // 解放する予定があったり、任意のサイズを確保する場合は下のように // 行うことができます。 int len = 256; char *pBuf = new char[len]; delete [] pBuf; // ※ []を必ず記述することに注意 // C++といえど、char bBuf[len]; は不可能です。 // また従来のようにmallocで行うこともできます。 int len = 256; char *pBuf = (char *)malloc( len ); free( pBuf ); newで配列の確保をした場合は解放の記述方法に注意してください。
なお残念ながらnewで確保する配列には初期値を与えることはできません。

ここまではあまりnewのありがたみがありませんでした。せいぜいキャストしなくてすむくらいです。 しかしオブジェクトの動的な確保というか生成を行う場合はnewを使用するしかありません。 mallocでは不可能です。以下に実例を述べます。

以下のようなオブジェクトがあるとします。 class CTest { int m; public: CTest( ){ m = 0; cout << "construct: default\n"; } CTest( int n ){ m = n; cout << "construct: " << m << "\n"; } ~CTest( ){ cout << "destruct\n"; } }; このオブジェクトを一個使おうと思った時は、もちろん CTest test; とします。この時デフォルト(引数なし)のコンストラクタ(とデストラクタも)当然実行されます。
ちなみに、 CTest test( 0 ); とやって引数ありのコンストラクタを実行させることもできます。

またオブジェクトの配列も以下のようにして生成できます。 // デフォルト(引数なし)のコンストラクタを実行させる場合 CTest atest[10]; // 引数が一つのコンストラクタを実行させる場合 CTest atest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // 多次元配列の場合 CTest atest[4][2] = { 0, 1, 2, 3, 4, 5, 6, 7 }; // 引数が二つのコンストラクタを実行させる場合 CTest atest[3] = { CTest( 0, 0 ), CTest( 1, 1 ), CTest( 2, 2 ) }; // さらにそれが多次元配列の場合 CTest atest[2][3] = { CTest( 0, 0 ), CTest( 0, 1 ), CTest( 0, 2 ), CTest( 1, 0 ), CTest( 1, 1 ), CTest( 1, 2 ), }; 引数が複数になるとクラス名を添える正式な書式が必要となります。

次に、動的にオブジェクトの生成と消滅を行う場合はnewとdeleteを使います。 // 一個だけ生成(デフォルトのコンストラクタ)と消滅 CTest *p = new CTest; delete p; // 一個だけ生成(引数ありのコンストラクタ)と消滅 CTest *p = new CTest( 10 ); delete p; これはmallocを使って、 CTest *p = (CTest *)malloc( sizeof( CTest ) ); free( p ); のように実現できるかといえばできません。mallocを使った例ではCTestのサイズ(メンバに持つ メモリの合計でコードサイズは含まれない)だけの領域を確保しただけで、オブジェクトを作成した ことにはならないからです。newとdeleteではコンストラクタとデストラクタが自動で実行されます が、mallocとfreeでは当然実行されません。かといって手動で p->CTest( ); とやろうとしても コンパイルエラーとなってしまいます。コンストラクタが実行できないのはオブジェクトにとって 致命的です。したがってオブジェクトの動的な生成、消滅をさせる場合は、newとdeleteを使う以外 ありません。

複数のオブジェクトを動的に生成、消滅させるには以下のように行います。 int len = 5; CTest *p = new CTest[len]; // もちろん定数5でもOKです。 delete [] p; // ここでデストラクタがあればlen回実行される。添字は必要ないようだ。 newで[]を使ってオブジェクトの配列を生成した場合、deleteの時に[]を入れないと、配列分の メモリは解放されますが、配列の一つ一つに対してデストラクタが実行されないので予期しない 不具合の原因になるので注意してください。

なおnewで生成するオブジェクトの配列は、デフォルトのコンストラクタしか実行されません。 CTest *p = new CTest[10]( 100 ); // ダメ delete [] p; CTest *p = new CTest[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // ダメ delete [] p;
目次に戻る




【Windows API関係】

 
◆ 絶対パスを相対パスに変換する
絶対パスを相対パスに変換するには,PathRelativePathTo() を使います。 基準となるフォルダまたはファイル szSrcPath に対して,szDestPath の相対パスを szRelPath に格納するには 以下のようにします。 #include <shlwapi.h> char szRelPath[MAX_PATH]; PathRelativePathTo( szRelPath, szSrcPath, FILE_ATTRIBUTE_DIRECTORY, szDestPath, FILE_ATTRIBUTE_NORMAL ); szSrcPath と szDestPath は,ファイルまたはフォルダのどちらでも構わないようです。 ファイルのときは FILE_ATTRIBUTE_NORMAL,フォルダのときは FILE_ATTRIBUTE_DIRECTORY をフラグ指定してください。
目次に戻る

 
◆ 画像ファイルのロードと描画
ビットマップファイルを単純に描画したいだけなら以下のようすればできます。 BYTE *pFileImage = ビットマップのファイルイメージ; BITMAPFILEHEADER *pBMFileHeader = (BITMAPFILEHEADER *)pFileImage; if( pBMFileHeader->bfType == *((WORD *)"BM") ){ BITMAPINFOHEADER *pBMInfoHeader = (BITMAPINFOHEADER *)(pFileImage + sizeof(BITMAPFILEHEADER)); BYTE *pBMData = (BYTE *)(pFileImage + pBMFileHeader->bfOffBits); ::StretchDIBits( pDC->GetSafeHdc(), 0, 0, pBMInfoHeader->biWidth, pBMInfoHeader->biHeight, 0, 0, pBMInfoHeader->biWidth, pBMInfoHeader->biHeight, pBMData, (BITMAPINFO *)pBMInfoHeader, DIB_RGB_COLORS, SRCCOPY ); } ::StretchDIBits() は JPEG や PNG ファイルも描画できるようです。詳細はヘルプを参照してください。

ビットマップファイルやsusieプラグインに対応した画像ファイルのロードと描画を行うクラスを作成してみました (初期のプログラムなのでちょっと恥ずかしいです)。

ロードクラス描画クラスを参照してみてください。
コメントに使い方を記述してありあます。
目次に戻る

 
◆ GetLastErrorとFormatMessage
Windows API関数を実行したときの詳細なエラーコードは GetLastError( ) 関数で取得できます。 このコードが何を示すかは C:\MSDEV\INCLUDE\WINERROR.H を調べると大体分かりますが, 以下のように FormatMessage( ) 関数を使うと日本語でエラーメッセージを取得することができます。 DWORD dwErr = GetLastError(); char szMsgBuff[256]; FormatMessage( FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), szMsgBuff, 256, NULL );
目次に戻る

 
◆ お手軽なロックの防止
サブスレッドを使うまでもないけど,処理が長くてウインドウが無反応になるのを防ぐには 以下のコードを処理の中に入れるといいです。 // ウインドウメッセージの処理(大量データを処理したときのロック防止) MSG msg; if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){ TranslateMessage( &msg ); DispatchMessage( &msg ); } ただ上記の方法だとウインドウを閉じるなどのボタンまで効いてしまうので,処理の最中に 操作されて都合の悪いコントロールなどはあらかじめ無効にしておくのが無難です。 メッセージの内容を見てDispatchするかどうか判断してもいいかも。
目次に戻る

 
◆ IMEの無効化
エディットコントロールなどにメールアドレスを入力するようなとき, 自動的に日本語入力を禁止にしてあげると使い勝手がいいかもしれません。

IMEを無効にしたり有効にしたりするには ImmAssociateContext( ) 関数を使用します。 以下に使用例を示します。クラスウィザードで日本語入力を禁止にするコントロールのWM_SETFOCUSと WM_KILLFOCUSにハンドラを実装して以下の様にコーディングします。 #include "imm.h" #pragma comment( linker, "/DEFAULTLIB:imm32.lib" ) // メンバ変数 HWND m_hWndFocused; HIMC m_hIMC; // IMEを無効にするコントロールのWM_SETFOCUSハンドラ void C????Dlg::OnSetfocusNoIME( ) { // IMEを無効にする m_hWndFocused = GetFocus( )->GetSafeHwnd( ); if( m_hWndFocused ) m_hIMC = ImmAssociateContext( m_hWndFocused, NULL ); } // IMEを無効にするコントロールのWM_KILLFOCUSハンドラ void C????Dlg::OnKillfocusNoIME( ) { // IMEを有効にする if( m_hWndFocused ) ImmAssociateContext( m_hWndFocused, m_hIMC ); }
目次に戻る

 
◆ MAPIでメールの送信
MAPIとは Messaging Application Programming Interface の略称で,これに対応した別のメールクライアント アプリケーションを経由してメールに関する機能を使用できるようになる関数群です。ネットワークやメール送信 プロトコルの知識がなくてもメールに関するアプリケーションを作成することができます。多くのメール増殖型 コンピュータウイルスがMAPIを悪用しているのもインタフェースが簡単だからです。

どのメールクライアントを利用するかはインターネットエクスプローラから, ツール − インターネットオプション − プログラム − 電子メール の設定を行うことで選択が可能です。

下にメール送信のサンプルプログラムを掲載します。 #include "mapi.h" typedef ULONG (WINAPI *MAPI_SEND_MAIL_PROC)( LHANDLE lhSession,\ ULONG ulUIParam, lpMapiMessage lpMessage, FLAGS flFlags, ULONG ulReserved ); // 送信元情報 MapiRecipDesc Sender; Sender.ulReserved = 0; Sender.ulRecipClass = MAPI_ORIG; Sender.lpszName = "送信元メールアドレス"; Sender.lpszAddress = NULL; Sender.ulEIDSize = 0; Sender.lpEntryID = NULL; // 受信先情報(複数の場合は配列にする) MapiRecipDesc Receiver; Receiver.ulReserved = 0; Receiver.ulRecipClass = MAPI_TO; // MAPI_CC, MAPI_BCCもあり Receiver.lpszName = "受信先メールアドレス"; Receiver.lpszAddress = NULL; Receiver.ulEIDSize = 0; Receiver.lpEntryID = NULL; // 添付ファイル情報(複数の場合は配列にする) MapiFileDesc AttachFile; AttachFile.ulReserved = 0; AttachFile.flFlags = 0; AttachFile.nPosition = 0xffffffff; AttachFile.lpszPathName = "添付ファイル名"; AttachFile.lpszFileName = NULL; AttachFile.lpFileType = NULL; // 送信設定 MapiMessage Message; Message.ulReserved = 0; Message.lpszSubject = "件名"; Message.lpszNoteText = "本文"; Message.lpszMessageType = NULL; Message.lpszDateReceived = NULL; Message.lpszConversationID = NULL; Message.flFlags = MAPI_RECEIPT_REQUESTED; Message.lpOriginator = &Sender; // 送信元情報へのポインタ Message.nRecipCount = 1; // 受信先情報の数 Message.lpRecips = &Receiver; // 受信先情報へのポインタ Message.nFileCount = 1; // 添付ファイル情報の数(なければ0) Message.lpFiles = &AttachFile; // 添付ファイル情報へのポインタ(なければNULL) // メールクライアントソフトでダイアログを表示するかのフラグ FLAGS flag = 0; if( ダイアログを表示する場合 ) flag = MAPI_DIALOG; // MAPIルーチンを使用して送信 HINSTANCE hInst; hInst = LoadLibrary( "mapi32.dll" ); if( hInst == NULL ){ AfxMessageBox( "mapi32.dllがロードできませんでした。送信に失敗しました。" ); } else { MAPI_SEND_MAIL_PROC MAPISendMailProc = (MAPI_SEND_MAIL_PROC)GetProcAddress( hInst, "MAPISendMail" ); if( MAPISendMailProc( 0, 0, &Message, flag, 0 ) != SUCCESS_SUCCESS ){ AfxMessageBox( "送信に失敗しました。" ); } else AfxMessageBox( "送信完了しました。" ); FreeLibrary( hInst ); }
目次に戻る

 
◆ フォント一覧
インストールされているフォントの一覧を取得するにはEnumFontFamiliesEx( )というAPI関数を使用します。 Enum〜とかいう関数は何らかの情報を列挙する関数で,以下のようにコールバック関数とセットで使用します。 // フォント列挙コールバック関数 int CALLBACK EnumFontFamExProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntm, int FontType, LPARAM lParam ) { CComboBox *pComboBox = (CComboBox *)lParam; // 日本語TrueTypeFontのみコンボボックスにフォント名を追加 if( FontType == TRUETYPE_FONTTYPE // && lpelfe->elfLogFont.lfPitchAndFamily & FIXED_PITCH // 等幅条件 && lpelfe->elfLogFont.lfCharSet == SHIFTJIS_CHARSET ){ pComboBox->AddString( lpelfe->elfLogFont.lfFaceName ); } // フォントの列挙を継続するには1を,中止するには0を戻す。 return( 1 ); } // フォント一覧を取得する(コンボボックスを初期化するときなど) HDC hdc = ::GetDC( this->GetSafeHwnd( ) ); LOGFONT LogFont; ZeroMemory( &LogFont, sizeof(LOGFONT) ); LogFont.lfCharSet = DEFAULT_CHARSET; // ここではコンボボックスに一覧をセットするために // そのアドレスをパラメータとして渡している。 EnumFontFamiliesEx( hdc, &LogFont, (FONTENUMPROC)EnumFontFamExProc, (LPARAM)&m_ComboBox, 0 ); ::ReleaseDC( this->GetSafeHwnd( ), hdc ); // MS ゴシックをデフォルトで選択 m_ComboTTF.SelectString( -1, "MS ゴシック" ); なお,フォントを選択する場合にはCFontDialogというコモンダイアログを利用する方法もあります。
目次に戻る

 
◆ IE拡張メニュー
インターネットエクスプローラはレジストリへの登録によってコンテキストメニュー (右クリックメニュー)の項目追加が行えます。

以下のようにレジストリを登録してインターネットエクスプローラ上に表示された 画像を右クリックすると、「画像を処理...」というメニュー項目がコンテキストメニュー に追加され、その項目を実行するとスクリプト c:\hoge\script.htm が実行されます。 HKEY_CURRENT_USER +----Software +----Microsoft +----Internet Explorer +----MenuExt +----画像を処理... (標準) "file://c:\hoge\script.htm" contexts 0x00000002 Flags 0x00000000 このIE拡張メニューを簡単に登録・削除する関数を作ってみました。
これです。
目次に戻る

 
◆ インターネット上のファイルを取得する
インターネット関連のプログラムは winsock API を使ってガリガリ組まないと いけないんだろうなぁ。簡単にアクセスできるようなクラスは VC++6.0 以降でも 買わないとないんだろうなぁ…。と悶々としていたら、wininet API というものが あることを知りました!

これはインターネットエクスプローラ3.0以降をインストールするともれなく付いてくる wininet.dll が提供してくれる API で、HTTP や FTP といったよく使われるプロトコルだけに 対応した関数しかないようですが、それでも十分です。 wininet.dll の詳細は検索サイトなどで検索してご覧ください。

DLL なので VC++4.0 の環境でももちろん使用でき(
DLLのアクセス参照)、 早速インターネット上のファイルをダウンロードする関数を作ってみました。 これです。

まだまだ VC++4.0 でいけるじゃん!!
と思ったら、VC++.NETって Windows98 にはインストールできないのね…。
目次に戻る

 
◆ ファイル・フォルダの取り扱い
ファイルやフォルダの削除、移動、コピーおよび名前の変更方法は以下の通りです。 // ファイルのコピー BOOL flag, ret; flag = TRUE; // コピー先に同名ファイルがあればコピーしないで失敗を戻す flag = FALSE; // コピー先に同名ファイルがあれば上書きコピーして成功を戻す BOOL ret = CopyFile( "ExistingFileName", "NewFileName", flag ); // ret == FALSE でエラー // ファイル・フォルダ(サブフォルダ含む)の移動および名前の変更 // この関数で移動と名前変更ができますが // ドライブをまたがるフォルダ移動はできません。 BOOL ret = MoveFile( "ExistingFileName", "NewFileName" ); // ret == FALSE でエラー // ファイルの削除 BOOL ret = DeleteFile( "FileName" ); // ret == FALSE でエラー // フォルダの削除(フォルダが空でないと失敗します) BOOL ret = RemoveDirectory( "PathName" ); // ret == FALSE でエラー フォルダのコピーはシェル関数 SHFileOperation( ) を使います。 エクスプローラが行うファイル処理を実行するAPIで、フラグにより確認ダイアログの 表示なども自動的にしてくれます。下はこれを使ってフォルダのコピー関数を作成してみたものです。 #include <windows.h> // フォルダのコピー // [引数] // char *src; // コピー元フォルダ名 // char *dst; // コピー先フォルダ名 // ※いずれも存在フォルダを指定すること // HWND hwnd; // この関数を実行するウインドウのハンドル BOOL CopyDirectory( char *src, char *dst, HWND hwnd = NULL ) { if( strcmp( src, dst ) == 0 ) return( FALSE ); char szzSrcDir[MAX_PATH], szzDstDir[MAX_PATH]; ZeroMemory( szzSrcDir, MAX_PATH ); ZeroMemory( szzDstDir, MAX_PATH ); strcpy( szzSrcDir, src ); strcpy( szzDstDir, dst ); SHFILEOPSTRUCT FileOp; FILEOP_FLAGS flag = FOF_NOCONFIRMATION|FOF_NOCONFIRMMKDIR|FOF_SILENT; ZeroMemory( &FileOp, sizeof(SHFILEOPSTRUCT) ); FileOp.hwnd = hwnd; // 実行するウインドウのハンドル FileOp.wFunc = FO_COPY; // コピーを指定 FileOp.pFrom = szzSrcDir; // 二つのNULLで終端されたコピー元フォルダ名 FileOp.pTo = szzDstDir; // 二つのNULLで終端されたコピー先フォルダ名 FileOp.fFlags = flag; // ダイアログは表示しない int ret = SHFileOperation( &FileOp ); if( ret ) return( FALSE ); else return( TRUE ); } SHFileOperation( )関数はコピーの他にも削除、移動、リネームができます。
削除の場合は pFrom メンバに削除したいファイル名を指定します( pTo は無視されます)。 またフラグに FOF_ALLOWUNDO を指定して削除するとゴミ箱に入ります。
ダイアログを表示させるなど詳細は SHFILEOPSTRUCT のヘルプを参照されたし。

フォルダのコピーは DOSコマンドの xcopy を使用した方が遙かに早いという報告もあります。
ShellExecute( )CreateProcess( )で 実行できます。
xcopy の使い方はコマンドラインで xcopy /? |more とやってみて調べてください。
目次に戻る

 
◆ サブスレッドを作成する
まずはおさらい。
タスクとプロセスという言葉は同じ意味で使用されています。また一つのプロセスは少なくとも一つの実行単位である 「メインスレッド」を持ち,必要に応じてサブスレッドを作成することができます。Windowsはプロセス単位に メモリを割り当て、そのプロセスに属するスレッドを実行します。Windows以外のOSではスレッドという概念が 存在せず,タスク=実行単位というものもあるので私は混乱していました。

さてメインスレッドはCWinAppを作成した時点で自動的に作成されていますが(CWinAppはCWinThreadの派生クラス), サブスレッドの関数は以下のように作成します。 // サブスレッドの作成 UINT SubThreadFunction( LPVOID pParam ) { BOOL *pFlag = (BOOL *)pParam; // 時間のかかる処理loop { // キャンセル要求の確認 if( *pFlag == TRUE ) break; } // サブスレッド終了 return( 0 ); // 戻り値は好きに定義できる } サブスレッドには32ビットの変数を引き渡せます。キャンセルフラグへのアドレスなどを渡すと良いでしょう。
サブスレッドの終了はサブスレッド自身が return( ) します。AfxEndThread( ) を実行する方法も ありますが、実行するとすぐにスレッドが廃棄されてローカルのクラスオブジェクトの デストラクタが実行されずメモリリークすることがあるので注意してください。

あとはメインスレッドのサブスレッドを実行したい場所で AfxBeginThread( (AFX_THREADPROC)SubThreadFunction, (LPVOID)&Flag ); とすればサブスレッドがすぐに作成されて実行されます。
終了を待ったり一時停止させたりなど,より高度なコントロールをしたい場合はCreateThread( )を 使います。AfxBeginThread( )と併せてヘルプを見てください。

これら一連の流れのサンプルソースは
ここです。

注意点としては,MFCの制限でサブスレッドからメインスレッドのCWnd派性クラスを 操作できないらしいです。サブスレッド実行中にメインスレッドの操作をしたい場合は、 サブスレッドからメインスレッドへウインドウメッセージを送信すると良いでしょう。

またサブスレッドで既存ファイルを上書きオープンするか新規ファイルを作成するかしても スレッド終了後に消えてしまう?という不可解な現象に遭遇したことがあります。謎です。
目次に戻る

 
◆ 他のアプリケーションを実行する
他のアプリケーションを実行するには ShellExecute( ) が使えます(
ここ参照)が、 コマンド名とオプションを別々に与えなければならずちょっと面倒くさいので、 コマンドライン文字列をそのまま実行できる CreateProcess( ) がお勧めです。 PROCESS_INFORMATION pi; STARTUPINFO si; ZeroMemory( &si, sizeof( STARTUPINFO ) ); si.cb = sizeof( STARTUPINFO ); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOWNORMAL; char Command[] = "C:\\WINDOWS\\Notepad.exe"; BOOL ret = CreateProcess( NULL, Command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ); if( ret == FALSE ) AfxMessageBox( "外部ツールの起動に失敗しました" ); else { if( 終了を待つ場合 ) WaitForSingleObject( pi.hProcess, INFINITE ); // スレッドハンドルとプロセスハンドルの解放 CloseHandle( pi.hThread ); CloseHandle( pi.hProcess ); } なおペイントブラシ(C:\WINDOWS\PBRUSH.EXE)を上記方法で終了を待ってもうまく行きません。 終了しないうちに WaitForSingleObject( ) から戻ってきてしまいます。 これは C:\WINDOWS\PBRUSH.EXE は C:\PROGRAM FILES\ACCESSORIES\MSPAINT.EXE を呼んで すぐに終了しているからです。
目次に戻る

 
◆ ファイルの関連付け
ファイルをダブルクリックすると拡張子に応じたアプリケーションが起動する「関連付け」を アプリケーションから設定するにはレジストリを操作する必要があります。

以下のようにレジストリツリーを構成すると拡張子".aaa"に対するアイコンの設定と 関連付けが行えます。regedit.exe で他の拡張子の場合と比べてみてください。 HKEY_CLASSES_ROOT +----.aaa (標準) "aaa_auto_file" | ~ | +----aaa_auto_file (標準) (値の設定なし) | +----DefaultIcon (標準) "c:\windows\notepad.exe,1" | +----shell (標準) (値の設定なし) | +----open (標準) (値の設定なし) | +----command (標準) "c:\windows\notepad.exe "%1"" レジストリを操作する関数群は以下の通り。 レジストリキーの作成(オープン): RegCreateKeyEx( ) レジストリキーのオープン: RegOpenKeyEx( ) レジストリ項目の読み出し: RegQueryValueEx( ) レジストリ項目の書き込み: RegSetValueEx( ) レジストリ項目の削除: RegDeleteValue( ) レジストリキーのクローズ: RegCloseKey( ) レジストリキーの削除: RegDeleteKey( ) サンプルソースは
ここです。


【レジストリ追加TIPS】

★CRegKey
VC++4.0でレジストリを操作するには上記 RegCreateKeyEx( ) とか RegOpenKeyEx( ) とかを シコシコ呼ばねばならなかったのですが,新しいCRegKey という便利なクラスが追加されていました。ぐはー。

★SDI/MDIアプリケーション
MFCアプリケーションウィザードでSDI/MDIアプリケーションを作成した場合, 以下のレジストリがデフォルトで使用されます。

・プロジェクトの保存フォルダにある [Project名].reg に記載されたレジストリ。
・HKEY_CURRENT_USER\Software\アプリケーション ウィザードで生成されたローカル アプリケーション


前者はドキュメントの拡張子に対応したものです。
後者は「最近使ったファイル」が格納されるレジストリで,アプリケーション固有の設定を保存するためにも使用されます。

デフォルトのままでは情けないのでキー名を会社やプロジェクト名に変更してください。 CXXXXApp::InitInstance() 内に TODO: があります。

このキーに各種設定などを保存したり読み出したりするには,以下のようにします。 // 数値の読み書き int n = AfxGetApp()->GetProfileInt( "SETUP", "ID", 0 ); AfxGetApp()->WriteProfileInt( "SETUP", "ID", 1 ); // 文字列の読み書き CString str = AfxGetApp()->GetProfileString( "HISTORY", "LOG", "default" ); AfxGetApp()->WriteProfileString( "HISTORY", "LOG", "test" );
目次に戻る

 
◆ ウインドウをフォアグラウンドへ
ウインドウを最前面に持ってくるのは SetForegroundWindow( ) で可能 …だったのですが、 Windows98以降ではタスクバーが点滅するだけで最前面には来なくなってしまいました。

しかし以下の関数を実装すれば可能となります。 // ウィンドウをフォアグラウンドにする(Win98対応) BOOL SetForegroundWindow98( HWND hWnd ) { DWORD target, foreground; BOOL ret; foreground = GetWindowThreadProcessId( GetForegroundWindow( ), NULL ); target = GetWindowThreadProcessId( hWnd, NULL ); AttachThreadInput( target, foreground, TRUE ); ret = SetForegroundWindow( hWnd ); AttachThreadInput( target, foreground, FALSE ); return( ret ); }
目次に戻る

 
◆ ウインドウメッセージ
ウインドウ(CWnd派生クラス)は全てウインドウメッセージというものを基本に動作しています。 ウインドウの作成、描画、キー入力、マウスの移動、クリック、全てです。 これらのメッセージは WM_???? というように c:\msdev\include\winuser.h で全て定義されています。
MFCが登場する前はこのウインドウメッセージを直接処理してWindowsプログラミングをしていました。

MFCではウインドウメッセージを直接扱わなくてもある程度のプログラミングができますが、 アプリケーション間で情報をやりとりしようとしたり、仮想的なキー入力を発生させようとしたりするには やはりこのウインドウメッセージを使う必要が出てきます。逆に言えば、ウインドウメッセージを使えば ウインドウの全てを制御できると言っても過言ではありません。

ウインドウメッセージを送信する関数は SendMessage( ), または PostMessage( ) です。 受信(通知されたメッセージを元に動作を行う)処理は基本的に WindowProc( ) です。 メッセージの中には上位で処理されて WindowProc( ) には降りてこないものもあるので、 その時は PreTranslateMessage( ) を使えば全てのメッセージの通知を取得することができます。
また親ウインドウに通知したメッセージを処理するには OnChildNotify( ) が使えます。 MFCコントロールの派生クラスをクラスウィザードを使わずに作成した場合、派生クラス側で メッセージを処理する際に使うことになります。
これらの関数は全て CWnd のメンバ関数です。詳細はヘルプなどを参照してください。

さて少しハードですが、以下にダイアログアプリケーション同士で通信するサンプルを示します。

前準備として受信側ダイアログアプリケーションの識別用キーワードをダイアログに 埋め込みます。リソースエディタで受信側のメインダイアログ上にスタティックテキストを 作成します。ID は IDC_USER_DIALOG_KEY でキャプションは USER_DIALOG_KEY とでもして おきます。このキャプションが受信相手を特定するキーワードになるのでできるだけユニークな ものにすると良いです。なおこのスタティックテキストはプロパティで可視オプションを外して 非可視にしておくのと無効オプションを付けて無効にしておくのを忘れなでください。

次に通信メッセージのIDを定義します。システムで用いられているIDと衝突しないように WM_APP+n という値を使用します。 // 送信側ダイアログ CSendDlg.h および受信側ダイアログ CRecvDlg.h // メッセージIDを定義する #define USER_MESSAGE (WM_APP+100) さて今度は実際にメッセージを送信するタイミングでのコーディングです。

まず送信側ダイアログが受信側ダイアログのウインドウハンドルを調査します。 一般的には FindWindow( ) 関数を使用しますが、ダイアログベースのアプリケーションは 検索できないので (FindWindow( )の引数で与えるクラス名としてダイアログクラス名を使うと 検索してくれないようです。かといってタイトルで検索しようにも不定の場合どうしようも ありません。)、ここでは EnumWindows( ) を用います。

EnumWindows( ) は全ての起動中ウインドウを検索し引数で与えられるコールバック関数を実行します。 コールバック関数は検索されたウインドウハンドルを与えられるので、それが期待するウインドウか どうか調査して次のウインドウを再検索するか検索を終了するかを戻り値として返します。 調査では前準備で埋め込んでおいたキーワードが存在するかをチェックします。 // 送信側ダイアログ CSendDlg.cpp // EnumWindows( )関数用コールバック関数のプロトタイプ宣言 BOOL CALLBACK EnumWindowsProcSingle( HWND hWnd, LPARAM param ); // 送信側ダイアログ CSendDlg.cpp // 実際に送信を行うタイミング 〜前略〜 // EnumWindows( )で受信側のウインドウハンドルを検索する BOOL Ret; EnumWindows( EnumWindowsProc, (LPARAM)&Ret ); if( Ret == TRUE ) // 受信側に送信完了; else // 受信側が見つからなかった; 〜後略〜 // 送信側ダイアログ CSendDlg.cpp // ウインドウハンドル検索用コールバック関数 BOOL CALLBACK EnumWindowsProc( HWND hWnd, LPARAM param ) { BOOL *pRet = (BOOL *)param; *pRet = FALSE; // 検索されたウインドウで // キーワードを埋め込まれたオブジェクトがあるか調査する CWnd *pDlg = CWnd::FromHandle( hWnd ); CWnd *pItem = pDlg->GetDlgItem( IDC_USER_DIALOG_KEY ); // オブジェクトがなければ次のウインドウを検索 if( pItem == NULL ) return( TRUE ); // キーワードの確認 CString Key; pItem->GetWindowText( Key ); // 受信ダイアログに埋め込んだものと異なれば次のウインドウを検索 if( Key != "USER_DIALOG_KEY" ) return( TRUE ); // 受信側が識別できたのでメッセージを送信する WPARAM wparam = 通知したい任意の値1; LPARAM lparam = 通知したい任意の値2; SendMessage( hWnd, USER_MESSAGE, wparam, lparam ); *pRet = TRUE; return( FALSE ); // 送信完了したので検索終了 } 受信側では、クラスウィザードでウインドウプロシージャ WindowProc( ) 関数を 実装(オーバーライド)して、メッセージを以下のように受け取ります。 // 受信ダイアログ CRecvDlg.cpp // ウインドウメッセージハンドラ LRESULT CRecvDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if( message == USER_MESSAGE ){ 知りたい値1 = wParam; 知りたい値2 = lParam; 任意の処理( ); } return CDialog::WindowProc(message, wParam, lParam); } 以上で一連の送信・受信が完了しました。

受信処理が終了するまで送信側の SendMessage( ) 関数は戻ってきません。送信だけして受信処理が 終わらなくても構わない場合は代わりに PostMessage( ) 関数を使うと良いでしょう。 ただしその場合の受信は WindowProc( ) 関数でなく、任意のタイミングで GetMessage( ) 関数 またはPeekMessage( ) 関数を呼び出して能動的に受信しにいかなければなりません。

さらに応用です。アプリケーション間で通信を行う場合、その情報として WPARAM と LPARAM 型の パラメータをやりとりできましたが、もっと大きなメモリの内容を相手に伝えることは できるでしょうか?

LPARAM に送信側ワークエリアのアドレスをセットすればうまくいきそうですが、実際は期待通り 動作してくれません。Win32ではプロセス空間がアプリケーション毎に分離されているので 単純にアドレスをパラメータとして SendMessage( ) しても受信側がそのアドレスを見ると なんだか別物なのです…。

では完全に不可能なのかというとそうではなくて、実は WM_COPYDATA というメッセージが用意されて おり、メモリ内容のコピーを送信することは可能なのです。 // 送信側 char szBuf[MAX_PATH]; // コピーしたいバッファ strcpy( szBuf, "Test" ); // バッファ内容を任意にセット DWORD type = 0; // バッファ内容のタイプを任意に指定 COPYDATASTRUCT cds; cds.dwData = type; // バッファ内容のタイプ cds.cbData = strlen( szBuf ) + 1; // バッファ内容サイズ // (テキストの場合NULL文字も数に入れる) cds.lpData = (PVOID)szBuf; // バッファ先頭アドレス SendMessage( hWnd, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds ); // 受信側ウインドウメッセージハンドラ LRESULT CRecvDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if( message == WM_COPYDATA ){ COPYDATASTRUCT *pcds = (COPYDATASTRUCT *)lParam; DWORD type = pcds->dwData; DWORD size = pcds->cbData; char *pBuf = (char *)pcds->lpData; // type 別に size 分の pBuf 内容を処理する } return CDialog::WindowProc(message, wParam, lParam); } あとこれは完全に抜け道ですが、ダイアログの識別に使った非可視のスタティックテキストは、 なんと送・受信両側からGet/SetWindowText( )ができるので、ちょっとしたテキストであれば 共有することが可能です。が、正規の使用法でないと思うので保証はしません。

ウインドウメッセージを使えば何でもできると書きましたが、例えばキーが押されていないのに ウインドウにキーが押されたと勘違いさせることができます。 これは、エディットボックスに長い文字列をセットすると右端が見えなくなってしまいますが、 Endキーが押されたかのようにウインドウメッセージを送信して右端を表示させるなど便利に使えます。 GetDlgItem( IDC_EDIT )->SendMessage( WM_KEYDOWN, (WPARAM)VK_END, (LPARAM)0 ); // ※リソースエディタでエディットボックスのスタイルの // 水平オートスクロールを有効にしておく
目次に戻る

 
◆ 二重起動の禁止
アプリケーションが二重に起動しないようにするには既に起動しているという 証を作成するMutexを使用します。Mutexの作成は何らかのキーワード文字列を用いて行います。 アプリケーションの最初で以下の処理を実装してください。 // キーワードとして実行ファイルのパスを使用 char szMutexKey[MAX_PATH]; GetModuleFileName( NULL, szMutexKey, MAX_PATH ); // 作成していないのにMutexをオープンできれば既に起動されている HANDLE hMutex; hMutex = OpenMutex( MUTEX_ALL_ACCESS, FALSE, szMutexKey ); if( hMutex ){ CloseHandle( hMutex ); // 既アプリをアクティブにする処理など // 終了 return FALSE; } // オープンできなければ未作成つまり未起動状態なので // 起動している証としてミューテックスを作成する hMutex = CreateMutex( FALSE, 0, szMutexKey ); // 〜メイン処理開始〜 // 終了処理でミューテックスを開放 ReleaseMutex( hMutex );
目次に戻る

 
◆ 特殊フォルダへのパス
ウインドウズディレクトリやシステムディレクトリはインストールされている環境や 95系やNT系で異なります。したがって"c:\\windows\\system"などと決め打ちはできません。

このような特殊フォルダへのパスは以下のようにして取得することができます。 char szPath[MAX_PATH]; // 取得するバッファ // カレントディレクトリの取得 GetCurrentDirectory( MAX_PATH, szPath ); // システムディレクトリの取得 GetSystemDirectory( szPath, MAX_PATH ); // ウインドウズディレクトリの取得 GetWindowsDirectory( szPath, MAX_PATH ); // テンポラリディレクトリの取得 (これだけ文字列末に\がつくことに注意) GetTempPath( MAX_PATH, szPath ); // デスクトップディレクトリの取得 #include <winnetwk.h> // MFCの場合、#include "stdafx.h" より後で記述すること #include <shlobj.h> LPMALLOC pMalloc; if( SHGetMalloc( &pMalloc ) == NOERROR ){ ITEMIDLIST *pidl; if( SHGetSpecialFolderLocation( NULL, CSIDL_DESKTOPDIRECTORY, &pidl ) == NOERROR ){ SHGetPathFromIDList( pidl, szPath ); } if( pidl ) pMalloc->Free( pidl ); pMalloc->Release( ); } デスクトップディレクトリなど仮想的なフォルダを取得するにはシェルの提供する関数 SHGetSpecialFolderLocation( )と、シェルが管理するメモリ領域(IMallocインターフェイス)を 使用する必要があります。詳細は同関数のヘルプを参照してください。 この方法でデスクトップ以外にもマイコンピュータフォルダやプリンタフォルダなどに アクセスできます。
目次に戻る

 
◆ 実行ファイルの存在するディレクトリ
カレントディレクトリではなく実行ファイルの存在ディレクトリを調査するには GetModuleFileName( )関数を使います。この関数は実行ファイル名のドライブ名から拡張子まで フルパスで取得できてしまうので、ディレクトリだけ抽出するために_splitpath( )関数を実行します。 // インストールフォルダの取得 char szPath[_MAX_PATH]; GetModuleFileName( NULL, szPath, _MAX_PATH ); // フルパスを分解 char szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szName[_MAX_FNAME], szExt[_MAX_EXT]; _splitpath( szPath, szDrive, szDir, szName, szExt );
目次に戻る

 
◆ DLLを作成する
メッセージボックスを表示するDLLを作成してみます。

ファイル-新規作成-プロジェクトワークスペースでDynamic-Link Libraryを選択します。 ファイル-新規作成-テキストファイルでDLLのソースファイルを作成します。 以下の記述を行います。 #include <windows.h> extern "C" __declspec(dllexport) void DisplayMessage( LPCTSTR lpszMessage ) { ::MessageBox( NULL, lpszMessage, "DLLTEST", MB_OK ); } このファイルをdlltest.cppなどとして保存します。 また右クリックメニューで「プロジェクトへファイルの挿入」を行います。 あとはビルドしてDLLファイルの完成です。
次にこのDLLを実行するプログラムを作成します。

ファイル-新規作成-プロジェクトワークスペースでConsole Applicationを選択します。 ファイル-新規作成-テキストファイルでソースファイルを作成します。 以下の記述を行います。 #include <windows.h> typedef void (*DISPLAYMESSAGE)( LPCTSTR ); void main( void ) { HINSTANCE hDllInstance; DISPLAYMESSAGE DisplayMessage; hDllInstance = LoadLibrary( "..\\dlltest\\debug\\dlltest.dll" ); if( hDllInstance != NULL ){ DisplayMessage = (DISPLAYMESSAGE)GetProcAddress( hDllInstance, "DisplayMessage" ); if( DisplayMessage != NULL ) DisplayMessage( "DLLEXE!!" ); FreeLibrary( hDllInstance ); } } このファイルをdllexe.cppなどとして保存します。 また右クリックメニューで「プロジェクトへファイルの挿入」を行います。 あとはビルドして実行すればDLLの実行が行えます!!

やってみたら結構簡単で感激です!!!! (T_T)
目次に戻る

 
◆ DLLのアクセス
DLLの関数を自分のプログラムから実行するには以下のように行います。 分かってみると結構簡単だった。 /* Susieプラグインの使用例 */ /* ヘッダファイルなどでDLL内の関数定義を宣言 */ typedef int (WINAPI *GetPluginInfo)( int, LPSTR, int ); /* 〜 */ /* DLLのロード */ HINSTANCE hInst; hInst = LoadLibrary( "filname.dll" ); /* 〜 */ /* DLL関数の実行 */ char str[256]; GetPluginInfo proc; proc = (GetPluginInfo)GetProcAddress( hInst, "GetPluginInfo" ); if( proc != NULL ){ proc( 1, str, 256 ); ::MessageBox( NULL, str, NULL, NULL ); } /* 〜 */ /* DLLの開放(忘れずに!!) */ FreeLibrary( hInst );
目次に戻る

 
◆ ログインユーザ名の取得
ログインユーザ名の取得は以下のようにすれば行えます。 char szName[256]; DWORD dwSize = 256; GetUserName( szName, &dwSize );
目次に戻る

 
◆ フォルダ(ディレクトリ)の確認と作成
フォルダの存在確認を_chdir( )で行い、エラーがあれば存在しないフォルダということで _mkdir( )を実行してフォルダの新規作成が行えます。以下にサンプルを表示します。 #include <direct.h> CString rDir; rDir = "c:\\tmp"; int nChkDir = _chdir( (LPCSTR)rDir ); if( nChkDir == -1 ) _mkdir( (LPCSTR)rDir ); 上記の例ではディレクトリが存在するときカレントディレクトリが変わってしまうので気を付けてください。
ディレクトリの存在確認だけだったら
ここをご覧ください。
目次に戻る

 
◆ アプリ終了方法
任意のタイミングで自分自身を終了させたい場合は以下のようにします。 this->SendMessage( WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL ); また自分以外の他のアプリを終了させたい場合は ::SendMessage( hWnd, WM_CLOSE, (WPARAM)NULL, (LPARAM)NULL ); 対象となるウインドウのハンドラを FindWindow( ) や EnumWindow( ) で 探しておく必要があります。EnumWindow( ) の使用例は
ここにあります。
目次に戻る

 
◆ メモリリークの調査法
自作アプリケーションを作成していて一番心配なのはメモリリークです。
MFCを使用してデバグ実行を終了させると、メモリリークがあれば以下のように表示されます。 Detected memory leaks! Dumping objects -> {35} normal block at 0x007408C0, 630 bytes long. Data: <<!DOCTYPE html p> 3C 21 44 4F 43 54 59 50 45 20 68 74 6D 6C 20 70 Object dump complete. 上記の{35}というのがメモリ割り当て番号なので、この割り当てが行われた時に ブレークできれば具体的にどのメモリがリークしたか分かります。 ブレークポイントを設定するには、割り当てが行われるよりも前の位置(プログラム最初の方であれば どこでも良いです)で、 _CrtSetBreakAlloc( 35 ); と一行入れておくだけで可能です。
そうしてデバグ実行させるとリークするメモリが割り当てられたときにブレークするので、 表示メニューのコールスタックウインドウを使ってメモリの割り当てに至るまでの流れを 追うことができます。

(VC7)
デバグ実行してメモリリークが発生した場合,その旨を出力ウインドウに表示してくれるのですが, そこにソースファイル名があればダブルクリックしてみてください。なんとそのメモリを確保した 場所にジャンプしてくれます。これはスゴイ!!


目次に戻る

 
◆ ファイル・フォルダの検索
ある特定のファイルを検索する関数は以下の通りです。引数にフルパスを指定して、ファイルが 存在すればTRUEを、存在しなければFALSEを戻します。 // ファイルの検索 BOOL FileSearch( char *pFilename ) { HANDLE hnd; WIN32_FIND_DATA data; hnd = FindFirstFile( pFilename, &data ); if( hnd != INVALID_HANDLE_VALUE ){ FindClose( hnd ); return( TRUE ); } else return( FALSE ); } さらに例えば拡張子BMPを持つファイルを複数検索するには以下のように行います。 /* c:\windows\*.bmp を検索(ワイルドカードOK) */ HANDLE hFile; // 検索ハンドル WIN32_FIND_DATA data; // 発見ファイル情報を受ける変数 /* 検索実行 */ hFile = FindFirstFile( "c:\\windows\\*.bmp", &data ); if( hFile != INVALID_HANDLE_VALUE ){ do{ // 見つかった // ファイル名は data.cFileName に書かれている } while( FindNextFile( hFile, &data ) ); // 次のファイルを検索 FindClose( hFile ); // ハンドルの後始末 } サブフォルダ以下も探すには,再帰的に検索を行う必要があります。

ここに ファイルやフォルダ関連の便利な関数を用意してみました。 以下のような関数があります。 フォルダの検索 BOOL FolderSearch( char *pFolder ); ファイルの検索 BOOL FileSearch( char *pFilename ); ファイルの再帰的検索 BOOL RecursiveSearchFile( char *filename, char *dir, char *fullpath ); フォルダ名の取得 BOOL GetFolderName( char *pPath, char *pFolderName ); ファイル名のみの取得 char* GetElementName( char *pPath ); 末尾の'\'を削除 BOOL RemoveTailSeparater( char *pPath );
目次に戻る

 
◆ INIファイル
Microsoftは設定値などの保存をレジストリに行うよう指示していますが個人的にはなんかイヤです。 レジストリの量が増えることも理由の一つですが、再インストール時や他のPCに環境をコピーする 時に、アプリケーションの設定値がアプリのインストールフォルダにINIファイルとして存在すれば そのフォルダごとコピーするだけで復帰が済むからです。そのアプリが使用しているレジストリを 調査してレジストリを書き出して…なんてやってられません。 アンインストールの時も使用しているフォルダを丸ごと削除で簡単です。

というわけで設定値をレジストリでなく「適当なフォルダ」のINIファイルに保存するには, 以下のようにすれば簡単にできます。構文解析しなくて済むので楽ですね! /* 必要な変数の宣言 */ char dir[256], inifile[256], buf[256]; /* INIファイル名の調査 */ GetCurrentDirectory( MAX_PATH, dir ); wsprintf( inifile, "%s%s", dir, "\\setup.ini" ); /* INIファイルから設定の取得 */ GetPrivateProfileString( "APPNAME", "KEY", "128", buf, 256, inifile ); /* 設定値の反映 */ m_EditBox.SetWindowText( buf ); /* 〜 */ /* 設定値の取得 */ m_EditBox.GetWindowText( buf, 256 ); /* INIファイルへの保存 */ WritePrivateProfileString( "APPNAME", "KEY", buf, inifile ); 構造体データをそのままINIファイルに書き出したり読み込んだりする関数 WritePrivateProfileStruct( )やGetPrivateProfileStruct( )もあるようです。 設定値などを構造体で管理している場合、こちらの方が一発で簡単だけど、 バージョンアップ等で構造体の構造が変わってしまった場合など、以前に 保存した設定が受け継がれなくなってしまうのでプログラマが便利な分、 使用者が不便になってしまいます。

INIファイルを読み書きする同様な関数、AfxGetApp( )->WriteProfileString( ) と AfxGetApp( )->WriteProfileInt( ) は、INIファイルの保存先が c:\windows\アプリ名.ini になってしまいます。また同じく ::WriteProfileString( ) は c:\windows\win.ini 内に データを記述するので、これらを使うくらいならレジストリに保存した方がマシです。
目次に戻る

 
◆ コマンドラインパラメータの取得
コマンドラインパラメータの取得は簡単。
argc, argv が __argc, __argv という形でそのまま使用できます。 サンプルコードは以下です。 #include <stdlib.h> // デフォルト値のセット int nFlag = FALSE; int nType = 0; CString Filename = "c:\\test.dat"; // オプション解析 for( int i=1; i<__argc; i++ ){ // フラグ if( strcmp( __argv[i], "/F" ) == 0 || strcmp( __argv[i], "/f" ) == 0 ){ nFlag = TRUE; } // タイプ else if( strcmp( __argv[i], "/T" ) == 0 || strcmp( __argv[i], "/t" ) == 0 ){ i++; if( i >= __argc ) break; nType = atoi( __argv[i] ); } // ファイル名 else { Filename = __argv[i]; } }
目次に戻る

 
◆ RS232C
MFC App Wizardでプロジェクトを作成した場合,デフォルトでRS232C関連のヘッダファイルが 読み込まれないので(コンパイル時間を短縮するためだと思う),プロジェクト内のstdafx.hで 定義されている#define VC_EXTRALEANをコメントアウトする。 /* 必要な変数 */ HANDLE mHdl; DCB mDCB; COMMTIMEOUTS mTimeout; COMSTAT mStat; unsigned long mErrors; unsigned char RecvBuffer[512]; /* 初期化 */ mHdl = CreateFile( "COM1", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); GetCommState( mHdl, &mDCB ); BuildCommDCB( "baud=9600 parity=N data=8 stop=1", &mDCB ); mDCB.fRtsControl = RTS_CONTROL_DISABLE; mDCB.fOutxDsrFlow = FALSE; mDCB.fDsrSensitivity = FALSE; mDCB.fAbortOnError = FALSE; SetCommState( mHdl, &mDCB ); GetCommTimeouts( mHdl, &mTimeout ); mTimeout.ReadIntervalTimeout = MAXDWORD; mTimeout.ReadTotalTimeoutMultiplier = 1; mTimeout.ReadTotalTimeoutConstant = 500; mTimeout.WriteTotalTimeoutMultiplier = 1; mTimeout.WriteTotalTimeoutConstant = 500; SetCommTimeouts( mHdl, &mTimeout ); /* 送信例 (TESTという文字列と復帰改行コードを送信する)*/ unsigned long n; WriteFile( mHdl, "TEST\n\r", 6, &n, NULL ); Sleep( 1000 ); /* 1000ms sleep */ /* 受信例 (100ms毎に受信バッファを調査し,128バイト分読み込んだら終了) */ long i; i = 0; for( ; ; ){ ClearCommError( mHdl, &mErrors, &mStat ); if( mStat.cbInQue > 0 ){ ReadFile( mHdl, RecvBuffer, mStat.cbInQue, &n, NULL ); RecvBuffer[mStat.cbInQue] = 0; printf( "%s", RecvBuffer ); i += mStat.cbInQue; if( i > 128 ) break; } Sleep( 100 ); } /* 終了処理 */ CloseHandle( mHdl );

[追加情報] Microsoft Comm Control というコンポーネントを挿入することで, 簡単にRS232C制御が可能という情報あり!

…だったが,どこにも資料が見つからないので RS232C制御するクラスを自分で作ってしまいました。
これ です。 といっても上記のコードをラッピングしただけですけど。
目次に戻る

 
◆ ウェブページを表示する
あるボタンを押すと、いわゆるインターネットブラウザが起動して作者のホームページ を表示する。こうするとできます。 ShellExecute( NULL, NULL, "http://www.hoge.ne.jp", NULL, NULL, SW_SHOWNORMAL ); またローカルディスクにあるhtmlで書かれたヘルプファイルや、テキストファイルや その多諸々、関連づけられたファイルを開くにもこの方法が使えます。上記のURLの 部分をフルパスのファイル名にすればOKです。 以下の例は、プログラムがインストールされたディレクトリにある、manual.htmlという ファイルを開く例です。 // プログラムを実行したディレクトリを取得 char pszCurDir[256]; GetCurrentDirectory( 255L, pszCurDir ); char manual[256]; // ファイル名構築用バッファ sprintf( manual, "file://%s\\%s", pszCurDir, "manual.html" ); // ファイル名を指定して実行 ShellExecute( NULL, NULL, manual, NULL, NULL, SW_SHOWNORMAL ); ただしこの方法は、関連づけがユーザによっているので、必ずしもhtmlファイルなら ネットスケープが立ち上がるとは限りません。イソターネットエクスプローラかも しれないし、メモ帳かもしれません…。
またここでファイル名でなくプログラム名がフルパスで記述されると、 そのプログラムが起動します。便利ですねー(笑)。 エクスプローラの起動例 ShellExecute( NULL, NULL, "explorer.exe", "/e,c:\\windows", "c:\\windows", SW_SHOWNORMAL ); なお指定のファイルが無いなど正常に実行できなかった場合は ShellExecute( ) の戻り値が32以下だそうです。
目次に戻る




【VC++/MFC関係】

 
◆ CRectの正規化
例えばマウスドラッグで領域を指定する場合などで,CRectの座標が,左(left)>右(right) または 上(top)>下(bottom) に なってしまう場合も多々あると思います。そのまま幅や高さを計算しようとするとマイナス値になってしまいますが, それぞれのメンバの大小を比較して入れ換える処理をベタで実装するのもスマートではありません。

そんな時はCRectのメンバ関数 NormalizeRect() を呼び出せば上記の大小入れ換えをやってくれます(正規化と呼ぶようです)。
目次に戻る

 
◆ データ管理の味方 CArray と CList
SDIやMDIアプリケーションを作成するとき,ドキュメントファイルのフォーマットをどうしようか悩むと思います。 固定長で固定配置のデータなら別にどうってことないのですが,可変長のデータだとどうやってデータを管理しましょうか?

そんなとき強力な味方になってくれるのが CArray と CList です。いろいろ言われている MFC ですが,これがあるおかげで 可変長ドキュメントでもかなり楽チンにプログラミングできます。
簡単に説明すると,CArray と CList は既存の型はもちろん,独自に定義した構造体や共用体などを配列またはリストとして 動的に管理できるようになるものです。

例えば以下のような構造体と共用体を定義したとします。 typedef struct hoge_st { int a, b, c, d; } HOGE_ST; typedef union fuga_un { int a; short b; char c; } FUGA_UN; この型のデータを動的に管理するには,malloc() や new でメモリを確保して,そのアドレスをリンクリストで 管理するのが普通です。削除するときもメモリリークしないように気をつけなければいけませんし, シリアライズするときも一度にはできません。とーっても面倒くさいです。

CArray を使うと,あたかも上記データを要素として持つ「配列」として扱うことができます。 同じように CList を使うと「リスト」として扱えます。また,CArray と CList はいずれもシリアライズ可能なので メンバ関数である Serialize() を実行するだけでファイル入出力ができてしまいます!すばらしい!!

使用サンプルを以下に示します。 #include <afxtempl.h> typedef struct hoge_st { int a, b, c, d; } HOGE_ST; typedef union fuga_un { int a; short b; char c; } FUGA_UN; // オリジナル構造体の配列を確保 CArray<HOGE_ST,HOGE_ST&> HogeArray; // デフォルトでは配列の要素数は1ずつ拡張される HogeArray.SetSize( 5, 10 ); // 要素数と拡張単位数を設定することもできる // 要素にアクセス HOGE_ST *p; p = &HogeArray[0]; p->a = 0xAAAAAAAA; // 途中で要素数の拡大可能 HOGE_ST hoge = {1,2,3,4}; HogeArray.Add( hoge ); INT_PTR n = HogeArray.GetSize(); p = &HogeArray[n-1]; // 全要素を削除 HogeArray.RemoveAll(); // オリジナル共用体のリストを確保 CList<FUGA_UN,FUGA_UN&> FugaList(16); // リスト要素の拡張単位数を最初に設定(デフォルト10) // 要素を登録 FUGA_UN fuga; fuga.a = 0xAAAAAAAA; fuga.b = (short)0xBBBB; fuga.c = (char)0xCC; FugaList.AddTail( fuga ); INT_PTR m = FugaList.GetCount(); // リストの検索 POSITION pos; FUGA_UN f; pos = FugaList.GetHeadPosition(); for( int i=0; i<FugaList.GetCount(); i++ ){ f = FugaList.GetNext(pos); } // 全要素を削除 FugaList.RemoveAll(); なお,ダイアログアプリケーションでは #include <afxtempl.h> しないと使えないようです。
ちなみに CArray<CString,CString&> は CStringArray と,CList<CString,CString&> は CStringList と同等です。
目次に戻る

 
◆ クライアント領域のサイズ
ウインドウのサイズではなくて,クライアント領域のサイズを設定したいときは 以下のようにしています。ダサいですか?(笑) // クライアント領域サイズ設定 CRect WindowRect, ClientRect; this->GetWindowRect( WindowRect ); this->GetClientRect( ClientRect ); int dw = WindowRect.Width() - ClientRect.Width(); int dh = WindowRect.Height() - ClientRect.Height(); this->SetWindowPos( 0, 0, 0, CLIENT_WIDTH+dw, CLIENT_HEIGHT+dh, SWP_NOMOVE ); this->CenterWindow();
目次に戻る

 
◆ 動的に実装したコントロールのイベント処理方法(VC7)
動的に実装したコントロールのイベントを処理するには以下のようにする必要があります。
目次に戻る

 
◆ ドッキングバー(VC7)
IDE(統合開発環境)のようなドッキングバー(ドッキングウインドウ)を簡単に実装するには 以下のサイトのソースコードを利用させてもらうのがいいでしょう。

http://www.datamekanix.com/sizecbar/

個人でも商用でもロイヤリティ無料で利用できるので(ただし著作権は放棄していない)重宝しています。 使用方法は上記のサイトでも解説があったり,アーカイブにデモソースがあったりするので割愛します。 以下はおぼえがき程度の実装メモです。 ・sizecbar-v2.44 のソースとヘッダをプロジェクトに登録します。afxChNil が定義されていないという エラーがコンパイル時に発生するので,sizecbar.cpp に以下の行を追加します。 ///////////////////////////////////////////////////////////////////////////// // "afxChNil" undeclared identifier in VC++.NET 2003 static TCHAR afxChNil = '\0'; ///////////////////////////////////////////////////////////////////////////// ・stdafx.hに以下の行を挿入します。 // ドッキングウインドウの追加 #include "..\sizecbar-v2.44\src\sizecbar.h" #include "..\sizecbar-v2.44\src\scbarg.h" #include "..\sizecbar-v2.44\src\scbarcf.h" ・MainFrm.hでメンバ変数を追加します。 CSizingControlBarCF m_wndXXXXBar; ・MainFrm.cppで以下のように実装します。 // ドッキングウインドウの追加 if( !m_wndXXXXBar.Create( _T("Docking Bar"), this, IDR_CONTROLBAR ) ){ return -1; // 作成できませんでした。 } m_wndXXXXBar.SetSCBStyle( m_wndXXXXBar.GetSCBStyle()|SCBS_SIZECHILD ); m_wndXXXXBar.SetBarStyle( m_wndXXXXBar.GetBarStyle()|CBRS_TOOLTIPS|CBRS_FLYBY|CBRS_SIZE_DYNAMIC ); m_wndXXXXBar.EnableDocking( CBRS_ALIGN_LEFT|CBRS_ALIGN_RIGHT ); // ドッキングを可能にする EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndXXXXBar, AFX_IDW_DOCKBAR_LEFT); あとは,ドッキングウインドウを親として各種コントロールを実装すればよいです。

ドッキングウインドウを親ウインドウとして持つオブジェクトのサイズを調整する場合は, WM_SIZEメッセージハンドラを実装して,そこでMoveWindow( )すれば可能です。 GetClientRect( ) ではドッキングウインドウのサイズをうまく取れないようです。

目次に戻る

 
◆ 印刷プレビューの言語(VC7)
印刷プレビューウインドウのボタンが英語になるのは MFC の DLL に原因があります。

デフォルトでは,プロジェクト → プロパティ → 全般 → MFCの使用 で,「スタティックライブラリでMFCを使用する」 となっていると英語に,「共有DLLでMFCを使用する」となっていると日本語になります。
スタティックライブラリでも日本語にしたい場合は,プロジェクト → プロパティ → リソース → 追加のインクルードディレクトリ に, $(VCInstallDir)\atlmfc\include\l.jpn を追加すると良いです。 この際,プロジェクト → プロパティ での構成を「全ての構成」にすることを忘れないでください。
目次に戻る

 
◆ メニューリソースIDの変更(VC7)
VC++.NET 2003 のリソースビューで Menu を新規追加したのはいいけど, メニューのリソースIDを IDR_MENU1 からどうしても変更できない!

…と思ったときは,ツリー項目の IDR_MENU1 を左クリックしてからおもむろに Alt+Enter を押してみてください。
プロパティウインドウが開いて変更できるでしょう。表示 → プロパティウインドウ でも可。

コンテキストメニュー(右クリックポップアップメニュー)にプロパティが存在しないのは不親切です!!

目次に戻る

 
◆ ダイアログエディタ(VC7)
VC++.NET 2003でもリソースビューでダイアログの挿入をして,ダイアログエディタでボタンなどの コントロールを載せていきますが,そのままでは,そのコントロールに変数を追加できません。 前もって新規ダイアログのクラスを作成しておかなければなりません。
VC4だと自動的にクラスウィザードが起動されたのですが,VC7では手動のようです。なんとなく不親切なんだよな〜。

なお,ダイアログに配置したコントロールの初期化は OnInitDialog() を実装して行ないます。 ダイアログのコンストラクタや OnCreate() ではまだコントロールの実体が生成されていないので エラーになってしまいます。
目次に戻る

 
◆ ステータスバー
SDI や MDI アプリケーションで,ステータスバーにメッセージを表示する方法。

ステータスバーの表示領域を増やすには,MainFrm.cpp の以下の配列で ID_SEPARATOR の行を 単純に増やすだけでできます。 static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_SEPARATOR, // status line indicator ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; ステータスバーにメッセージを表示するには, CMainFrame *pMainFrm = (CMainFrame *)AfxGetMainWnd(); CStatusBar *pStatusBar = (CStatusBar*)pMainFrm->GetMessageBar(); pStatusBar->SetPaneText( n, "message" ); ここで n は領域番号を示します。
目次に戻る

 
◆ スクロール
クライアント領域に何かを描画して,それをスクロールさせたくなったら ダイアログベースアプリケーションから卒業する時期です(笑)。

ダイアログベースはわりと簡単なのでウインドウプログラミングを始めるには とっつきやすいですが,スクロールバーを付けて描画をしようとすると, とたんに難しくなります。ボタンなどはスクロールバーについていきますが, 自分で描画したものは動きません。

ビットマップをメモリ上において,貼り付ける位置をスクロールバーの位置に応じて変える…

なんてことはしなくていいです。知らなかった私はしてしまいましたが。
長いものには巻かれろ,腐ってもMicrosoft, そういう場合はSDIアプリケーションにすれば良かったんです。

最初のAppWizardでViewの基本クラスをCScrollViewにしておけば, スクロールバーの制御を勝手にしてくれるし(単位と範囲の設定は必要), 印刷機能まで付けてくれます。
目次に戻る

 
◆ ブレークポイントで止まらない?
VC++4.0ではプロジェクトが存在するフォルダのパス名に半角スペースが入っていると ブレークポイントを張っても止まらないようです。え?今時4.0なんて使っている 人はいない? う〜,今年こそパソコン買い換えて環境を最新にするぞ!!

目次に戻る

 
◆ カスタムリソース
リソースエディタで登録,編集できるデータはアイコンやメニューだけではありません。 WAVデータなど,その他全てのデータファイルを実行ファイル内に格納し, リソースとして使用することができます。

まず,リソースエディタでリソースのインポートを実行して格納するデータファイルを 指定します。このときインポートするときに用途を「カスタム」にするとどんなファイルでも リソースとすることができます。

次にプログラム中からリソースのアクセスですが,WAVファイルをリソースとした場合に そのデータのアドレスを取得して再生するというサンプルを以下に示します。 #include <windows.h> #include "resource.h" #pragma comment( lib, "winmm.lib") void main( void ) { /* リソースからWAVデータを読み込む */ HRSRC hRes = FindResource( NULL, (const char *)IDR_WAVE1, "WAVE" ); HGLOBAL hSound = LoadResource( NULL, hRes ); /* 読み込んだデータのアドレスを取得 */ LPVOID pSound = LockResource( hSound ); /* WAVデータを再生 */ sndPlaySound( (const char *)pSound, SND_SYNC|SND_MEMORY ); // ロードしたリソースの解放 FreeResource( hRes ); } リソースの解放は明示的に FreeResource( ) しなくてもプログラムの終了時にシステムが 自動で解放するようです。ヘルプにはWindows95では必ず FreeResource( ) しろと書いて あるのに,デバッグでステップ実行してみると FreeResource( ) 自体は何もしていなかっ たりするのですが,FreeResource( ) したからといって即座にメモリが解放されるかどうか は95系かNT系かで実装が異なるみたいです。
目次に戻る

 
◆ ダイアログでコンソール入出力
ダイアログベースのアプリケーションにコマンドプロンプトからパラメータを指定して 実行できるようにしたとき,コンソールにメッセージを表示したいことがあります。
このとき単にprintf( )してもメッセージが全然表示されません。_cprintf( )でもダメダメです。

調べたところ,そのアプリケーション用に新しくコンソールを開かないといけないみたいです。 面倒くさいですが,次がそのサンプルコードです。出力コンソールを開いてメッセージを出力したあと, すぐに終了しないで入力コンソールを開いてRETURNキーを待つということをしています。

なおサンプルコードを使用するにはStdAfx.hのVC_EXTRALEAN定義をコメントアウトしておく必要があります。 // 出力コンソールを開く AllocConsole(); HANDLE hFile = CreateFile( "CONOUT$", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); // コンソール出力 char szMessage[] = "コンソール出力サンプル\n"; DWORD dwConsole; WriteFile( hFile, szMessage, strlen( szMessage ), &dwConsole, NULL ); // コンソールを閉じる確認 char szReturn[] = "\n終了するにはRETURNキーを押してください。\n"; WriteFile( hFile, szReturn, strlen( szReturn ), &dwConsole, NULL ); CloseHandle( hFile ); // 入力コンソールを開く(RETURNキーを待つ) hFile = CreateFile( "CONIN$", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); ReadFile( hFile, &dwConsole, 1, &dwConsole, NULL ); CloseHandle( hFile ); // コンソールを閉じる FreeConsole();
目次に戻る

 
◆ アクセラレータキー
メニュー項目などに「開く(O) CTRL+O」などとあると,コントロールキーを押しながら Oキーを押すとこの項目を実行できます。このCTRL+Oがアクセラレータキーと呼ばれるものです。 アクセラレータキーを実装すると使いやすいアプリケーションになるため,よく使う機能には 是非キーを割り当てたいものです。

MFC Appwizardを使用してSDIやMDIアプリケーションを作成するとアクセラレータ機能が標準で 実装されます。リソースのAcceleratorという項目にアクセラレータキーのリストがあるはずです。 ここにメニュー項目IDとキー操作を登録することで,アクセラレータキーの実装が済んでしまいます。 簡単ですね!メニュー項目のキャプションは 「開く(&O)...\tCTRL+O」というように記述しておきます。

ところがダイアログベースのアプリケーションではこの方法で実装できません!!できなくは ないですが,アクセラレータキーの機能自身を実装しなくてはいけないので大変です。 そこで見かけ上アクセラレータキーと同じ動きになるような方法をとることにします。

メニューの項目のキャプションは同じように「開く(&O)...\tCTRL+O」というように記述します。 ダイアログにPreTranslateMessage( )ハンドラを実装して以下のようにコーディングすると簡単に アクセラレータキーもどきが実装できます。 BOOL C????Dlg::PreTranslateMessage(MSG* pMsg) { if( pMsg->message == WM_KEYDOWN ){ // CTRL+O アクセラレータキーの代用 if( pMsg->wParam =='O' && (GetKeyState( VK_CONTROL )&0x8000) ) On????Open( ); } return CDialog::PreTranslateMessage(pMsg); } なおOn????Open( )はメニュー項目のハンドラです。
目次に戻る

 
◆ ネットワークプログラミング事始め
ネットワークプログラミングは難しそうに見えますが,クライアント(要求者)とサーバ(提供者)とで データをやりとりしているに過ぎません。そのやりとりがプロトコルというお約束で決められているだけです。 クライアントが「海」と言ったらサーバが「山」といって色々なデータがやりとりされているようなものです。
もちろん独自のプロトコルを作ってしまうことも可能です。ただ通信は相手がいることなので,その場合は クライアントとサーバの両方のアプリケーションを作成する必要があります。

アプリケーションは通信する相手毎にソケットというものを用意します。複数のソケットを作成して複数の相手と 同時に通信することも可能です。全てのネットワークプログラミングはこのソケットを使用して行われます。 ソケットに関するインタフェースはwinsockと呼ばれるAPIが用意されています(wsock32.dll)。
簡単に言うと,WSAStartup( )でAPIを初期化して,socket( )でソケットを作成して,connect( )で接続して send( )でデータを送信して,recv( )でデータを受信して,closesocket( )でソケットを削除してWSACleanup( )で 後始末をするという流れです。このようにネットワーク通信プログラム自身は大して難しくありません。 どのような手順でどんなデータをやりとりするかというプロトコルのプログラミングが面倒くさいだけです。

MFCにはソケットインタフェースをカプセル化したクラス CSocket があります。これはより詳細な制御を行える CAsyncSocket からの派生クラスですが,次のような簡単なコードでネットワーク通信プログラムが可能となって しまいます。 // ※プロジェクトを作成する際にWindowsソケットを有効にしておく // ソケット オブジェクトの構築 CSocket sockClient; // SOCKET の生成 sockClient.Create( ); // コネクションの検索 sockClient.Connect( strAddr, nPort ); // アドレスとポート番号 // ファイル オブジェクトの構築 CSocketFile file( &sockClient ); // アーカイブの構築 CArchive arIn( &file, CArchive::load ); CArchive arOut( &file, CArchive::store ); // 受信データがあるか調査 arIn.IsBufferEmpty( ); arIn >> dwValue; // 受信 arOut << dwValue; // 送信 CSocket の派生クラスを作成し,データを受信した時のイベントハンドラをオーバーライドすることに よって受信データ待ちもスマートにプログラミング可能です。 // ※プロジェクトを作成する際にWindowsソケットを有効にしておく #include "stdafx.h" // CSocket派生クラス class CSocketEx : public CSocket { CSocketEx( ){} // コンストラクタ ~CSocketEx( ){} // デストラクタ // データ受信ハンドラ void OnReceive( int nErrorCode ) { // Receive( )関数で受信 CSocket::OnReceive(nErrorCode); } }; // VC++4.0ではクラスウィザードでCSocketを基本クラスにした派生クラスを // 新規作成できなかったので手動で派生しています。VC++6.0ではできた…。 イベントハンドラには OnReceive( ), OnSend( ), OnAccept( ), OnConnect( ), OnClose( ) というものが存在します。 詳細はそれぞれヘルプを見てください。「Windows ソケット」をキーワードにヘルプを参照するとソケット プログラミングが良く分かります。

なお,CSocket は通信が完了するまでプログラムが待たされます(同期)。CAsyncSocketは通信が完了しなくても プログラムが継続します(非同期)。いずれにしてもプログラミングの複雑さはプロトコルの複雑さで決まります。
目次に戻る

 
◆ 日付と時刻
MFCには日付と時刻を管理する便利なCTimeクラスがあります。 以下のように簡単に時刻と時間の処理を実装することが可能です。 // 現在時刻で作成 CTime now = CTime::GetCurrentTime( ); // 指定した時刻で作成 CTime t = CTime( 2003, 10, 19, 11, 42, 0, -1 ); // 大小比較 if( now < t ){ ... } // 差分の取得 CTimeSpan span = t - now; // 時間の計算 now += span; // UTC時刻からローカル時刻への変換 #include <time.h> CTime utc = CTime( 2003, 10, 19, 2, 42, 0, -1 ); CTime local = utc; local -= CTimeSpan( (time_t)_timezone ); 詳細は CTime や CTimeSpan や「日付と時刻」や「時間管理」をキーワードにヘルプを参照してください。
目次に戻る

 
◆ ドキュメントとビュー
これまでは主にダイアログベースのアプリを作成してきました。 テキストエディタやペイントツールの様に、データを参照するだけでなく 編集や保存を行いたい場合は SDI または MDI アプリケーションを作成する 必要があります。

そこで必要になってくるのが「ドキュメント」と「ビュー」の概念です。 「ドキュメント・ビューアーキテクチャ」なんて仰々しく呼ばれたりするこの考え方は、 「ファイルとして残すデータとアプリケーションでデータをどう見せるかを切り離して 考えた方が何かと便利ですよ」というものです。

「ドキュメント」は「アプリケーションで扱うデータ」を意味していて、テキストファイルだけ でなく画像データや実行ファイルなど全てのファイルが対象になり得ます。

また、例えばテキストファイルという「ドキュメント」は人が読める文字列で表示させたり、 ダンプリストで表示させたりと、その表示方法はいくつも考えられます。しかし表示方法 によってドキュメントの内容は変わることはありません。

つまりこの二つを明確に分けて考えるとデータの管理や表示方法などのプログラミングが きれいに効率よくできますよというのが、ドキュメントとビューの考え方なのです。

では実際にサンプルコードを作成してみます。AppWizard で SDI または MDI アプリケーションを 作成するとドキュメントクラス C????Doc と ビュークラス C????View が作成されます。

ドキュメントクラスではファイルとして保存されるデータの定義を行い、 メンバ変数として実装します。また必要に応じてそのデータにアクセスするメンバ関数も追加します。 ただしデータファイル形式はここでは決定されません。後述の Serialize( ) 関数で決定されます。 この作業は C????Doc.h を変更します。 // アトリビュート public: char m_Data[10]; // ドキュメントデータ 新規ドキュメントを開いた時、このドキュメントデータの初期値を設定するように C????Doc.cpp 内の OnNewDocument( ) を変更します。 BOOL C????Doc::OnNewDocument( ) { if (!CDocument::OnNewDocument( )) return FALSE; for( int i=0; i<10; i++ ) m_Data[i] = 0; // 新規ドキュメントデータ return TRUE; } またドキュメントを開いたり保存したりする場合に呼ばれる関数が Serialize( ) です。 現在のデータを一旦保存して、次に読み込んだとき作業を継続できるという意味で「シリアライズ」 という名称になっています。 C????Doc.cpp 内にあるこの関数にコードを追加します。 void C????Doc::Serialize(CArchive& ar) { if (ar.IsStoring( )) // 保存する時 { for( int i=0; i<10; i++ ) ar << m_Data[i]; } else // 読み込む時 { for( int i=0; i<10; i++ ) ar >> m_Data[i]; } } ar は CArchve クラスのオブジェクトですが、自動的に生成されていて、 ファイルの入出力を適切に行ってくれるものです。ファイル名の決定やオープン、 クローズは自動的に行われるので、上記のようなたった数行でドキュメントデータと ファイルのやりとりが簡単に実装できます。

上記サンプルでは基本データ型の char 変数を演算子 << と >> を使うことによって 順次保存したり読み込んだりしています。この順番がデータファイルの形式を決定します。
残念ながら ar は基本データ型以外の構造体やクラスオブジェクトを直接扱うことはできません。 << や >> が有効なのは CObject の派生クラスで Serialize( ) が実装されているものに限られます。

ファイル形式がテキストでよければ,以下のようにもできます。 void CxxxxDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { CString String; String.Format( "VERSION: 2.2\r\n" ); ar.WriteString( String ); String.Format( "PROJECT: %s\r\n", "hoge" ); ar.WriteString( String ); } else { CString VersionString, ProjectString; ar.ReadString( VersionString ); ar.ReadString( ProjectString ); } } ar.ReadString() でどんどん読み込んでいってテキスト終端に到達したとき, 戻り値は FALSE なり NULL を返してくれるのですが, それと同時にMFCは必ず以下のような例外も発生してくれるようです…。 CArchive exception: endOfFile. XXXX.exe の 0x???????? で初回の例外が発生しました : Microsoft C++ exception: CArchiveException @ 0x0012edbc。 例外は終端以降を読んだときにしてほしいなぁ。
初回の例外 は実害がないようなので無視していいのかな?


次にビュークラスの実装を行います。
最初に表示する時も含めて再描画する際に実行される関数が C????View.cpp 内の OnDraw( ) 関数 です。以下のように実装してみます。 void C????View::OnDraw(CDC* pDC) { C????Doc* pDoc = GetDocument( ); ASSERT_VALID(pDoc); for( int i=0; i<10; i++ ){ CString Str; Str.Format( "m_Data[%d] = %d", i, pDoc->m_Data[i] ); pDC->TextOut( 0, i*20, Str ); } } ドキュメントへは GetDocument( ) 関数を使ってアクセスします。

ドキュメントを変更するインタフェースはビュークラスに実装します。ここでは マウスをダブルクリックしたときにドキュメントを変更してみます。 クラスウィザードでビュークラスに WM_LBUTTONDBLCLK ハンドラを実装して以下のように コーディングします。 void C????View::OnLButtonDblClk(UINT nFlags, CPoint point) { C????Doc* pDoc = GetDocument( ); // ドキュメントデータの変更 for( int i=9; i>0; i-- ) pDoc->m_Data[i] = pDoc->m_Data[i-1]; pDoc->m_Data[0] = pDoc->m_Data[1] + 1; // ドキュメントが更新されたことを通知 pDoc->SetModifiedFlag( ); // 更新されたドキュメントを表示するビューを再描画させる pDoc->UpdateAllViews( NULL ); CView::OnLButtonDblClk(nFlags, point); } これでドキュメントとビューの一連のコーディングが終了しました。 何たってファイルを直接操作しなくて良いのですから簡単すぎますね!(笑)


【ドキュメント・ビュー追加TIPS】

ドキュメントとビューに関するおぼえがきを以下に記載します。



★印刷倍率
MFCアプリケーションウィザードでMFCアプリケーションを作成するとき,「高度な機能」のところで 「印刷と印刷プレビュー(P)」をチェックすると,ビューに描いたものの印刷機能を自動で付けてくれてとても便利です。 が,実際に印刷するととても小さく印刷されてしまいます。画面に描画する場合も印刷する場合も 同じ OnDraw( ) が使われているため,Ondraw( ) 内で解像度に合わせて以下のようにスケールを変える必要があります。 // 印刷時のスケール設定 if( pDC->IsPrinting() == TRUE ){ pDC->SetMapMode( MM_ANISOTROPIC ); pDC->SetWindowExt( 100, 100 ); pDC->SetViewportExt( 400, 400 ); }
目次に戻る

 
◆ ウインドウの終了処理
ウインドウの終了時に取得したメモリなどのリソースを解放しなくてはなりませんが、 具体的にどこで行えば良いのでしょうか?
調査の結果、以下のような順番で処理が進むようです。

  1. OnClose( )
    右上の[X]ボタンやシステムメニューで終了を選択した時のみ実行されます。 ダイアログなどでESCキーが押された場合には実行されないので、後処理を行うには 不向きです。

  2. DestroyWindow( )
    ウインドウを閉じる(廃棄する)時に実行されます。デフォルトの処理が行われた時点で ウインドウが廃棄されます。アプリケーションの終了前にウインドウサイズを保存するなど、 ウインドウに関わる後処理を行う場合はここのデフォルト処理の前で行うと良いでしょう。
    またウインドウで使われる可能性のあるリソースの解放などはデフォルト処理の後で 行うと良いでしょう。

  3. OnDestroy
    ウインドウが廃棄されてから呼ばれます。 ウインドウで使われる可能性のあるリソースの解放はここで行うと良いでしょう。

  4. デストラクタ
    ウインドウクラスのデストラクタなので全ての終了処理が終わってから一番最後に実行されます。 ウインドウに関する情報は既に全て廃棄されているので、ここでの後処理はあまり必要ないかも しれません。

目次に戻る

 
◆ ファイルの日時とサイズ
バイナリファイル操作には CFile クラスを使うのが便利ですが、ファイルの作成日時やサイズを 知りたいだけの場合もこのクラスの静的メンバ関数を使用するとファイルをオープンしないで 可能になります。 CFileStatus rStatus; if( CFile::GetStatus( "filename", rStatus ) ){ CTime Date = rStatus.m_mtime; // ファイル最終変更日時 int nSize = rStatus.m_size; // ファイルの論理サイズ } else // ファイルが存在しない CFileStatus にはファイル属性などの情報もあるので詳しくはヘルプを見てください。
目次に戻る

 
◆ フォーカス
ダイアログを開いたとき、デフォルトではOKボタンにフォーカスがセットされています。 これを変更するにはリソースエディタのレイアウトでタブオーダーの設定をします。 一番若いスタティックでないコントロールにセットされるようです。 タブオーダーの設定の仕方は、ダブルクリックして1番をセットし、あとは順番にシングル クリックしてセットしていきます。

プログラム上でフォーカスを変更するには次のようにします。 NextDlgCtrl( ); // 次のコントロールへ PrevDlgCtrl( ); // 前のコントロールへ GotoDlgCtrl( GetDlgItem( IDC_???? ) ); // IDC_???? のコントロールにフォーカスをセット GetDlgItem( IDC_???? )->SetFocus( ); // IDC_???? のコントロールにフォーカスをセットする別の方法 またダイアログ上であたかもTABキーが押されたかのようにすることでも次のコントロールへ移動できます。 // 処理前メッセージハンドラ BOOL CVCalEdDlg::PreTranslateMessage(MSG* pMsg) { if( pMsg->message == WM_KEYDOWN ){ // RETURNキーは次のコントロールを選択する(TABキーに変換) if( pMsg->wParam == VK_RETURN ) pMsg->wParam = VK_TAB; } return CDialog::PreTranslateMessage(pMsg); }
目次に戻る

 
◆ メディアプレーヤクラス
メディアプレーヤを自作プログラムに組み込むことができるようです。 以前はOLEコントロールと呼んでいて今はActiveXコントロールとかいう技術を利用するのですが、 現在の筆者のレベルではよくわかりません。使ってるのがVC++4.0だし…。(^^; 要するにメディアプレーヤがクラスとして定義され、ダイアログなどにオブジェクトとして実装できるということらしいです。

  1. 最初にプロジェクトを作成する時にOLEサポートの選択でOLEコントロールを有効にします。
  2. 挿入->コンポーネント->OLEコントロールでWindows Media Playerを追加します。
  3. ダイアログなどにメンバ変数 CMediaPlayer2 m_Player; を追加します。#include "MediaPlayer2.h" するのを忘れずに。
  4. ダイアログのOnInitDialogで、 // スタティックコントロール内にメディアプレーヤを組み込む CRect Rect; this->GetDlgItem( IDC_STATIC_AREA )->GetWindowRect( Rect ); this->ScreenToClient( Rect ); Rect.DeflateRect( 2, 2, 3, 3 ); RECT rect = Rect; m_Player.Create( "Media Player", WS_CHILD|WS_VISIBLE, rect, this, 1 );
  5. ロードするファイルの指定は m_Player.SetFileName( "c:\\windows\\デスクトップ\\movie.mpg" ); で行う。
  6. 再生開始は m_Player.Play( ); で 停止は m_Player.Stop( ); らしい。
他にもオートスタートを制御する m_Player.SetAutoStart( FALSE ); とか、オートサイズ?を設定する m_Player.SetAutoSize( TRUE ); とか パネルを出すか出さないか設定する m_Player.SetShowControls( FALSE ); とか 色んなメンバ関数があるみたいですが、何せヘルプが無いのでよくわかりません。インターネットで調べてもあまり 使っている人はいないみたい…。暇ができたらもっと詳しく調べようと思います。[つづく]
目次に戻る

 
◆ 例外処理(初回)
デバグ実行終了時に以下のメッセージがでることがありました。

・例外処理 (初回) は xxxx.exe にあります: 0xC0000005: アクセス違反。 ・例外処理 (初回) は xxxx.exe (KERNEL32.DLL) にあります: 0xC0000005: アクセス違反。
・例外処理 (初回) は xxxx.exe (MSONSEXT.DLL) にあります: 0x006D007E: (unknown)。 ・CArchive exception: endOfFile.
 XXXX.exe の 0x???????? で初回の例外が発生しました : Microsoft C++ exception: CArchiveException @ 0x0012edbc。 他の場合もあると思いますが、とりあえず今回は以上。
目次に戻る

 
◆ CTabCtrl タブコントロールの実装
タブコントロールはプロパティシートについているようなタブを制御するクラスです。 ダイアログをタブによって切り換えたい場合はプロパティシートそのものである CPropertySheetクラスを使えばCTabCtrlクラスを使用しなくても簡単に実装できますが (
参照),タブによってツリーコントロールのような任意のウインドウを 切り換えたい場合はCTabCtrlを実装する必要があります。

ダイアログベースのアプリケーションにタブコントロールを実装する手順は次の通りです。
  1. メインダイアログにタブによって切り換えられる子ウインドウ(複数)をメンバ登録する。
  2. リソースエディタでメインダイアログにタブコントロールを貼り付ける。
  3. クラスウィザードでタブコントロールにコントロール変数(例m_Tab)を割り付ける。
  4. メインダイアログのOnInitDialogでm_Tab.InsertItem( )によってタブを追加する。
  5. 同じくOnInitDialogでm_Tabを親として全ての子ウインドウをCreateする。
  6. クラスウィザードでタブのメッセージTCN_SELCHANGEにハンドラOnSelchangeTabを割り付ける。
  7. タブが切り換わるとOnSelchangeTabハンドラが実行されるのでm_Tab.GetCurSel( )で選択されている タブを取得し,子ウインドウのどれか一つをShowWindow( SW_SHOW )し,他をShowWindow( SW_HIDE )する。
2つのタブを持ちツリーコントロールとリストコントロールを切り換えるようにした サンプルコードはこちら

目次に戻る

 
◆ CTreeCtrl ツリーコントロールの実装
ツリーコントロールとはエクスプローラの左側部分そのものです。階層的にアイテムを管理することができます。 使い方はメニューコントロールとリストビューコントロールのあいのこのようで、アイテムの追加には InsertItem( )を使用し、アイテムが選択された時のハンドラは TVN_SELCHANGED を実装します。

具体的なサンプルは
これ を見てください。 エクスプローラの左側のようにサブフォルダツリーを作成する関数を作ってみました。

上記サンプルでは一度に最下層サブフォルダまで検索してツリー表示するので 実際に使用してみるとストレスを感じることがありました。
そこでツリーを展開する時は直下のサブフォルダしか検索しないようにして動作を軽くしたのが これ です。ついでなので CTreeCtrl クラスの派生クラスとして作成してみました。 使用方法はソースを見てください。

なお、ツリーコントロールからフォーカスが外れると選択アイテムが分からなくなってしまうのが いやな場合は、以下のようにするとフォーカスが外れても選択されたままになります。 m_TreeCtrl.ModifyStyle( NULL, TVS_SHOWSELALWAYS ); 【ツリーコントロールの追加TIPS】

★項目の削除
項目を削除するには,現在選択されている項目を GetSelectedItem( ); で調査して, DeleteItem( ); で削除します。 以下にDELETEキーが押された際に項目を削除するコードを記載します。 OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if( nChar == VK_DELETE ){ HTREEITEM hItem = this->GetSelectedItem(); if( hItem ){ this->DeleteItem( hItem ); } } CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags); } ★項目ラベルの編集
項目ラベルを編集可能にするにはツリーコントロールのスタイルに TVS_EDITLABELS 属性をセットします。 変更イベントを受けるには,TVN_ENDLABELEDIT(WM_NOTIFY)メッセージハンドラを実装し, そこで this->SetItem( &pTVDispInfo->item ); します。

★アイコンの表示
ツリーコントロールにアイコンを表示させるには,リソースエディタでアイコンを作成しイメージリストにセットして, それをツリーコントロールに登録するという流れになります。

イメージリストはツリーコントロールにメンバ変数として以下のように追加します。 CImageList m_ImageList; 次に,イメージリストを初期化します。ツリーコントロールに OnCreate() を実装し,以下のコードを追加します。 // アイコンイメージリストの初期化 m_ImageList.Create( 16, 16, ILC_COLOR8, 2, 0 ); m_ImageList.Add( AfxGetApp()->LoadIcon( アイコン0のリソースID ) ); m_ImageList.Add( AfxGetApp()->LoadIcon( アイコン1のリソースID ) ); // ツリーコントロールにアイコンイメージを登録 this->SetImageList( &m_ImageList, TVSIL_NORMAL ); そして,InsertItem( "ITEM", 0, 1 ); のように非選択状態と選択状態でのそれぞれのイメージ番号を イメージリストのインデックスで指定すればアイコン付きのツリーコントロールが実装できます。

★コンテキストメニュー
ツリーコントロールでコンテキストメニューを表示させるにはWM_RBUTTONDOWNメッセージハンドラを 以下のように実装します。 // 右クリックハンドラ void CxxxxTree::OnRButtonDown(UINT nFlags, CPoint point) { // 該当座標に項目があれば選択してポップアップメニューを表示する HTREEITEM hItem = this->HitTest( point ); if( hItem ){ this->SelectItem( hItem ); CMenu XxxxxMenu; XxxxxMenu.LoadMenu( IDR_MENU_PRJ ); CMenu *pPopup = XxxxxMenu.GetSubMenu( 0 ); this->ClientToScreen( &point ); pPopup->TrackPopupMenu( TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, this ); } CTreeCtrl::OnRButtonDown(nFlags, point); }
目次に戻る

 
◆ CListCtrl リストビューコントロールの実装
リストビューとはエクスプローラの右側部分そのものです。アイコンの一覧表示(アイコンビュー)や 詳細の列表示(レポートビュー)といった形態を持つことができます。

レポートビューは自作アプリケーションでも便利に使えるので、今回はリストビューコントロールの レポートビュー形式での実装方法を解説します。


まずリソースエディタでリストビューコントロールを作成し、スタイルの表示をReportにしておきます。 これにメンバ変数をコントロールで割り付けます。ここでは m_ListCtrl という変数名とします。

次に、エクスプローラを詳細表示にすると右側に「名前」「サイズ」「ファイルの種類」などの列(カラム)が ありますが、これをヘッダを指定して追加します。 // カラム作成 LV_COLUMN lvclm; lvclm.mask = LVCF_TEXT|LVCF_WIDTH; lvclm.pszText = "名前"; // ヘッダ lvclm.cx = 80; // カラムのドット幅 m_ListCtrl.InsertColumn( 0, &lvclm ); // 追加 lvclm.pszText = "サイズ"; lvclm.cx = 80; m_ListCtrl.InsertColumn( 1, &lvclm ); lvclm.pszText = "ファイルの種類"; lvclm.cx = 150; m_ListCtrl.InsertColumn( 2, &lvclm ); そして実際の項目(アイテム)を登録します。 int i = 0; m_ListCtrl.InsertItem( i, "NAME" ); m_ListCtrl.SetItemText( i, 1, "SIZE" ); m_ListCtrl.SetItemText( i, 2, "TYPE" ); // 列幅調整 // m_ListCtrl.SetColumnWidth( 0, LVSCW_AUTOSIZE ); // m_ListCtrl.SetColumnWidth( 1, LVSCW_AUTOSIZE ); // m_ListCtrl.SetColumnWidth( 2, LVSCW_AUTOSIZE ); 上記例では i=0 として1個のアイテムしか登録しませんでしたが、 実際はループにして登録数だけi++していく必要があります。

リストからそれぞれの項目を取得するには以下のように行います。 CString Name[100], Size[100], Type[100]; int nListItem = m_ListCtrl.GetItemCount( ); // リスト登録数の取得 if( nListItem ){ for( int i=0; i<nListItem; i++ ){ Name[i] = m_ListCtrl.GetItemText( i, 0 ); Size[i] = m_ListCtrl.GetItemText( i, 1 ); Type[i] = m_ListCtrl.GetItemText( i, 2 ); } } さらに、マウスでクリックした時にどのアイテムがクリックされたかを取得するには CListCtrl の NM_CLICK ハンドラを実装します。 // リストビュークリックハンドラ (CListCtrlのNM_CLICKハンドラ) void C????::OnClickList( NMHDR *pNMHDR, LRESULT *pResult ) { NM_LISTVIEW *pNMListView = (NM_LISTVIEW *)pNMHDR; // pNMListView->iItem にアイテム番号が入ります // 先頭列をクリックされた時のみ反応させます(エクスプローラと同じ) if( pNMListView->iSubItem == 0 ){ CString name = m_ListCtrl.GetItemText( pNMListView->iItem, 0 ); CString size = m_ListCtrl.GetItemText( pNMListView->iItem, 1 ); CString type = m_ListCtrl.GetItemText( pNMListView->iItem, 2 ); } *pResult = 0; } 選択されているアイテムを調査するにはこうします。 int nSelected = m_ListCtrl.GetNextItem( -1, LVNI_SELECTED ); if( nSelected < 0 ) // 選択されていない さらにアイテムを選択状態にするにはこうします。 int i = 0; // アイテム番号 m_ListCtrl.SetFocus( ); m_ListCtrl.SetItemState( i, LVIS_SELECTED, LVIS_SELECTED ); // リストコントロール自身にフォーカスをセットしてからでないとダメ 同様にアイテムの属性を変えるにはこうします。 // 選択の解除 m_ListCtrl.SetItemState( i, (UINT)~LVIS_SELECTED, LVIS_SELECTED ); // フォーカスのセットと解除 m_ListCtrl.SetItemState( i, LVIS_FOCUSED, LVIS_FOCUSED ); m_ListCtrl.SetItemState( i, (UINT)~LVIS_FOCUSED, LVIS_FOCUSED ); ラベルを編集可能にするにはリソースエディタでスタイルのラベルの編集をチェックしておき クラスウィザードでLVN_ENDLABELEDITメッセージのハンドラを実装し以下のように変更する。 void CEGZcDlg::OnEndlabeleditListviewDstdir(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR; // TRUEを返して変更を有効にする *pResult = TRUE; }
基本は以上です。各アイテムにはビットマップイメージなども添えられるようなので 後はヘルプを見るなり頑張ってください。

なお、リストコントロールからフォーカスが外れると選択アイテムが分からなくなってしまうのが いやな場合は、以下のようにするとフォーカスが外れても選択されたままになります。 m_ListCtrl.ModifyStyle( NULL, LVS_SHOWSELALWAYS );
目次に戻る

 
◆ プログレスバーの実装
時間のかかる処理などでよく進行状況を表示するダイアログが開いたりしますが、 あの棒グラフみたいのなのがプログレスバーです。今回はこれを実装してみます。

  1. リソースビューの Dialog のところで右クリックし "dialogの挿入" を選択します。
    作成されたダイアログのプロパティの一般で ID を IDD_PROGRESS_DIALOG に、 キャプションは "Now Loading..." とでもしておきます。
    さらにプロパティのスタイルでシステムメニューのチェックボックスをクリアしてください。
    もしタイトルバーがいらなければタイトルバーのチェックボックスをクリアしてください。
    またその他のスタイルで、可視と無効をチェックしてください。

  2. ダイアログ上のOKボタンとキャンセルボタンを削除します。
    さらにスタティックテキストを作成し、後からこれを変更できるようにプロパティで IDを IDC_LABEL に、またキャプションを "Now Loading..." にでもしておきます。

    プログレスバーコントロールをダイアログに置いて、全体のレイアウトを整えてください。 プログレスバーのIDは IDC_PROGRESS とでもしておきます。

  3. クラスウィザードを起動します。 クラスの追加 "IDD_PROGRESS_DIALOGは新規リソースです…" と聞いてくるので 新規クラスの作成を選択します。

    新規クラスの作成では、クラス名を CProgressDialog にして後はそのままで作成ボタンを押します。

    クラスウィザードのメンバ変数で IDC_LABEL に m_Label というカテゴリが値の CString 変数と、 IDC_PROGRESS に m_ProgressBar というカテゴリがコントロールの CProgressCtrl 変数を割り当てます。

  4. 後は実際にこのプログレスバーのついたダイアログを使用するだけです。

    使用するプログラムに #include "ProgressDialog.h" しておき プログレスバーを出したい部分で以下のようなコーディングをします。
    プログレスバーダイアログを開くときはモーダルの DoModal( ) ではなく モードレスの Create( ) で行います。モーダルだとそこから先に処理が行かないからです。 // 親ウインドウを無効にする EnableWindow( FALSE ); // プログレスバーダイアログの作成 CProgressDialog pd; pd.m_Label = "プログレスバーをテスト中..."; pd.Create( IDD_PROGRESS_DIALOG ); pd.m_ProgressBar.SetRange( 0, 10 ); pd.EnableWindow( FALSE ); // プログレスバーを更新する Sleep( 1000 ); pd.m_ProgressBar.SetPos( 1 ); Sleep( 1000 ); pd.m_ProgressBar.SetPos( 2 ); (略) Sleep( 1000 ); pd.m_ProgressBar.SetPos( 9 ); Sleep( 1000 ); pd.m_ProgressBar.SetPos( 10 ); // プログレスダイアログを消去する pd.DestroyWindow( ); // 親ウインドウを有効にする EnableWindow( TRUE );
実際は、プログレスバーダイアログの作成部分と、プログレスバーの更新部分は別々の関数のはずで 長い処理に入る前にダイアログを作成し、長い処理の中からコールバック関数の形でプログレスバーの 更新を行う形になります。

上記で m_Label を動作に応じて変更したい場合は,最初に CStatic にしておいて, 随時 m_Label.SetWindowText( ) すればいいでしょう。
目次に戻る

 
◆ バージョン情報
リソースビューを見ているとVS_VERSION_INFOとかいうVersion管理情報があったりして、 さもここでバージョンを管理してくださいってなっているのですが、同じリソースビューにある Aboutダイアログにはスタティックテキストでバージョンが書かれていたりして、 ここまでするなら自動的に連動してくれよなんて思うのですが、自分でやらなければ ならないようです。

バージョンリソースにアクセスするには GetFileVersionInfoSize( ) GetFileVersionInfo( ) VerQueryValue( ) を組み合わせて使えばいいことがわかりました。 これを元にバージョンを取得するクラスなんかを作成すれば便利なのですが、 これが結構面倒くさいのです。

VC++の解説サイトを見てまわっていると
おパパさん という方が作られた大変すばらしい バージョンリソースクラスのサンプル を見つけてしまいました。マクロの使い方が凄いです。 でもマクロでメンバ関数が記述されているがゆえにデバグの時ちょっと不便だったのと 何をやっているか自分なりにかみくだいてコーディングしてみたかったので 書き換えたクラスが これ です。 ほとんどパクリです。スイマセン(^^;;;
目次に戻る

 
◆ タスクトレイアイコン
タスクトレイにアイコンを表示し、そのアイコンからのイベントを受け取るには以下のようにします。 ・ダイアログまたはメインフレームの定義追加 #define MY_NOTIFYICON (WM_APP+100) // 通知メッセージ ・ダイアログまたはメインフレームのメンバ変数として追加 NOTIFYICONDATA nIcon; ・ダイアログなら OnInitDialog( ) で、メインフレームの場合は OnCreate( ) に 次のコードを追加 // タスクトレイアイコンの初期化 nIcon.cbSize = sizeof( NOTIFYICONDATA ); nIcon.uID = 1; nIcon.hWnd = this->m_hWnd; nIcon.uFlags = NIF_MESSAGE|NIF_ICON|NIF_TIP; nIcon.hIcon = AfxGetApp( )->LoadIcon( IDR_MAINFRAME ); nIcon.uCallbackMessage = MY_NOTIFYICON; // タスクトレイにマウスポインタを置いたときに表示される文字列 lstrcpy( nIcon.szTip, "TEST" ); ・タスクトレイにアイコンを表示する Shell_NotifyIcon( NIM_ADD, &nIcon ); ・終了時にアイコンを消去するために DestroyWindow( ) を クラスウィザードでオーバライドする。 void C????::DestroyWindow( ) { Shell_NotifyIcon( NIM_DELETE, &nIcon ); return CDialog::DestroyWindow( ); } ・アイコンからのイベントを受け取るためにクラスウィザードで WindowProc( ) をハンドルし以下のように実装する。 LRESULT C????::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if( message == MY_NOTIFYICON ){ if( lParam == WM_LBUTTONDBLCLK ){ // アイコンが左ダブルクリックされたときの処理 } } return CDialog::WindowProc(message, wParam, lParam); } またタスクトレイにアイコンを表示している間、メインウインドウを隠す場合は以下のように するよ良いでしょう。 // メインウインドウを隠す ShowWindow( SW_HIDE ); // メインウインドウを復帰させる ShowWindow( SW_RESTORE ); SetForegroundWindow98( this->m_hWnd ); SetForegroundWindow98( ) については
ここを参照してください。

タスクトレイアイコンをクラス化してみました。これです。
目次に戻る

 
◆ キー入力
キーが押されたり離されたりするイベントを受けるには、クラスウィザードで WM_KEYDOWN および WM_KEYUP メッセージを実装します。メッセージハンドラは OnKeyDown( ) および OnKeyUp( ) で、 何のキーが押されたか引数 nChar でわかるようになっています。 アルファベットのキーであれば'A'などの値と比較することで何のキーが押されたか判定できます。 矢印キーなど特殊なものは仮想キーコードとして扱われ、その内容は msdev\include\winuser.h に VK_???? と定義されているので確認してみてください。以下にサンプルを示します。 // キー離上ハンドラ void C????Dlg::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { switch( nChar ){ case VK_PRIOR: // PageUp break; case VK_NEXT: // PageDown break; case VK_HOME: // Home break; case VK_END: // End break; case VK_F5: // F5 break; case VK_LEFT: // ← break; case VK_RIGHT: // → break; case VK_UP: // ↑ break; case VK_DOWN: // ↓ break; case 'C': if( GetKeyState( VK_CONTROL ) & 0x8000 ){ // Ctrl + C } break; } } なおダイアログでボタンなどが実装されている場合、ダイアログ自身にはキーイベントが届きません。 OnKeyDown( ) および OnKeyUp( ) を実装しても実行されないのです。 ダイアログ自身でキーを受けたい場合は、その上に載っているコントロールを全て無効にするか、 以下のようにメッセージハンドラ PreTranslateMessage( ) を実装してメッセージが処理される前に 直接制御を行います。 BOOL C????Dlg::PreTranslateMessage(MSG* pMsg) { // F5キーの離上に反応する if( pMsg->message == WM_KEYUP ){ if( pMsg->wParam == VK_F5 ){ // 最新の状態に更新など… return( TRUE ); } } // ESCキーの押下を無効にする if( pMsg->message == WM_KEYDOWN ){ if( pMsg->wParam == VK_ESCAPE ) return( TRUE ); } return CDialog::PreTranslateMessage(pMsg); } PreTranslateMessage( )内部でメッセージの処理を行った際は、必ず TRUE で戻ることを 忘れないようにしてください。動作がおかしくなる場合があります。

なお,CTRLキーやSHIFTキーおよびALTキーなどの特殊なキーについては,GetKeyState( ), OnSyskeyDown / OnSyskeyUp, WM_SYSKEYDOWN / WM_SYSKEYUP あたりを調査してみてください。
目次に戻る

 
◆ デスクトップの大きさ
デスクトップの大きさは以下のようにして取得できます。 CRect DesktopRect; GetDesktopWindow( )->GetClientRect( DesktopRect ); ただしこれはタスクバーサイズも含まれています。タスクバーの含まれないデスクトップサイズを 取得するにはこうします。 RECT rect; SystemParametersInfo( SPI_GETWORKAREA, 0, &rect, 0 ); 以上。
目次に戻る

 
◆ ウインドウの位置と大きさ
ウインドウの位置と大きさを取得したり設定するには以下のようにします。 // ウインドウの位置と大きさを取得する CRect rect; GetWindowRect( rect ); // ウインドウのクライアント領域(描画領域)の大きさを取得する GetClientRect( rect ); // ウインドウを親ウインドウの中央に配置する。 CenterWindow( ); // ※引数として基準にしたいウインドウへのポインタを指定できる。 // デスクトップを基準にしたい場合は GetDesktopWindow( ) の戻値を使用する。 // ウインドウの位置のみを変更する SetWindowPos( &wndTop, 100, 100, 0, 0, SWP_NOSIZE ); // ウインドウの位置と大きさを変更する SetWindowPos( &wndTop, 100, 100, 640, 480, SWP_DRAWFRAME|SWP_SHOWWINDOW ); // ウインドウを常に手前に表示する SetWindowPos( &wndTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); // 常に手前に表示するのを解除する SetWindowPos( &wndNoTopMost, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE ); // ウインドウを最大化する ShowWindow( SW_SHOWMAXIMIZED ); // ※リソースエディタでダイアログのプロパティのシステムメニューと // 最大化を有効にしておく // ウインドウを最小化(アイコン化)する ShowWindow( SW_MINIMIZE ); // ※リソースエディタでダイアログのプロパティのシステムメニューと // 最小化を有効にしておく // ウインドウが最大化されているか確認する BOOL bRet = IsZoomed( ); // ウインドウが最小化されているか確認する BOOL bRet = IsIconic( ); // ウインドウを非表示にする ShowWindow( SW_HIDE ); // ウインドウを最大化・最小化・非表示から元に戻す ShowWindow( SW_RESTORE );
目次に戻る

 
◆ クリップボード
テキストをクリップボードにコピーする方法 // テキストをクリップボードにコピー char *pText = "copy_text"; HGLOBAL hGlobal = GlobalAlloc( GHND, strlen( pText ) + 1 ); char *pBuf = (char *)GlobalLock( hGlobal ); strcpy( pBuf, pText ); GlobalUnlock( hGlobal ); if( OpenClipboard( ) == TRUE ){ EmptyClipboard( ); SetClipboardData( CF_TEXT, hGlobal ); CloseClipboard( ); } // [注意] クリップボードにコピーしたメモリは // OSの管理下になるので解放してはいけない クリップボードからテキストを取得する方法 // クリップボードからテキストを取得 char szText[256] = { 0 }; if( OpenClipboard( ) == TRUE ){ HGLOBAL hGlobal = GetClipboardData( CF_TEXT ); CloseClipboard( ); if( hGlobal ){ char *pBuf = (char *)GlobalLock( hGlobal ); if( pBuf ){ strcpy( szText, pBuf ); GlobalUnlock( hGlobal ); } } } // [注意] 取得を受けるバッファサイズが足りていることを確認すること ビットマップをクリップボードへコピーする方法 // ピクチャーコントロールに描画されたビットマップを // クリップボードへコピー CWnd *pWnd = GetDlgItem( IDC_PICTURECTRL ); CRect Rect; pWnd->GetClientRect( Rect ); CClientDC DCsrc( pWnd ); CDC DCdst; DCdst.CreateCompatibleDC( &DCsrc ); CBitmap Bitmap; Bitmap.CreateCompatibleBitmap( &DCsrc, Rect.Width( ), Rect.Height( ) ); DCdst.SelectObject( &Bitmap ); DCdst.BitBlt( 0, 0, Rect.Width( ), Rect.Height( ), &DCsrc, 0, 0, SRCCOPY ); if( OpenClipboard( ) == TRUE ){ EmptyClipboard( ); SetClipboardData( CF_BITMAP, (HBITMAP)Bitmap ); CloseClipboard( ); } // [注意] 上の例ではDCsrcとDCdstのデストラクタでデバイスコンテキストの // 解放が行われるのでわざわざ DCdst.DeleteDC( ) および // DCsrc.DeleteDC( ) しなくてもよい // またSelectObject( )で戻るオブジェクトを復帰させるとうまく動作しない クリップボードからビットマップを取得して描画する方法 // クリップボードからビットマップを取得して描画 if( OpenClipboard( ) == TRUE ){ HBITMAP hBitmap = (HBITMAP)GetClipboardData( CF_BITMAP ); CloseClipboard( ); if( hBitmap ){ BITMAP bm; if( GetObject( hBitmap, sizeof( BITMAP ), &bm ) ){ // ピクチャーコントロールに描画 CWnd *pWnd = GetDlgItem( IDC_PICTURECTRL ); CClientDC DCdst( pWnd ); CDC DCsrc; DCsrc.CreateCompatibleDC( &DCdst ); HBITMAP hOld = (HBITMAP)SelectObject( DCsrc.GetSafeHdc( ), hBitmap ); // 等倍で描画する場合 DCdst.BitBlt( 0, 0, bm.bmWidth, bm.bmHeight, &DCsrc, 0, 0, SRCCOPY ); // 領域内に収めて描画する場合 CRect Rect; pWnd->GetClientRect( Rect ); DCdst.StretchBlt( 0, 0, Rect.Width( ), Rect.Height( ), &DCsrc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY ); SelectObject( DCsrc.GetSafeHdc( ), hOld ); } } } // [注意] 上の例ではDCdstとDCsrcのデストラクタでデバイスコンテキストの // 解放が行われるのでわざわざ DCdst.DeleteDC( ) および // DCsrc.DeleteDC( ) しなくてもよい
目次に戻る

 
◆ リリースモードDLLとでバグモードDLLで発生する問題
Win95+VC4.0の環境では気づかなかったのですが、NT+VC4.0ではデバッグ実行した後に HEAP[アプリ名]: Invalid Address specified to RtlFreeHeap( 130000, ef0568 ) と表示されることがありました。googleで調べてみるとメモリを確保するようなDLL(例えばSUSIEプラグイン) を使用し、そのメモリの解放を本体で行う場合に発生することがわかりました。
参考 http://homepage2.nifty.com/tulip-an/soft/scrwrpprblm.html
要するにデバグモードではメモリの確保と解放の際、本来の領域に加えてデバグ用の領域も確保・ 解放を行いますが、リリースモードでコンパイルされたDLLでメモリを確保し、デバグモードで コンパイルされた本体でそのメモリを解放しようとすると、確保されていないデバグ用領域を 解放しようとするために発生するそうです。

解決方法は無いようなのでこのエラーメッセージは無視する以外ありませんね…。
目次に戻る

 
◆ アプリケーションの設定をどこに保存するか?
ダイアログ一つで済むアプリケーションならともかく、比較的規模が大きくなると アプリケーション全体からアクセスする必要のある設定値などの変数をどこに確保したら良いかが 問題となってきます。これらは複数のオブジェクトのメンバ変数としてバラバラに確保されたり すると管理も大変になるので構造体などで一元管理しておきたいものです。 でも複数のオブジェクトからそれぞれどうやって一元管理したデータにアクセスすれば いいでしょうか?

アプリケーションを作成した場合、アプリケーション本体のオブジェクトとして theAppが作成されます。とにかくこれが最初のオブジェクトで、ダイアログベースのアプリでは このオブジェクトがダイアログを生成し、DoModal( )します。

したがってアプリケーション全体からアクセスされる設定値やワーク変数は、アプリケーション本体 であるこのtheAppオブジェクトが持つべきです。すなわちtheAppオブジェクトのメンバとして ワーク構造体を持たせるといいでしょう。この構造体は他のオブジェクトから当然アクセスするので パブリックメンバにしておきます。

次に他のオブジェクトからtheAppのメンバ変数である構造体にアクセスするにはtheAppのアドレスを 取得する必要があります。これは関数AfxGetApp( )で可能です。ただしこの関数はtheAppの 基本クラスであるCWinAppクラス型のポインタを返すので、そのままでは派生クラスオブジェクトで あるtheAppで追加されたメンバに直接アクセスできません。したがってそのアドレスを派生クラスの ポインタとしてキャストする必要があります。つまり ((CTestApp*)AfxGetApp( ))->member; とする ことによりメンバ変数にアクセス可能となります※。
※ただしここではアプリケーションクラス名をCTestAppとしています。

後はこのメンバ変数の構造体の内容をレジストリなりINIファイルから読み込んだり書き出したり 好きにしてください(笑)。
目次に戻る

 
◆ ダイアログのスクロールバー
リソースエディタでダイアログにスクロールバーを追加できますが、その制御はコントロール 変数を割り当てて行うのではなく、ダイアログ自身の(正確には親クラスの)メンバ関数で行います。 詳細はヘルプの「CWnd スクロール関数」を確認してください。

下記にスクロールバーの設定例を示します。 SCROLLINFO ScrollInfo; ScrollInfo.cbSize = sizeof( SCROLLINFO ); ScrollInfo.fMask = SIF_ALL|SIF_DISABLENOSCROLL; ScrollInfo.nMin = min; // 最小値 ScrollInfo.nMax = max; // 最大値 ScrollInfo.nPage = page; // スクロールバーのサイズ ScrollInfo.nPos = 0; // スクロールバーの位置 ScrollInfo.nTrackPos = NULL; // お約束 // 水平スクロールバーへの設定の場合 this->SetScrollInfo( SB_HORZ, &ScrollInfo ); // 垂直スクロールバーへの設定の場合 this->SetScrollInfo( SB_VERT, &ScrollInfo ); // 両方のスクロールバーへの設定の場合 this->SetScrollInfo( SB_BOTH, &ScrollInfo ); スクロールバーが無効になる条件の場合、デフォルトでは非表示になりますが上記のように SIF_DISABLENOSCROLLオプションを付けておくと非表示の代わりに淡色化されます。

またスクロールバーが操作された時のハンドラはクラスウィザードのメッセージマップで WM_HSCROOLおよびWM_VSCROOLコマンドを実装することで可能です。ただスクロールバーは 自動的に位置を変えて再描画してはくれず、同ハンドラの引数でどの様に動かされたかを 判定し、自分でスクロールバーの位置を変更しなければなりません。
ここにサンプルを示します。
目次に戻る

 
◆ プリコンパイル済みヘッダーの…
コンパイル時に、 「fatal error C1010: プリコンパイル済みヘッダーの検索中に予期しない EOF を検出しました。」 と表示されエラーになる場合は、そのソースファイルの先頭で #include "stdafx.h" する必要があります。
目次に戻る

 
◆ メニューの動的な追加
メニューバーやコンテキストメニュー(ポップアップメニュー)を動的に変更する方法を解説します。 クラスウィザードでは実現できないので、マニュアルでソースを変更しないとできません。

まず、メニューを実装するオブジェクトのヘッダファイルを変更します。 以下に示す★マークが追加が必要な部分です。なお下のコードは例なので必ずしも一致するとは 限りません。 // インプリメンテーション protected: HICON m_hIcon; // 生成されたメッセージ マップ関数 //{{AFX_MSG(CMenutestDlg) virtual BOOL OnInitDialog( ); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint( ); afx_msg HCURSOR OnQueryDragIcon( ); afx_msg void OnContextMenu(CWnd* pWnd, CPoint point); ★ afx_msg void OnInitMenu(CMenu* pMenu); // メニュー初期化ハンドラ追加 //}}AFX_MSG ★ afx_msg void OnExecMenu( UINT uID ); // メニュー項目実行ハンドラ追加 DECLARE_MESSAGE_MAP( ) メニュー項目実行ハンドラの追加位置が、//{{AFX_MSG 〜 //}}AFX_MSG の外側に書くのは、 このハンドラ一つで動的に追加された複数のメニュー項目からのメッセージコマンドを受け付ける ことによるからです。ClassWizardは複数のメッセージコマンドを一つの関数で受け付けるのに 対応していないので、ClassWizardの編集対象外の位置に追加します。なお動的に追加するメニュー の個数が少なく既定の場合は、個別にハンドラを作成し //{{AFX_MSG 〜 //}}AFX_MSG 内に記述する こともできると思います。

次にメニューを実装するオブジェクトのソースファイルを変更します。 BEGIN_MESSAGE_MAP(CMenutestDlg, CDialog) //{{AFX_MSG_MAP(CMenutestDlg) ON_WM_SYSCOMMAND( ) ON_WM_PAINT( ) ON_WM_QUERYDRAGICON( ) ON_WM_CONTEXTMENU( ) ON_WM_INITMENU( ) // ★メニュー初期化メッセージコマンド追加 //}}AFX_MSG_MAP ON_COMMAND_RANGE( 1000, 1099, OnExecMenu ) // ★メッセージコマンド追加 END_MESSAGE_MAP( ) 上記変更によってメニューオブジェクトが初期化される際と動的に追加されたメニュー項目が 選択実行された場合に、これから用意するハンドラOnInitMenu( ), OnExecMenu( )が実行されるよう になります。メッセージコマンド追加部分の1000, 1099というのはメニュー項目を動的に追加する 時に指定する、その項目が選択実行された場合に発行されるメッセージコマンドIDです。
なお動的に追加するメニューが少なく既定の場合で個別にハンドラを作成する時は //{{AFX_MSG_MAP 〜 //}}AFX_MSG_MAP の内側にON_COMMANDマクロで記述します。

メニューオブジェクトが初期化される際に実行されるハンドラを追加します。 // メニュー初期化ハンドラ void CMenutestDlg::OnInitMenu(CMenu* pMenu) { CDialog::OnInitMenu(pMenu); // ここでメニューの以前の項目の削除と新しい項目の追加を行う CString str; pMenu->GetMenuString( 0, str, MF_BYPOSITION ); if( str == "TEST0" ){ pMenu = pMenu->GetSubMenu( 0 ); pMenu->RemoveMenu( 1, MF_BYPOSITION ); pMenu->AppendMenu( MF_STRING, 1000, "MENU APPEND" ); DrawMenuBar( ); } else if( str == "POPUP0" ){ pMenu->AppendMenu( MF_STRING, 1010, "POPUP APPEND" ); } } 上記の例では先頭項目が"TEST0"というメニューバーに「最近使ったファイル」のような感じで "MENU APPEND"という項目を追加しているのと、先頭項目が"POPUP0"というコンテキストメニューに "POPUP APPEND"という項目を追加しています。
このハンドラはメニューが開かれる度に毎回実行されるので、メニューバーのように削除されずに 残っているものは、重複して追加されないように前もって追加する位置の項目を削除してから追加 しています。
また上記例では追加した項目が選択実行された場合にメッセージコマンドIDである1000と1010が次に 実装されるハンドラに渡されます。 // メニュー項目実行ハンドラ void CMenutestDlg::OnExecMenu( UINT uID ) { afxDump << uID << "\n"; } uIDの値を見て、どのメニュー項目が選択実行されたかを知るわけです。このハンドラはIDが 1000〜1099までのものを渡されるので、メニュー項目を追加する場合にそれらのIDを用いる ようにします。なおIDの値には特に制限はないようですが、シンボルブラウザで重複しないように 定義しておくのが気持ちがいいかしれません。

最後に
これ はお気に入りフォルダ以下を検索し階層メニューを作成するサンプルです。
目次に戻る

 
◆ 文字列リスト
複数のCStringオブジェクトを管理したい場合にCStringListオブジェクトが便利です。 自分で作ろうかと思ってたら既にありました。ぐはー。 CStringList StrList; CString str = "TEST0"; StrList.AddHead( str ); StrList.AddHead( "TEST1" ); StrList.AddHead( "TEST2" ); StrList.AddHead( "TEST3" ); afxDump << StrList.GetCount( ) << "\n"; POSITION pos = StrList.GetHeadPosition( ); while( pos ){ str = StrList.GetNext( pos ); afxDump << "[" << pos << "]: " << str << "\n"; } 注意するべき点はリスト内部で管理されたCStringオブジェクトの位置はPOSITION型だと いうことです。連続して読み出す場合などは上記例のwhileループ内部を参考にしてください。 詳細はCStringListのヘルプ参照のこと。

とか言ってたら配列のように管理できるクラスがありました。ズガビーン!!!!
CStringArrayがそうです。以下にサンプルを示します。 CStringArray StrArray; CString str = "TEST0"; StrArray.Add( str ); if( StrArray.GetSize( ) > 5 ) StrArray.RemoveAt( 0 ); StrArray.Add( "TEST1" ); if( StrArray.GetSize( ) > 5 ) StrArray.RemoveAt( 0 ); StrArray.Add( "TEST2" ); if( StrArray.GetSize( ) > 5 ) StrArray.RemoveAt( 0 ); for( int i=0; i<StrArray.GetSize( ); i++ ){ afxDump << StrArray.GetAt( i ) << "\n"; } 上の例では配列要素数を最大5個に制限しています。
なんだーこっちの方が簡単じゃん!!
目次に戻る

 
◆ 「ファイルを開く」ダイアログの使用法
ファイル名を指定するのに良く使われる「ファイルを開く」ダイアログはコモンダイアログと 呼ばれており以下のようにして簡単に使用できます。 下の例では取得したファイル名をエディットボックスのコントロール変数にセットしています。 /* TRUEだと「ファイルを開く」,FALSEだと「ファイルを名前を付けて保存」 */ CFileDialog dlg( TRUE ); long nResponse = dlg.DoModal( ); if( nResponse == IDOK ){ /* エディットボックスにセット */ m_Filename.SetWindowText( dlg.GetPathName( ) ); } また拡張子を選択するような場合は,以下のように行います。 CFileDialog dlg( TRUE, ".jpg", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "読込み可能ファイル(*.jpg;*.bmp)|*.jpg;*.bmp|全てのファイル(*.*)|*.*||" ); long ret = dlg.DoModal( ); if( ret == IDOK ){ m_FileName.SetWindowText( dlg.GetPathName( ) ); } フォルダの初期位置やキャプションを指定するにはDoModal( )を行う前にダイアログのメンバ変数を変更します。 CFileDialog dlg( FALSE ); dlg.m_ofn.lpstrTitle = "お気に入りに追加"; // キャプションの変更 dlg.m_ofn.lpstrInitialDir = "C:\\WINDOWS\\FAVORITE"; // フォルダの初期位置の変更 dlg.m_ofn.lpstrFile = "filename.dat"; // デフォルトファイル名の設定 dlg.m_ofn.nMaxFile = MAX_PATH; // ファイル名の最大文字数 long nResponse = dlg.DoModal( ); lpstrFileをフルパスでセットした場合はlpstrInitialDirを設定しなくてもそのフォルダが開きます。

またフォルダを選択するダイアログも見たことがあるかと思いますが、 残念ながらコモンダイアログでは用意されていません。

実装するにはシェル関数を使う必要がありますが、気が向いたのでフォルダダイアログという クラスを作ってみました。
これです。
作成にはかぶさんという方の サイトを参考にしました。 ここの シェルエクステンションという項目です。
以下のようにして使用します。 CFolderDialog dlg( "フォルダを選択してください" ); if( dlg.DoModal( this->GetSafeHwnd( ) ) == IDOK ){ CString path = dlg.GetPathName( ); }
目次に戻る

 
◆ メニューの実装
ダイアログベースのアプリケーションにメニューを追加したり,マウス右ボタンのクリックで 表示されるコンテキストメニュー(ポップアップメニュー)を実装する方法を解説します。
  1. リソースエディタのリソース一覧表示部分で右クリックし「挿入...」を選択,Menuを挿入する。
  2. 新規追加されたメニューリソースのプロパティで適当なIDをつける。例"IDR_MENU"
  3. メニューアイテムを作成する。このときアイテムのプロパティで「ポップアップ」をチェックすると, 子メニューを作成することができる。
  4. リソースエディタでメニューのプロパティからクラスウィザードを起動すると「クラスの追加」という 問い合わせてくるので「既存のクラスを選択」しメニューを表示するダイアログを選択する。
  5. 起動したクラスウィザードで追加したメニューアイテムのIDを選択しメッセージからCOMMANDを ダブルクリックすると,メニューアイテムを選択したときに実行されるハンドラ関数名を聞いてくるので設定する。この操作を各アイテム毎に行う。
  6. それぞれのハンドラ関数にやりたいことを実装する。
ダイアログにメニューを追加する場合はリソースエディタでダイアログ自身のプロパティを変更します。 一般タブの「メニュー」に追加するメニューIDを設定します。メニューアイテムを有効・無効にしたり チェックマークを付けたりするにはダイアログオブジェクト内で以下のようにします。 CMenu *pMenu = GetMenu( ); pMenu->EnableMenuItem( IDM_?, MF_GRAYED|MF_BYCOMMAND ); // 無効(淡色)にする pMenu->EnableMenuItem( IDM_?, MF_ENABLED|MF_BYCOMMAND ); // 有効にする pMenu->CheckMenuItem( IDM_?, MF_CHECKED|MF_BYCOMMAND ); //チェックマークを付ける pMenu->CheckMenuItem( IDM_?, MF_UNCHECKED|MF_BYCOMMAND ); //チェックマークを外す DrawMenuBar( ); // メニューの再描画 コンテキストメニューを作成する場合はクラスウィザードを起動してダイアログのメッセージから WM_CONTEXTMENUを選択して以下のようにハンドラ関数を実装します。 void CTmpDlg::OnContextMenu(CWnd* pWnd, CPoint point) { CMenu menu; menu.LoadMenu( IDR_MENU ); // IDR_MENU はメニューリソースID CMenu *pPopup = menu.GetSubMenu( 0 ); // コンテキスト(ポップアップ)メニュー表示 pPopup->TrackPopupMenu( TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, this ); } 【メニュー追記】

★動作が変?

ダイアログでないものからコンテキストメニューを表示して,ある項目を選択したらダイアログを 開くという実装をしようと,マウスの右クリックハンドラでコンテキストメニューを実装したら ,開いたダイアログ上でマウス操作ができないという現象が発生しました。

開いたダイアログ上で一度右クリックするとその後は期待通りマウス操作できるようになるのですが,
これは TrackPopupMenu( ) の仕様がからんでいるようです(詳細は同関数のヘルプを参照)。

マウス右クリックハンドラでは,TrackPopupMenu( ) を呼び出す前にデフォルトハンドラの OnRButtonDown( ) などを呼び出しておけば上記のような現象は発生しないようです。


★メニューでのトグル動作を簡単に実装する

メニュー項目の選択で,チェックマークを付けたり外したりしてトグル的な操作をしたい 場合はメニュー項目のイベントハンドラ ON_COMMAND と ON_UPDATE_COMMAND_UI を実装して 以下のような処理を追加すると簡単にできます。 // メニュー項目の ON_COMMAND メッセージハンドラ BOOL bShow = m_wndXXXX.IsVisible(); ShowControlBar(&m_wndXXXX, !bShow, FALSE); // メニュー項目の ON_UPDATE_COMMAND_UI メッセージハンドラ pCmdUI->Enable(); pCmdUI->SetCheck(m_wndXXXX.IsVisible()); 上記の例ではウインドウ m_wndXXXX の表示,非表示をメニューでトグル的に操作しています。
目次に戻る

 
◆ プロパティシート
オプション設定などでよく用いられるタブがいくつかついたダイアログはプロパティシートで作成できます。
以下はメニューからオプションを選択するとプロパティシートが開かれるコードを実装する手順です。
  1. 挿入→コンポーネントでプロパティシートを追加する(アクセス権はベースとなるダイアログに設定する)
  2. ウィザードによりプロパティシートクラスと各シートのクラス,およびリソースが自動的に作成される。クラス名はわかりやすい名前に変更した方がいいでしょう。
  3. リソースエディタで追加された各シートのプロパティのキャプションを変更しタブ文字列とする。
  4. 同じくリソースエディタで追加された各シートに好きなコントロールをひょいひょいと載せる。ID設定も気の利いたものを。
  5. 搭載したコントロールにメンバ変数を「値」で割り付ける。
  6. リソースビューで「挿入...」しMenuを追加する。
  7. メニューバーについてクラスウィザードを起動すると「クラスの追加」ウインドウが開くので 「既存のクラスを選択」にし,メニューを表示させる元ダイアログやフレームを選択する。
  8. メニューバーを表示させるダイアログやフレームについてリソースエディタでプロパティを表示し 「一般」タブ内にある「メニュー」をさっき追加したメニューのIDを設定する。
  9. メニューバーに項目「オプション」を追加する。IDはIDM_OPTION等。
  10. 追加した項目についてクラスウィザードを起動し,メッセージのCOMMANDをダブルクリックして、 メニュー選択ハンドラを実装する。
  11. メニュー選択ハンドラから、プロパティシートを追加したウィザードによって自動追加されたOnProperties( )を呼び出す。
  12. OnProperties( )内で適宜以下のような変更を行う。 CMyPropertySheet propSheet; // 適用ボタンを消す propSheet.m_psh.dwFlags |= PSH_NOAPPLYNOW; // ヘルプボタンを消す propSheet.m_psh.dwFlags &= ~PSH_HASHELP; propSheet.m_Page1.m_psp.dwFlags &= ~PSP_HASHELP; propSheet.m_Page2.m_psp.dwFlags &= ~PSP_HASHELP; propSheet.m_Page3.m_psp.dwFlags &= ~PSP_HASHELP; //現在の設定値をメンバ変数にセットする。 m_Static1.GetWindowText( propSheet.m_Page1.m_Edit ); m_Static2.GetWindowText( propSheet.m_Page2.m_Edit ); m_Static3.GetWindowText( propSheet.m_Page3.m_Edit ); if( propSheet.DoModal( ) == IDOK ){ // OKが押されたときにメンバ変数を読み取って設定に反映させる。 m_Static1.SetWindowText( propSheet.m_Page1.m_Edit ); m_Static2.SetWindowText( propSheet.m_Page2.m_Edit ); m_Static3.SetWindowText( propSheet.m_Page3.m_Edit ); } とする。
プロパティシートの大きさも各シートの大きさに自動的にあわせてくれるし,うぉー結構簡単だったぜぃ!

プロパティシートのキャプションを変更するにはリソースビューのストリングテーブルに登録された データを変更する必要があります。

適用ボタンを有効にする場合、プロパティページで SetModified( ) を実行します。適用ボタンが押された 場合、めくられたプロパティページの各 OnOK( ) ハンドラが呼ばれるので処理が必要な場合は クラスウィザードで実装しておいてください。

プロパティページを増やす場合はリソースエディタでダイアログを追加作成(プロパティは他の プロパティページのものに合わせる)し、基本クラスをCPropertyPageとした新規クラスを作成します。 後は他のページと同じようにプロパティシートに追加すればOKです。 後から追加したプロパティページは別ファイルになってしまうので #include など手動で追加する 必要があります。

デバグ実行させた後、 「例外処理(初回)はxxx.exe(COMCTL32.DLL)にあります:0xC0000005:アクセス違反」 と表示されることがあります。

本家 Microsoft の
解説によると… メモ 対応するダイアログ リソースから最初にプロパティ ページが作成されるとき、 初回例外が発生する可能性があります。これは、プロパティ ページによって、 ページの作成前にダイアログ リソースのスタイルが必要なスタイルに変更される ためです。通常、リソースは書き込み禁止であるため、例外が発生する可能性が あります。この例外はシステムによって処理され、システムによって、更新された リソースのコピーが自動的に作成されます。このような処理により、初回例外は 無視できるようになります。 だそうです。

無視しても構わないようですが、気持ち悪ければリソースファイル(*.rc)をテキストエディタで 開き、各プロパティページで FONT 9, "MS Pゴシック" という項目を削除すれば、 例外は発生しなくなります。リソースを変更したらまた元に戻っちゃいますが。

またプロパティページに拡張ダイアログがあるとデバグ実行した時に (X) Debug Assertion Failed! など とエラーウインドウが表示されます。原因を追ってみると Msdev\Mfc\src\Dlgprop.cpp の834行目に こんな記述がありました。 // WINBUG: Windows currently does not support DIALOGEX resources! // Assert that the template is *not* a DIALOGEX template. // DIALOGEX templates are not supported by the PropertySheet API. // To change a DIALOGEX template back to a DIALOG template, // remove the following: // 1. Extended styles on the dialog // 2. Help IDs on any control in the dialog // 3. Control IDs that are DWORDs // 4. Weight, italic, or charset attributes on the dialog's font ようするにバグというか仕様らしいです。 プロパティページに載せるダイアログアイテムのプロパティの拡張スタイルは全て設定しないように すれば大丈夫みたいです。
目次に戻る

 
◆ ファイルの読み書き
CFile クラスを使用するのが簡単です。 CFile File; BYTE *pFileImage; /* ファイルダイアログによるファイル名の取得 */ CFileDialog dlg( TRUE ); INT_PTR nRet = dlg.DoModal(); if( nRet == IDOK ){ // ファイル読み込み if( File.Open( dlg.GetPathName(), CFile::modeRead ) == TRUE ){ UINT nLen = (UINT)File.GetLength(); pFileImage = new BYTE [nLen]; File.Read( pFileImage, nLen ); File.Close(); } /* ファイル新規作成(上書き) */ if( File.Open( dlg.GetPathName(), CFile::modeCreate|CFile::modeWrite ) == TRUE ){ unsigned int nData; File.Write( &nData, sizeof(unsigned int) ); File.Close( ); } } 1行ずつテキストを読み書きするだけなら以下のように CStdioFile を使うのが一番簡単です。
筆者はこのクラスを最近まで知りませんでした(苦笑)。 ファイルのクローズもデストラクタでやってくれるようです。 CStdioFile File( pszFilename, CFile::modeReadWrite|CFile::typeText ); CString String; File.ReadString( String ); File.WriteString( String );
目次に戻る

 
◆ Lineの描画
// 描画領域としてリソースエディタでピクチャーコントロールを作成し // コントロール変数を付加 CStatic m_Area; /* ペン形式を設定する変数を確保しておく */ CPen Pen; /* 初期化関数でペン形式を設定しておく */ Pen.CreatePen( PS_SOLID, 1, RGB( 0x00, 0xff, 0x00 ) ); /* 描画が必要な場所で以下のように描画を行う */ long x0, y0, x1, y1; CPen *pPenOld; CDC *pDC; pDC = m_Area.GetDC( ); pPenOld = pDC->SelectObject( &Pen ); /* もちろん座標の設定が必要(x0,y0,x1,y1) */ pDC->MoveTo( x0, y0 ); /* 始点 */ pDC->LineTo( x1, y1 ); /* 終点 */ pDC->SelectObject( pPenOld ); /* デバイスコンテキストの開放を忘れないこと */ ReleaseDC( pDC );
目次に戻る

 
◆ タイマーと時間処理
例えば1秒毎に何か処理をしたいというプログラムは「タイマー」を利用すると可能です。

タイマーの実装方法は,クラスウィザードのメッセージマップで WM_TIMER メッセージを追加して, 作成された OnTimer ハンドラに好きな処理を実装するだけです。 タイマーの初期化はOnInitDialogなど初期化処理の中で SetTimer( 1, 1000, NULL ); などとします。 タイマーの起動周期はmsec単位で指定できますが,あまり早くするとシステムの負担が大きくなります。

またある処理の中で1秒間だけ休止したいという場合は Sleep( 1000 ); とすると1000msecだけ休止します。 休止期間中は他のスレッドが動くのでシステムに負担をかけません。

さらにその時々の詳細な時間を調査したい場合は GetTickCount( ); を使用します。 この関数はWindowsが起動してからの時間がmsec単位で戻ります。戻り値がDWORDなので 49日位すると0に戻ってしまいますので注意してください。

目次に戻る

 
◆ オプションダイアログの搭載
メニューからオプションを選択、何々の設定…とかやる方法。
  1. リソースビューでDialogを右クリックし「Dialog」の挿入を行う。
  2. 追加したダイアログに好きなコントロールをひょいひょいと載せる。ID設定も気の利いたものを。
  3. 搭載したコントロールにメンバ変数を「値」で割り付ける。
  4. 追加したダイアログについてクラスウィザードを起動すると「クラスの追加」ウインドウが開くので で「新規クラスの作成」を選択する。
  5. クラス名はCOption等,基本クラスはCDialogにする。
  6. リソースビューで「挿入...」しMenuを追加する。
  7. メニューバーについてクラスウィザードを起動すると「クラスの追加」ウインドウが開くので 「既存のクラスを選択」にし,メニューを表示させる元ダイアログやフレームを選択する。
  8. メニューバーを表示させるダイアログやフレームについてリソースエディタでプロパティを表示し 「一般」タブ内にある「メニュー」をさっき追加したメニューのIDを設定する。
  9. メニューバーに項目を追加する。IDは何でもいいらしい。ID_MENU等。
  10. さらに追加した項目についてクラスウィザードを起動し,メッセージのCOMMANDをダブルクリックして、 ハンドラ関数名を決定する。
  11. そのハンドラ内で // TODO: この位置にコマンド ハンドラ用のコードを追加してください COption dlg; // 現在の設定値をメンバ変数にセットする。 dlg.m_data = data; if( dlg.DoModal( ) == IDOK ){ // OKが押されたときにメンバ変数を読み取って設定に反映させる。 data = dlg.m_data; } とする。
  12. 上記ハンドラが記述されるファイルでCOptionクラスの定義ヘッダファイルを#includeする。

目次に戻る

 
◆ ドロップしたファイルの名前を受け取る
エクスプローラからドラッグ&ドロップしてきたファイルを開かずに、 そのファイル名だけ受け取る方法。

・ダイアログベースの場合 デフォルトではファイルのドロップイベントを受け取れない。 クラスウィザードでもイベントを追加できないので、手作業で以下のことを行う。 (★マークがついている個所が追加部分です) 1. ダイアログ.h にメッセージマップ関数を追加定義 // 生成されたメッセージ マップ関数 //{{AFX_MSG(C????Dlg) virtual BOOL OnInitDialog( ); afx_msg void OnPaint( ); afx_msg HCURSOR OnQueryDragIcon( ); afx_msg void OnDropFiles( HDROP hDropInfo ); // ★ ドロップハンドラ追加 virtual void OnOK( ); //}}AFX_MSG DECLARE_MESSAGE_MAP( ) 2. ダイアログ.cpp にメッセージマップ関数を追加 BEGIN_MESSAGE_MAP(C????Dlg, CDialog) //{{AFX_MSG_MAP(C????Dlg) ON_WM_PAINT( ) ON_WM_QUERYDRAGICON( ) ON_WM_DROPFILES( ) // ★ ドロップハンドラ追加 //}}AFX_MSG_MAP END_MESSAGE_MAP( ) 3. 同じくダイアログ.cpp の OnInitDialog( ) にドロップイベントを許可する旨を追加 // TODO: 特別な初期化を行う時はこの場所に追加してください。 DragAcceptFiles( ); // ★ ファイルドロップを許可 4. ドロップイベントハンドラを追加 ★// ファイルドロップイベントハンドラ ★void C????Dlg::OnDropFiles( HDROP hDropInfo ) ★{ ★ long files; ★ char filename[256]; ★ ★ /* ドロップされたファイル数を取得 */ ★ files = DragQueryFile( hDropInfo, 0xFFFFFFFF, filename, 256 ); ★ ★ /* ドロップされた一番目のファイル名文字列を取得 */ ★ DragQueryFile( hDropInfo, 0, filename, 256 ); ★ ★ CEdit *pEdit = (CEdit*)GetDlgItem( IDC_???? ); ★ pEdit->SetWindowText( filename ); ★ ★ // CWnd::OnDropFiles( hDropInfo ); ★} 上記の例ではエディットボックスに取得したファイル名をセットしています。

・SDI(MDI)の場合 デフォルトではファイルを開こうとしてしまう。 1. クラスウィザードで MainFrame に OnDropFiles イベントを追加する。 2. OnDropFiles イベントハンドラを以下のように修正。 void CMainFrame::OnDropFiles(HDROP hDropInfo) { // TODO: この位置にメッセージ ハンドラ用のコードを追加するか // またはデフォルトの処理を呼び出してください ★ CM3UView *pView; ★ pView = (CM3UView *)GetActiveView( ); // これが勝利の鍵だ!! ★ pView->OnDropFiles( hDropInfo ); ★ ★ DragFinish( hDropInfo ); ★ // CFrameWnd::OnDropFiles(hDropInfo); } デフォルトのイベントハンドラをコメントアウトして、 ビューの OnDropFiles を呼び出すようにする。 3. MainFrm.cpp にドキュメントクラスとビュークラスの .h ファイルを #include する。 4. クラスウィザードでビューに OnDropFiles イベントを追加する。 5. ビューの OnDropFiles イベントハンドラを以下のように修正。 void CM3UView::OnDropFiles(HDROP hDropInfo) { // TODO: この位置にメッセージ ハンドラ用のコードを追加するか // またはデフォルトの処理を呼び出してください ★ long files; ★ char filename[256]; ★ ★ /* ドロップされたファイル数を取得 */ ★ files = DragQueryFile( hDropInfo, 0xFFFFFFFF, filename, 256 ); ★ ★ /* ドロップされた一番目のファイル名文字列を取得 */ ★ DragQueryFile( hDropInfo, 0, filename, 256 ); ★ ★ CEdit *pEdit = &GetEditCtrl( ); ★ CString string; ★ pEdit->GetWindowText( string ); ★ if( string.GetLength( ) > 0 ) string += "\r\n"; ★ string += filename; ★ pEdit->SetWindowText( string ); ★ ★ // CEditView::OnDropFiles(hDropInfo); } この例では CEditView に取得したファイル名をどんどん追加していってます。 ドロップするファイルは1つとは限らないので、ファイル数の分だけループする と良いでしょう。
目次に戻る

 
◆ MDIウインドウをスプリット化する
エクスプローラなどでは一つのウインドウが左と右に別れていますが、 これがウインドウのスプリットです。 PowerWaveも上の方には波形全体を表示するウインドウ、 下の方には拡大できるエディットウインドウを作成しようと思っています。
さて、MDI(マルチドキュメントインタフェース)で子ウインドウをスプリットするには 以下のような方法が有効でした。 1. AppWizardでMDIを選択しプロジェクトを作成する。 2. 途中ステップ6/6で、ビュークラスを適当な名前に変更する。 (プロジェクト名と同じ名前のビューだとよくわからなくなるので) 3. ClassWizardで新規ビュークラスを作成する。 4. ChildFrm.h のCChildFrameに public: CSplitterWnd m_wndSplitter; を追加する。 5. ClassWizardで ChildFrame::OnCreateClient のイベントハンドラを実装する。 6. そのハンドラに以下のコードを追加する。 if( !m_wndSplitter.CreateStatic( this, 2, 1 ) || !m_wndSplitter.CreateView( 0, 0, RUNTIME_CLASS( CWholeView ), CSize( 100, 100 ), pContext ) || !m_wndSplitter.CreateView( 1, 0, RUNTIME_CLASS( CPartialView ), CSize( 0, 0 ), pContext ) ){ MessageBox( "スプリットウインドウの作成に失敗しました", NULL, NULL ); return( FALSE ); } 上の例ではCWholeViewとCPartialViewがスプリットされたウインドウの 2つのビュークラスです。 7. ChildFrm.cppにビュークラスのヘッダファイルを#includeする。 こんな感じです。関数の詳しい情報などはヘルプを読めばわかると思います。

目次に戻る

 
◆ ツールヒントを出そう
マウスを置いてしばらくすると、漫画のセリフのような小さなヘルプがでるヤツが ツールヒントです。これを出すのは凄く簡単!! 挿入→コンポーネントでツールヒントを選択し、導入して下さい。 自動的にコードにインプリメントされます。 やらなくちゃいけないのは、どのオブジェクトでどの文字列を出すかという設定だけ。 以下のようなコードが自動的に挿入されているはずなので、適切に設定します。 // TODO: コントロールの追加には以下のどちらかの形式を使用してください: // m_tooltip.AddTool(GetDlgItem(IDC_<name>), <string-table-id>); // m_tooltip.AddTool(GetDlgItem(IDC_<name>), "<text>"); // 実際の設定例 m_tooltip.AddTool(GetDlgItem(IDC_EDIT), IDS_TIP_EDIT ); これこそストリングテーブルで用意した方が後々らくですね。;-)

これでそれっぽいアプリになりました。 そうそうこれは、ボタンとかエディットボックスとか具体的に動作するオブジェクトでしか ヒントが出ないようです。CStaticには出ないみたい…。

[後日談]
挿入→コンポーネントではなんとプロパティページにはツールヒントを導入できないことが発覚!! おいおい一番必要なところなのに…。

しょうがないのでツールヒントを手動でインプリメントする方法を調べました。

まずツールヒントを表示したいダイアログ(プロパティページ)クラスの protected: メンバに コントロール変数を追加します。 protected: CToolTipCtrl m_tooltip; 次にダイアログの OnInitDialog( ) で以下のコードを追加します。 m_tooltip.Create( this ); m_tooltip.Activate( TRUE ); // 以下のどちらかの形式でヒントを表示したいダイアログアイテムに // 表示するテキストを割り当てます // m_tooltip.AddTool( GetDlgItem(IDC_<name>), <string-table-id> ); // m_tooltip.AddTool( GetDlgItem(IDC_<name>), "<text>"); さらにダイアログに PreTranslateMessage ハンドラをクラスウィザードで実装し以下のように コーディングします。 BOOL C????Dlg::PreTranslateMessage(MSG* pMsg) { // ツールヒント処理 m_tooltip.RelayEvent(pMsg); return CDialog::PreTranslateMessage(pMsg); } 以上でツールヒントの実装は終了です。

[後々日談]
なんとVC++4.0でのツールヒントは1行テキストしか対応していないことが判明。ズガビーン!!
調べによるとVC++6.0では複数行に対応しているみたいだけど、そんなの買う金があったら未だに VC++4.0なんて使ってねーよ! ということで、CToolTipCtrlから複数行対応の派生クラス作成に挑戦!!

OnPaintあたりでもオーバーライドすれば楽勝だろうなんて思っていたら、なんとOnPaintが 実行されないことが分かってあきらめかけましたが、WindowProcを丸ごとオーバーライドして なんとかできました。
これが 複数行対応のCToolTipsCtrlです。色とフォントも変えられます。 CToolTipCtrlと差し替えてそのまま使用できます。

目次に戻る

 
◆ キャプションや値を変更してみよう
スピンボタンのコントロールと同じように、クラスウィザードでオブジェクトへの コントロール変数を割り当てます。CStaticだったりCButtonだったりCEditだったり しますが、これらは全てCWndの派生クラスです。つまりメンバ関数である GetWindowTextでそのオブジェクトのキャプションを取得でき、SetWindowTextで キャプションを設定(変更)することが可能です。 これでCEditの場合はエディットボックスの中身を参照・変更できます。
目次に戻る

 
◆ ストリングテーブルを使う
管理上、メッセージなんかはストリングテーブルで リソースとして一元管理したいところ。 リソースエディタのツリーにて右クリックし、挿入、String Tableを選択すると ストリングテーブルを作成することができます。 このテーブルにIDS_からはじまるIDとそれに定義するメッセージを追加し、 ソース中でこの文字列を使いたくなったら、以下のようにしてロードして使用します。 CString str; // ロード用CString変数が必要 str.LoadString( IDS_MESSAGE ); // リソースから文字列をロード pDC->TextOut( 0, 0, str ); // などとして描画 これで多言語化もバッチリ?! (^^
目次に戻る

 
◆ DCちょっといい話
ウインドウに何らかの描画を行う場合デバイスコンテキストを取得しますがここでまとめておきます。

デバイスコンテキストとはWindowsシステムの中の画家さん達です。GDIオブジェクトという ビットマップやフォントやペンが画材です。画家さん達は全部で5人しかいません。つまりシステムで GetDC( )できるのは同時に5回までです。これを越えると予期しない領域に描画が行われたりして Windowsの中の人も大変です。したがってデバイスコンテキストは必要なときに取得し、用が済んだら 解放します。アプリのメンバ変数などで抱え込んではいけません。
デバイスコンテキストをGetDC( )したのにReleaseDC( )しなくてもエラーにはなりません。 せめてデバイスコンテキストが足りなくなったらメッセージ出してもいいのに…

またCDCオブジェクトを自分で生成し、CreateDC( ), CreateIC( ), CreateCompatibleDC( ) で作成された デバイスコンテキストはデストラクタで自動的に後処理(DeleteDC( ))が行われるので解放を特に意識 する必要はありません。これはCClientDCやCWindowDCで生成した場合も同じです。

以下の2つのサンプルは同じ効果を得られます。 // ピクチャーコントロールのアドレスを取得 CWnd *pWnd = GetDlgItem( IDC_PICTURE ); // 描画領域座標を取得 CRect rect; pWnd->GetClientRect( rect ); // ピクチャーコントロールのデバイスコンテキストを取得 CDC *pDC = pWnd->GetDC( ); // 塗る pDC->FillSolidRect( rect, RGB(0,255,0) ); // フレームの再描画(必要であれば) pWnd->Invalidate( ); // デバイスコンテキストの開放を忘れないこと pWnd->ReleaseDC( pDC ); // ピクチャーコントロールのアドレスを取得 CWnd *pWnd = GetDlgItem( IDC_PICTURE ); // ピクチャーコントロール用のデバイスコンテキストを生成 CClientDC dc( pWnd ); // 描画領域座標を取得 CRect rect; pWnd->GetClientRect( rect ); // 塗る dc.FillSolidRect( rect, RGB(0,255,0) ); // フレームの再描画(必要であれば) pWnd->Invalidate( ); 後者では自分で生成したデバイスコンテキストでピクチャーコントロールのコンテキストを コンストラクタ内部で取得していますが、変数が破棄される際にデストラクタが解放を 自動的に行っているのでアプリケーションで明示的に解放する必要はありません。
状況にもよりますが、簡単な描画を行うのであれば後者の方が楽だと思います。

ところでデバイスコンテキストに関してフォントや色を変更した後,元に戻さないといけないのですが, ひとつひとつ元に戻すのはおっくうです。と思っていたら一度に復帰させる便利な関数がありました! // デバイスコンテキストの保存 int SavedDC = pDC->SaveDC( ); // 背景モードを透明に設定 pDC->SetBkMode( TRANSPARENT ); // テキスト色を設定 pDC->SetTextColor( RGB( 0, 0, 0 ) ); // 塗り塗り書き書き // デバイスコンテキストの復帰 pDC->RestoreDC( SavedDC ); どうやらシステムに保存するスタックがあるようです。

以下ちょっとした描画ルーチン集
CClientDC DC(this); // 背景の塗りつぶし CRect Rect; this->GetClientRect( Rect ); DC.FillSolidRect( Rect, RGB(0,0,0) ); // 枠の描画 DC.FrameRect( CRect(100,100,200,200), &CBrush( RGB(255,0,0) ) ); // 文字の描画 DC.SetBkMode( TRANSPARENT ); DC.SetTextColor( RGB(255,255,255) ); DC.TextOut( 0, 0, "ふがふが" );
目次に戻る

 
◆ Fillちょっといい話
ある決められた領域を塗りたい場合、その領域の座標x,y,w,hを#defineしておいて
pDC->FillSolidRect( x, y, w, h, color );とするのはちょっと鬱です。
あらかじめリソースエディタでそこにピクチャーオブジェクトを作成しておきましょう。 ピクチャーオブジェクトはCStaticクラスオブジェクトですがCWndの派生クラスなので 領域情報を得る関数GetWindowRect( )やデバイスコンテキストを取得する関数GetDC( )を 使用でき、以下のように座標を意識しなくても、この領域を塗ることができます。

リソースエディタで作成したピクチャーコントロールはタイプを「フレーム」にし、 IDを例えばIDC_PICTUREとしておき、ダイアログのOnPaint( )などで以下のように記述します。 // ピクチャーコントロールのアドレスを取得 CWnd *pWnd = GetDlgItem( IDC_PICTURE ); // 描画領域座標を取得 CRect rect; pWnd->GetClientRect( rect ); // ピクチャーコントロールのデバイスコンテキストを取得 CDC *pDC = pWnd->GetDC( ); // 塗る pDC->FillSolidRect( rect, RGB(0,255,0) ); // フレームの再描画(必要であれば) pWnd->Invalidate( ); // デバイスコンテキストの開放を忘れないこと pWnd->ReleaseDC( pDC ); レイアウトが気に入らなくなったらピクチャーコントロールの位置や大きさを リソースエディタで変更するだけでいいのでとっても楽チンですね。

上記の応用で、ピクチャーコントロールで位置と大きさを指定した子ウインドウを作成することもできます。 ピクチャーコントロールは非可視にしておき以下のようにします。 CRect Rect; GetDlgItem( IDC_PICTURE )->GetWindowRect( Rect ); this->ScreenToClient( &Rect ); CWnd Window; Window.Create( NULL, NULL, WS_VISIBLE, Rect, this, IDC_PICTURE ); Window.ModifyStyleEx( NULL, WS_EX_STATICEDGE, SWP_DRAWFRAME );
目次に戻る

 
◆ アイコン
ウインドウのタイトルバーにアイコンを追加・変更する場合は以下のようにします。 // アイコン変更 HICON hIcon = AfxGetApp()->LoadIcon( アイコンのリソースID ); this->SetIcon( hIcon, FALSE ); ボタンにアイコンを貼り付けるにも SetIcon( ) が使えますが,その前にひと手間あります。
リソースエディタのプロパティのスタイルで「アイコン」にチェックを入れておきます。 アイコンは同じくリソースエディタで,挿入-リソース-Iconで新規作成します。大きさは標準の32×32です。 それ以外だとリサイズされて品質が落ちてしまいます。貼り付けの位置はボタンの中央とアイコンの中央を 合わせるので図柄は真ん中辺に描いてください。
ボタンの ID が IDC_BUTTON ,アイコンの ID が IDI_ICON の場合,OnInitDialog で以下のようにすると 貼り付きます。 ((CButton *)GetDlgItem(IDC_BUTTON))->SetIcon(AfxGetApp( )->LoadIcon(IDI_ICON));
目次に戻る

 
◆ マウスカーソル
マウスカーソルの座標を取得および設定するには以下のようにします。 POINT Pos; GetCursorPos( &Pos ); // 取得 SetCursorPos( Pos.x, Pos.y ); // 設定 マウスカーソルが動いたときは、WM_MOUSEMOVE メッセージハンドラで受けられますが タイトルバーなど非クライアント領域でのイベントは得られません。
非クライアント領域でのイベントは WM_NCMOUSEMOVE で受けられますが、クラスウィザードでは 出てこないので手作業で実装する必要があります。
※VC++4.0以降の環境では選択できるかも。 VC++.NET 2003ではリソースプロパティのメッセージ一覧から選択できました。

作業は
「ドロップしたファイルの名前を受け取る」で OnDropFiles( ) を 実装した方法と同じです。ヘッダファイルとソースファイルにメッセージマップとハンドラの登録 を行います。 ヘッダファイル.h に afx_msg void OnNcMouseMove( UINT nHitTest, CPoint point ); を追加 ソースファイル.cpp にメッセージマップ ON_WM_NCMOUSEMOVE( ) とハンドラ void C????Dlg::OnNcMouseMove( UINT nHitTest, CPoint point ) { } を追加 なおウインドウ外のマウスイベントを取得するには SetCapture( ) すると得られます。 ただし受けるウインドウがアクティブである必要があります。

マウスカーソルのアイコンを変更する場合は以下のようにします。 // マウスカーソルを砂時計にする。 SetCapture( ); SetCursor( AfxGetApp( )->LoadStandardCursor( IDC_WAIT ) ); // マウスカーソルを元に戻す ReleaseCapture( ); 砂時計などの標準のカーソルは上記のように LoadStandardCursor( ) でロードしますが、 自分で作ったカーソルを表示するにはその代わりに LoadCursor( IDC_MYCURSOR ) を使い、 リソースエディタで作成したカーソルのIDを指定します。
マウスカーソルを SetCapture( ) しておかないと変わらないので注意してください。 また ReleaseCapture( ) も忘れずに。
目次に戻る

 
◆ ラジオボタン
ラジオボタンは複数の選択肢から一つを選択するためのボタン群です。

実装方法を以下に示します。
リソースエディタで複数のラジオボタンを並べて,最初のボタンのプロパティの一般でグループに チェックを入れます。その他のボタンはグループのチェックを入れないようにします。 さらにプロパティのスタイルを自動にしてチェックマークが自動でどれか一つに入るようにします。 最後にレイアウトメニューのタブオーダーでラジオボタン群が順番になるようにします。 タブオーダーの設定の仕方は、ダブルクリックして1番をセットし、あとは順番にシングル クリックしてセットしていきます。

各ボタンのクラスウィザードでBN_CLICKEDハンドラを実装すると,各ボタンが押されたときの 処理を実装することができます。

OnInitDialogなど初期化時にどれか一つを選択するには ((CButton *)GetDlgItem( IDC_RADIO_???? ))->SetCheck( 1 ); などとしてチェックマークを付けることができます。
目次に戻る

 
◆ スピンボタン
リソースエディタでエディットボックスとスピンボタンを関連づけて作成するには それぞれのコントロールを配置した後、レイアウトメニューのタブオーダーでスピンボタンの直前に エディットボックスがナンバリングされるようにします。タブオーダーの設定の仕方は、 ダブルクリックして1番をセットし、あとは順番にシングルクリックしてセットしていきます。 またスピンボタンのプロパティでスタイルの自動関連付けと数値の自動表示を設定しておきます。 こうしてデフォルトでは0から100までしか変更できない、しかも増減が逆な感のあるヘンテコなものが できあがります。
最小と最大の値を変更するには、クラスウィザードのメンバ変数で、 スピンボタンオブジェクトにスピンボタンコントロール変数を割り付けます。 その後、初期化処理の中でこのコントロール変数を例えば CSpinButtonCtrl m_SpinButton; // クラスウィザードが自動で*.hに追加 … m_SpinButton.SetRange( 0, 255 ); // OnInitDialog( )などで最小・最大を設定 とすると、期待通りの動作になります。
最大と最小が入れ替わっていると、増減の向きが逆になるようです。
ボタンなど、リソースオブジェクトのコントロールは、 このようにコントロール変数を割り付け、それのメンバ関数を呼ぶことによって 行うみたいです。
目次に戻る

 
◆ エディットボックス
例えばエディットボックス内の数値が変更されたら、画面も更新したいと思って エディットボックスのEN_UPDATEハンドラで画面更新処理Invalidate(FALSE)等を 呼んだりすると、エディットボックスでのカーソルの動きが変になります。 カーソルが進んだと思っても書き直しでまた先頭に戻ってしまう。
この場合、エディットボックスの変更はリターンキーで確定という仕様に割り切り、 EN_UPDATEハンドラでは画面更新処理は行わず、リターンキーを押すと実行される OnOKハンドラでInvalidate(FALSE)を実行して画面を書き直すという風に妥協しました。(^^;

[追記]
Invalidate( )はダイアログ全体を無効にする関数(その結果再描画される)なので、 エディットボックスのカレット(カーソル)も初期化されてしまうのは当たり前です。 上を書いた当時はそんなことも分からなかったのですね。(^^;

上記の場合の解決方法としては画面を更新する関数を起こして、EN_UPDATE ハンドラから 呼べばいいだけなのに、何故そうしなかったのでしょう?全部 OnPaint( ) でやろうとしてたの でしょうか?きっと C++ がよく分からなくてメンバ関数とかの追加ができなかったのでしょう。(^^;;;

[ちょっと高度?]
例えばエディットボックスに時刻を表示するとします。表示されている時刻を変更しようと 入力操作をした時,2桁入力したら自動で":"を挿入するような便利な補完機能を簡単に 実装するにはどうすればいいでしょうか?

EN_CHANGEメッセージのハンドラでエディットボックス内の文字列の前の値をstaticでとっておいて 前の文字列の長さが1で新しい文字列の長さが2のときのみ補完を実行すれば可能です。 以下にサンプルコードを示します。 // エディットボックス更新ハンドラ void C????Dlg::OnChangeEditBox( ) { // 更新前の文字列 static CString StringOld; // 更新後の文字列を取得 CString StringNew; m_EditBox.GetWindowText( StringNew ); // 自動補完する条件に当てはまった場合 if( StringNew.GetLength( ) == 2 && StringOld.GetLength( ) == 1 ){ // 補完して更新 StringNew += ":"; m_EditBox.SetWindowText( StringNew ); // 更新後のカレットを末尾に移動 m_EditBox.SendMessage( WM_KEYDOWN, (WPARAM)VK_END, (LPARAM)0 ); } // 更新前の文字列を更新 StringOld = StringNew; }
目次に戻る

 
◆ デスクトップのDCを使ってみよう
デスクトップのデバイスコンテキストを取得することができます。 CDC dc_root; dc_root.CreateDC( "DISPLAY", NULL, NULL, NULL ); これで取得したdc_rootは他の時と同じようにCDCのメンバ関数を使うことが可能です。 テキスト出力を行うと壁紙の部分にテキストが描画されます(笑)。
なおこの場合、dc_rootのデストラクタでコンテキストの解放が自動的に行われるので ReleaseDC( )やDeleteDC( )を実行する必要はありません。
目次に戻る


 
◆ テキストカラー変更方法
テキスト描画の際に、テキストの色と、背景の色を以下のようにすると変更できます。 // pDC デバイスコンテキストへのポインタ pDC->SetBkMode( TRANSPARENT ); // 背景モードを透明に設定 pDC->SetTextColor( RGB( 255, 0, 0 ) ); // テキスト色を設定 pDC->TextOut( 0, 80, "背景色透明 テキスト色を赤に変えた" ); pDC->SetBkMode( OPAQUE ); // 背景モードを塗に設定 pDC->SetBkColor( RGB( 0, 0, 255 ) ); // 背景色を設定 pDC->SetTextColor( RGB( 255, 255, 255 ) ); // テキスト色を設定 pDC->TextOut( 0, 120, "背景色青 テキスト色を白に変えた" );
目次に戻る

 
◆ フォント選択方法
DC(デバイスコンテキスト)を取得した後、そのDCでテキストを描画するときに 以下のようにするとフォントを選択できます。 // フォントサイズをポイントで指定する場合 CFont Font; Font.CreatePointFont( 90, "MS Pゴシック" ); CFont *pFontOld = pDC->SelectObject( &Font ); pDC->TextOut( 0, 0, "フォントを変えてみた" ); pDC->SelectObject( pFontOld ); 1ポイントは1/72インチなので,1ポイントが何ピクセル(ドット)に相当するかは以下のように計算できます。 dpi値とは画面のプロパティで設定されている値で1インチ(2.54cm)あたりを何ピクセルで 画面表示するかを表したものです。通常ディスプレイは96dpiとされているので計算してみると 9ポイントのフォントは12ピクセルで表示されます。なおWindowsはテキトーなので 15インチ1024×768のディスプレイでも17インチ1024×768のディスプレイでも 96dpiが使用されているので注意が必要です。

また文字列の幅と高さをピクセル数で取得したい場合は以下のようにします。 CString String = "サンプル文字列"; CSize Size = pDC->GetTextExtent( String ); // Size.cx に幅,Size.cyに高さが格納されます。 なお斜体や太字など,より詳しくフォントの設定をしたい場合は、以下のようにします。 CFont Font; Font.CreateFont( 48, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE, SHIFTJIS_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "MS Pゴシック" ); CFont *pFontOld = pDC->SelectObject( &Font ); pDC->TextOut( 0, 0, "フォントを変えてみた" ); pDC->SelectObject( pFontOld ); 詳細はヘルプを見てください。

またエディットボックス内のフォントを変更する場合は CFont m_Font などとして メンバ変数を確保しておき,OnInitDialog( )で m_Font.CreatePointFont( 240, "MS Pゴシック" ); m_EditCtrl.SetFont( &m_Font ); とすればそのエディットボックスは常に変更したフォントが使用されます。
目次に戻る

 
◆ ダイアログベースのアプリを簡単に終了させない方法
MFCのAppWizardでダイアログベースのアプリケーションを作ると、 OKとCANCELのボタンがあるだけのスケルトンが作成されます。 大抵このボタンは必要無いのでリソースエディタで削除しちゃいますが、 にも関わらずリターンキーを押したりすると勝手に終了しちゃうのが 気に食わないですね。だからといってOKとCANCELが押されたときのハンドラを 削除すると、今度は永遠に終らなくなってしまいます。

簡単な方法。リソースエディタでOKボタンを削除する前にクラスウィザードでOKボタンが 押されたときのハンドラを実装(オブジェクトIDがIDOKでBN_CLICKEDメッセージを追加)し, 以下のように変更する。 void C????Dlg::OnOK( ) { // ダイアログが閉じないように小細工 // Dialog::OnOK( ); }
その後でボタンを削除してください。あいかわらずESCキーを押すと終了しちゃうけど…。(^^;

[追記]
こういった特殊なキーは OnKeyDown( ) より前で処理されてしまうので、全て制御したい場合は 以下のようにメッセージハンドラ PreTranslateMessage( ) を実装し、 メッセージが処理される前に制御を行う必要があります。 BOOL C????Dlg::PreTranslateMessage(MSG* pMsg) { // ESCキーを無効にする if( pMsg->message == WM_KEYDOWN ){ if( pMsg->wParam == VK_ESCAPE ) return( TRUE ); } return CDialog::PreTranslateMessage(pMsg); }
目次に戻る

 
◆ メッセージボックス
メッセージボックスは確認用のダイアログを表示したりデバグ用に文字列を 表示したりするのに使用できます。ボタンの数やアイコンの種類も変更できます。 方法はヘルプを参照してください。 // CWndクラスにはメンバ関数としてメッセージボックスを表示する関数が // あるのでその派生クラスのオブジェクトからは以下のように使えます。 this->MessageBox( "ほげほげ" ); // CWndクラス以外のオブジェクトでは次のような関数が使用できます。 AfxMessageBox( "ふがふが" ); ::MessageBox( NULL, "へろへろ", NULL, NULL ); ヘルプを見るとアプリケーションは基本的に AfxMessageBox( ) 関数を使えと書いてあります。 AfxMessageBox( ) はメッセージボックスのウインドウタイトル文字列(キャプション)が 自動的にアプリケーション名になります。ただしアプリケーションのコンストラクタ実行中は まだアプリケーション名(CWinApp.m_pszAppName)がセットされていないようで,デフォルトである 「エラー」という文字列が表示されてしまいます。これを避けるにはAPIである ::MessageBox( ) を使用するしかないようです。
目次に戻る

 
◆ 文字列
文字列を確保,操作するには CString クラスが便利である。これは文字列のポインタというより 各種編集関数を備えた文字列自身と捕らえればよい。以下のようにするだけで エディットボックス内の文字列を変更できる。 CString str; int i = 2; str.Format( "TEST: 1+1=%d", i ); this->m_edit_ctrl.SetWindowText( str ); // エディットボックスに表示 またCString型のデータをNULLで終わる文字列に変換したい場合は、 CString型のデータを(LPCTSTR)でキャストすれば可能である。その逆は直接代入可能。 // CString型のデータを文字列に変換 CString str; char *buf[256]; sprintf( buf, "%s", (LPCTSTR)str ); // 文字列からCString型に変換 CString str; str = "TEST";
目次に戻る

 
◆ リソースのコントロール
リソースエディタで作成したエディットボックスなどのオブジェクトをプログラム中で アクセスするには,クラスウィザードにより当該オブジェクトへカテゴリがコントロールの変数を追加 すれば,その変数を使用することにより可能である。
目次に戻る

 
◆ TODOを信じよう! (^^;
MFCが作ってくれたスケルトンソースですが、コードをインプリメントする時は TODOとコメントされた場所に記述するようにしましょう (AppWizardでコメント出力を許可する必要があります)。 追加したメンバ変数の初期化などはOnInit****( )の中のTODOで行わないと、 正常に動作しなくなることがしょっちゅうでした。 ただカーソルやアイコンをロードするのはTODOのない所(クラスのコンストラクタ) でないとダメみたいなんだよなぁ(謎)。 まだダイアログアプリしかやってないのでホントは違うかもしれません。(^^;

…というのはC++とVC++を分かっていなかった頃に書いた文章です(笑)。

目次に戻る




【その他】

 
◆ STARTコマンド
Windowsアプリケーションをコマンドプロンプトから実行した場合,実行終了を待たずに プロンプトが戻ってきます。バッチファイルなどで順次処理をさせたい場合は,以下のように STARTコマンドで終了を待つと良いでしょう。 START /WAIT アプリケーション名 詳細はコマンドプロンプトから START /? でヘルプを見てください。
目次に戻る

 
◆ エンディアン
longやshort型などの複数バイトデータをメモリ上に格納するとき,どのような順番でバイト を並べていくかというのがエンディアンです。データの上位バイトを下位アドレス (0番地に近い方)から並べるのがビッグエンディアン(Big Endian)で,上位アドレスから 並べるのがリトルエンディアン(Little Endian)と呼ばれています。 unsigned short us = 0xAABB; のメモリ上の配置 【ビッグエンディアン】 【リトルエンディアン】 アドレス: データ アドレス: データ +0: AA +0: BB +1: BB +1: AA unsigned long ul = 0xAABBCCDD; のメモリ上の配置 【ビッグエンディアン】 【リトルエンディアン】 アドレス: データ アドレス: データ +0: AA +0: DD +1: BB +1: CC +2: CC +2: BB +3: DD +3: AA なぜこのような種類があるかの詳細な説明は他に譲るとして,簡単に言えばプロセッサの生い立ちに 原因があるということです。モトローラ系のプロセッサはビッグエンディアン,インテル系のプロセッサは リトルエンディアンです。まあ電源周波数が関東で50Hz,関西で60Hzなのと似たような状況です。

エンディアンの異なるコンピュータ同士でバイナリデータをやりとりするとデータの値がひっくり 返って問題が発生してしまうので,どちらのエンディアンを使用するかが大抵決められています。 ネットワークのTCP/IPではビッグエンディアンを使用することが決められていて,これをネット ワークバイトオーダーと呼んだりします。

エンディアンの入れ替えは下記のようにマクロを使って行えます。 #define WORDSWAP( w ) ( LOBYTE( w ) << 8 | HIBYTE( w ) ) #define DWORDSWAP( dw ) ( LOBYTE( LOWORD( dw ) ) << 24 | HIBYTE( LOWORD( dw ) ) << 16 | LOBYTE( HIWORD( dw ) ) << 8 | HIBYTE( HIWORD( dw ) ) ) unsigned short us; unsigned long ul; us = 0xAABB; us = WORDSWAP( us ); ul = 0xAABBCCDD; ul = DWORDSWAP( ul ); また複数バイトのバイトオーダーを入れ替えるswab( )という関数があります。 さらにネットワーク関連のライブラリにhtonl( ), htons( ), ntohl( ), ntohs( )という バイトオーダーの入れ替え関数が用意されています。このライブラリ関数はソースコード中に 以下の記述をすることによって使用できるようになりますが,上記のマクロで十分でしょう。 #include <winsock.h> #pragma comment( linker, "/DEFAULTLIB:wsock32.lib" )
目次に戻る

 
◆ 単語の抽出
文字列の中の単語を分離するには sscanf( ) などを使用すると思いますが,単語間のセパレータ文字が いくつか種類がある時はなかなか難しいです。
そのような場合,トークン抽出関数 strtok( ) を利用すると便利です。 セパレータを複数(もちろん一種類でも可)指定して,トークン(単語)ずつ分離できます。 こんな便利な関数があるなんて最近まで知りませんでした。 詳細はヘルプを見てください。サンプルコードも載っています。
目次に戻る

 
◆ 文字コード
日本語を表すいわゆる全角文字のコードは、電子メールなどで使用されるJISコード、 WindowsやMacで使われるシフトJISコード、UNIXで用いられるEUCコードなどがあります。

詳しい話は他に譲るとして、ここではそれぞれのコード間でコードを変換するサンプルを掲載します。
これです。話が早いでしょ?(笑)

Windowsではフォルダやファイル名にもシフトJISコードが使用されていますが,困ったことに フォルダの区切り文字である'\'(0x5c)が文字コードの2バイト目に存在する場合があるのです。 具体的には以下の文字の2バイト目が'\'(0x5c)です。 ―ソЫ\噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭 フォルダ名の解析を行う場合などはこの点を考慮してプログラミングしなければなりません。 つまり「ソフト」とか「性能」とか「予定表」という名前のフォルダが存在しても期待通り 動作しなければなりません。文字を調査するには_ismbb 系ルーチンが有効でしょう。 詳細はヘルプを見てください。

【Unicode】
最近は全世界の言語を2バイト長の文字コードに集約しようとするUnicodeというものを よく聞くようになりました。日本語や中国語の似たような形の文字は一つの文字に割り当ててしまう というちょっと強引なコード体系でしたが,結局2バイトでは足りなくなり可変長のマルチバイト 文字コードになってきています。

一口にUnicodeといっても実はいろんなフォーマットがあります。 UTF-7, UTF-8, UTF-16BE, UTF-16LEというように全然集約されていません。 まったく勘弁して欲しいです…。
(UTF: Universal multi-octet character set Transformation Format)
【マルチバイト文字】
文字を可変長バイトで表現する文字コードのこと。 ASCIIは1バイト,日本語は2バイトで表現するSJISや,Unicodeなどはマルチバイト文字である。

【ワイド文字】
主に2バイト固定長で表現される文字コードのこと。特に決まった文字コードがあるわけではないが WindowsではUTF-16BEのようだ。

マルチバイト文字列(SJIS)とワイド文字列(UNICODE)の変換は以下のようにすることが可能です(VC++.NET2003で確認)。 ただし,最新のコンパイラでないと対応してないかもしれません。筆者愛用のVC++4.0はもちろん非対応でしたー! char MultiByteString[16]; wchar_t WideString[8]; // ロケール設定 setlocale( LC_ALL, "Japanese" ); // マルチバイト文字列(SJIS)からワイド文字列(UNICODE)への変換 mbstowcs( WideString, "テスト", 16 ); // ワイド文字列(UNICODE)からマルチバイト文字列(SJIS)への変換 wcstombs( MultiByteString, WideString, 16 );
目次に戻る

































先頭に戻る

©Yutaka Wada(ana53), AirparkLab ALL RIGHTS RESERVED.