出力先の指定などフォルダー選択のダイアログを表示させたいことがあります。
フォルダー選択ダイアログを表示させるためには、SHBrowseForFolder関数を使います。
SHBrowseForFolder関数は、BROWSEINFO構造体を引数に取るので構造体に値をセットしなければいけません。
またSHBrowseForFolder関数の戻り値である、ITEMIDLIST構造体は取得したのち解放が必要なので、IMallocインターフェイスを用意したりと結構手間がかかります。
冒頭から見慣れない関数、構造体などがたくさん出てくるかもしれませんが、要点さえ押さえていれば簡単に実装できるので、ボタンを押下するとフォルダー選択ダイアログを表示させるサンプルプログラムを作成してみましょう。
Contents
サンプルプログラム
リソースエディター
ダイアログベースのプロジェクトを作成し、以下の手順で作成します。
関連リンク:無料のVisual StudioをインストールしてMFCを動かそう
- リソースエディターで以下のようなダイアログを作成します。
- [Button1]を右クリックし、[イベントハンドラーの追加]をクリックして、OnBnClickedButton1関数を作成します。
コーディング
まずは、フォルダー選択ダイアログを表示させる関数を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
BOOL SelectFolder( HWND hWnd, LPCTSTR lpDefFolder, LPTSTR lpSelectPath, UINT nFlag, LPCTSTR lpTitle) { LPMALLOC pMalloc; BOOL bRet = FALSE; if (SUCCEEDED(SHGetMalloc(&pMalloc))) { BROWSEINFO browsInfo; ITEMIDLIST *pIDlist; memset(&browsInfo, NULL, sizeof(browsInfo)); browsInfo.hwndOwner = hWnd; browsInfo.pidlRoot = NULL; browsInfo.pszDisplayName = lpSelectPath; browsInfo.lpszTitle = lpTitle; browsInfo.ulFlags = nFlag; browsInfo.lpfn = &BrowseCallbackProc; browsInfo.lParam = (LPARAM)lpDefFolder; browsInfo.iImage = (int)NULL; pIDlist = SHBrowseForFolder(&browsInfo); if (NULL == pIDlist) { strcpy_s(lpSelectPath, MAX_PATH, lpDefFolder); } else { SHGetPathFromIDList(pIDlist, lpSelectPath); bRet = TRUE; pMalloc->Free(pIDlist); } pMalloc->Release(); } return bRet; } |
フォルダー選択ダイアログを表示させる、SHBrowseForFolder関数の戻り値、ITEMIDLIST構造体は取得したのち必ず解放しなければいけません。
ITEMIDLIST構造体を解放させるためには、IMallocインターフェイスのポインタが必要となるため、関数冒頭で宣言、SHGetMalloc関数にて作成します。
IMallocインターフェースの作成が成功すれば、今回作成するフォルダー選択ダイアログの構造を規定していきます。(BROWSEINFO構造体)
hWndOwner | フォルダー選択ダイアログのオーナーウィンドウのハンドル。 |
---|---|
pidRoot | ルートフォルダを表すITEMIDLISTへのポインタ。 NULLを指定した場合には”デスクトップ”がルートフォルダに設定される。 |
pszDisplayName | 選択されたアイテムの表示名を格納するバッファへのアドレスを指定。 |
lpszTitle | フォルダー選択ダイアログのツリービュー上部に表示される文字列のアドレス。 |
ulFlags | フォルダー選択ダイアログのオプションフラグ。後述。 |
lpfn | イベント発生時にフォルダー選択ダイアログが呼び出すコールバック関数のアドレス。 必要なければNULL指定可能。 |
lParam | コールバック関数に渡されるアプリケーション定義値。 |
iImage | 選択されたフォルダと関連付けられるシステムイメージリストのインデックス。 |
ulFlags(オプションフラグ)
選択可能なフォルダーの指定や、ユーザーが指定できるエディットボックスの有無、新しいフォルダー作成ボタンなど、フォルダー選択ダイアログのオプションが指定できます。
- BIF_RETURNONLYFSDIRS
ファイルシステムディレクトリ以外が選択されている場合は[OK]ボタンがグレーアウトします。 - BIF_DONTGOBELOWDOMAIN
ダイアログのツリービューコントロールにドメインレベルのネットワークフォルダーを含めない。 - BIF_EDITBOX
アイテム名を書き込むことができるエディットボックスを作成します。 - BIF_VALIDATE
上記エディットボックスに無効な名前を入力した場合、コールバック関数にBFFM_VALIDATEFAILEDメッセージを送ります。 - BIF_NEWDIALOGSTYLE
リサイズなどが可能な新しいユーザーインターフェイスを使用します。
[新しいフォルダー]ボタンやフォルダーの削除などが追加されます。
他にもいくつか定義されていますが、頻繁に使用するのはこのあたりでしょうか。
関連リンク:BROWSEINFO structure (Windows)
フォルダー選択ダイアログの表示
BROWSEINFO構造体に値をセットしたら、SHBrowseForFolder関数を呼び出して、フォルダー選択ダイアログを表示させることができます。
呼び出しに成功すれば、SHGetPathFromIDList関数にITEMIDLIST構造体と、フルパスを格納するバッファを指定してフルパスを取得します。
関連リンク:SHBrowseForFolder function (Windows)
ITEMIDLIST構造体とIMallocインターフェイスの解放
最後に、IMallocインターフェイスのFree関数でITEMIDLIST構造体を解放、そしてIMallocインターフェイス自体を解放して完了です。
コールバック関数定義
さて、フォルダー選択ダイアログの設定を行う際、ダイアログが呼び出すコールバック関数のポインタが必要でした。
1 2 3 4 5 6 7 8 9 10 11 12 |
int CALLBACK BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { switch (uMsg) { case BFFM_INITIALIZED: SendMessage(hWnd, BFFM_SETSELECTION, (WPARAM)TRUE, lpData); break; case BFFM_SELCHANGED: break; } return 0; } |
- BFFM_INITIALIZED
ダイアログの初期化が完了したときにメッセージが送られます。 - BFFM_IUNKNOWN
IUNNOWNインターフェイスを使用時にメッセージが送られます。 - BFFM_SELCHANGED
ダイアログで選択が変更された場合にメッセージが送られます。 - BFFM_VALIDATEFAILED
ダイアログ内のエディットボックスに存在しないフォルダーなど、無効な名前を入力した場合にメッセージが送られます。
特にコールバック関数が必要でない場合はBROWSEINFO構造体にNULLを指定することもできます。
関連リンク:BFFCALLBACK function pointer (Windows)
Button1のイベントハンドラー
ボタンのイベントハンドラーとして作成した、OnBnClickedButton1関数を編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void CMFCApplication2Dlg::OnBnClickedButton1() { char dir[MAX_PATH] = { '\0' }; UpdateData(TRUE); BOOL bRes = SelectFolder( this->m_hWnd, NULL, dir, BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE, _T("フォルダーを選択してください。") ); if (bRes) AfxMessageBox(dir); } |
選択したフォルダーのフルパスを格納するバッファを用意して、作成したSelectFolder関数を呼び出すだけです。
実行し、ボタンを押下するとフォルダー選択ダイアログが表示され、選択したフォルダーのフルパスをメッセージボックスで表示できていると思います。
まとめ
うまく実行できたでしょうか。
そんなに難しい実装ではなかったと思いますが、見慣れない構造体などの定義があると少し躊躇してしまう人もいるのではないでしょうか。
設定する項目をしっかり理解して使い方に慣れていくことが肝要ですね。