とりあえず関数だけ
とりあえず関数だけ紹介します。前回、前々回と同じクラスに作製していると考えてください。 |
CString CTestView::BrowseForFolder2( HWND p_hWnd, CString p_cSetStr
, CString p_cRootStr, CString p_cCaptionStr, UINT p_uiFlags )
{
LPMALLOC pMalloc;
// IMallocインターフェイスへのポインタを取得します。
if( ::SHGetMalloc( &pMalloc ) != NOERROR )
return _T( "" );
LPITEMIDLIST pIDL, pSetIDL, pRootIDL;
BROWSEINFO stBInfo;
char chReturn[MAX_PATH];
CString cRetStr;
pRootIDL = GetItemIDList( p_cRootStr );
pSetIDL = GetItemIDList( p_cSetStr );
// 構造体を初期化します。
stBInfo.pidlRoot = pRootIDL;//ルートフォルダです。
stBInfo.hwndOwner = p_hWnd; //このダイアログの親ウィンドウのハンドルです。
stBInfo.pszDisplayName = chReturn; //選択されているフォルダを返すためのポインタです。
stBInfo.lpszTitle = p_cCaptionStr; //説明の文字列です。
stBInfo.ulFlags = p_uiFlags; //フォルダだけにします。
stBInfo.lpfn = (BFFCALLBACK)BrowseCallbackProc; //プロシージャへのポインタです。
stBInfo.lParam = (LPARAM)pSetIDL; //選択するフォルダへのIDです。
// ダイアログボックスを表示します。
pIDL = ::SHBrowseForFolder( &stBInfo );
// pidlにはそのフォルダのネームスペースに関連づけられたポインタが
// 入っています。この時点ではchReturnには選択されたフォルダ名だけ
// しか入っていないので、このポインタを利用します。
if( pIDL != NULL )
{
// フルパスを取得します。
if( ::SHGetPathFromIDList( pIDL, chReturn ) )
cRetStr = chReturn;
pMalloc->Free( pIDL );
}
// リリースします。
if( pRootIDL != NULL )
pMalloc->Free( pRootIDL );
if( pSetIDL != NULL )
pMalloc->Free( pSetIDL );
pMalloc->Release();
// 文字列を返します。
return cRetStr;
}
関数の雛形は前々回のものと同じですが、途中で前回作製したGetItemIDList()という関数を使用している部分や、この関数から返ってくるアイテムIDをあとでリリースしている部分、そしてBROWSEINFO構造体へと入れているデータがいくつか違います。その辺を見ていきましょう。
あ、この関数を作製しただけじゃ、ビルドできません。ビルドはもう少し待ってくださいね。 |
アイテムIDの取得
前回作製した関数で、ファイルのパスからアイテムIDを取得しています。このアイテムIDをあとで使用します。使用したあとはIMalloc::Free()を使って削除しておきます。それにしても、ポインタの戻り値ってなんか変……。 |
ルートフォルダ
BROWSEINFO構造体のメンバpidlRootには、ルートフォルダに指定するフォルダのアイテムIDへのポインタ(長いな)を入れます。 ルートフォルダとは、ツリーコントロールの一番根本になるフォルダのことです。通常はデスクトップがルートですが、このメンバに指定したフォルダのアイテムIDを入れることで、好きなフォルダをルートにすることができます。 例えばインストーラで、アプリケーションへのショートカットを作製する場所をこのダイアログで選んでもらう時にC:\Windows\スタート メニュー(もちろんホントは半角カタカナ)をルートに指定すれば、スタートボタンから現れるツリーの中のフォルダを必ず指定してくれるはずです。 あとで紹介する方法を使えば特定のフォルダを選択できないようにすることができます。でも、この方法は簡単で、ユーザーにも解りやすい方法と言えるでしょう。 ちなみにあまり例はないのですが、例えば「タスクバーのプロパティ」ダイアログの「スタートメニューの設定」ページで「削除」ボタンを押すと現れるダイアログや、インターネットエクスプローラーで「お気に入りの追加」を選んだ時に現れるダイアログなどが、ルートフォルダを変更している例です。 |
BrowseCallbackProc
さて、BROWSEINFO構造体のlpfnメンバには、なにやら怪しい値が入っています。BrowseCallbackProcとは何なのでしょう? これ、実は関数です。といっても、これから作るんですが。 ここで指定している関数は、言ってみればダイアログプロシージャのようなものです。ダイアログから送られてくるメッセージを処理するためのプロシージャ関数を、ここで指定するわけです。必要がなければNULLで構わないのですが、これを使うのと使わないのとではえらい差があります。というわけで、ここでは使ってみましょう。
では実際に関数を作製します。「どういう関数を作ればいいのか」ということは::BrowseCallbackProc()のリファレンスを見てください(つまりAPIとしてどういう関数にしなければならないのか、決められています)。具体的には、次のようなコードを、これまでと同じファイル(例ではTestView.cpp)に書き込んでください。 |
int AFXAPI BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData )
{
// 初期化時にフォルダを選択させます。
if( uMsg == BFFM_INITIALIZED )
{
::SetWindowText( hwnd, _T( "テストのダイアログ" ) );
::SendMessage( hwnd, BFFM_SETSELECTION, FALSE, lpData );
}
else
{
char chText[MAX_PATH];
if( ::SHGetPathFromIDList( (LPITEMIDLIST)lParam, chText ) )
::SendMessage( hwnd, BFFM_SETSTATUSTEXT, TRUE, (LPARAM)chText );
}
return 0;
}
この関数は、Viewクラスのメンバとして作っているわけではないので、クラスウィザード等を使わずにそのまま書き込んでください。
さらに、この関数のプロトタイプ宣言をヘッダーファイルに書き込んでください。 |
static int AFXAPI BrowseCallbackProc( HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData );
ここまで書き込めばビルドできます。では、::BrowseCallbackProc()の中身について見ていきましょう。 |
::BrowseCallbackProc()
まず、この関数の引数を見てみましょう。
第1引数のhwndにはダイアログのウィンドウハンドルが入っています。ウィンドウハンドルが入っている……いい響きです。
第2引数について見てみましょう。この関数は、ダイアログでイベントが起きる度にウィンドウズから呼び出されます。と言ってもその「イベント」はたった2種類だけです。
第4引数のlpDataには、::SHBrowseForFolder()で渡したBROWSEINFO構造体のlParamメンバに入っていたものがそのまま入っています。今回は「ダイアログが開いたときに選択しておくフォルダのアイテムIDへのポインタ」を入れてあります。
さて、上の説明通り、この関数は「ダイアログが開くとき」と「フォルダが選択されたとき」のふたつの場合しかないので、このふたつについて処理を行えば十分ということです。上のコードはそうなっています。
BFFM_ENABLEOKメッセージを送ると、「OK」ボタンを押せるようにしたり押せないようにしたりできます。::SendMessage()のwParam(つまり第3引数)に0以外を入れると押せるように、0を入れると押せないようにします。::EnableWindow()やCWnd::EnableWindow()とフラグは同じですね。
BFFM_SETSELECTIONメッセージを送ると、特定のフォルダを選択状態にできます。この機能を使うと、フォルダが選択された状態でダイアログが開かせることができます。上の例ではこれを行っています。
BFFM_SETSTATUSTEXTメッセージを送ると、ツリーコントロールの上のスタティックボックスに文字列を書き込むことができます。書き込む文字列へのポインタをlParamに入れる必要があります。 |
まとめ
今回は、インターフェイスというよりも、::SHBrowseForFolder()と::BrowseCallbackProc()の解説に終始しました。ですが、それだけこの関数が「使える」ということです。ところが残念なことに、サンプルが非常に少なく、しかも::BrowseCallbackProc()を使っているものに至っては皆無であり、その上インターフェイスというハードルがあるという、取っつきにくさではトップクラスのAPIでもあるのです。 とはいえ、筆者だって特別な情報を得たわけではありません。サンプルをコピーして、英文をよく読んで、デバッグを丹念にして、色々な状況を試してみる。それだけのことです。 「実現したい機能があるけど、難しそう」と思うことは多々あるでしょう。でも、色々試してみればなんとかなるものです。当たって砕けろ、失敗の許されるプログラミングならではの言葉ではないでしょうか。
もっとも、適当半分に理解したAPIをフリーウェアやシェアウェアに使うのはちょっちやばめですが……。 |
(C)KAB-studio 1997 ALL RIGHTS RESERVED. |