SHBrowseForFolderによりフォルダー名を取得するプログラム(32/64bit)

icon 項目のみ表示/展開表示の切り替え

概要

SHBrowseForFolderを用いフォルダー名を取得するプログラムです。
フォルダー名を選択するツリービューとエディットボックス及び新しいフォルダーの作成ボタンを備えています。ダイアログボックスの大きさは変更可能です。
GetDirという関数がフォルダー名を取得する関数となっており、初期フォルダの指定が可能です。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

_tWinMainから直接呼び出す場合

_tWinMainから直接GetDir関数を読み出してフォルダー名を取得しメッセージボックスに取得されたフォルダー名を表示します。
GetDir関数はSHBrowseForFolder APIを呼び出し、フォルダーを選択するダイアログを表示します。
初期化時にBrowseCallbackProc関数が呼び出されます。BFFM_INITIALIZEDメッセージの場合フォルダー選択の初期時のフォルダーを設定します。
フォルダーの選択が変更されるとBrowseCallbackProc関数にBFFM_SELCHANGEDメッセージが届きます。この時、フォルダー名をSHGetPathFromIDList関数で取得します。エディットボックスのハンドルをFindWindowExで取得し、選択されたフォルダー名をエディットボックスに設定します。
フォルダー名の選択が終了しOKボタンがクリックされると、SHBrowseForFolderが終了します。戻り値からSHGetPathFromIDListにより選択されたフォルダーのフルパスを取得できます。その後、SHBrowseForFolderで作成されたメモリをCoTaskMemFreeで解放します。

ソースコード

getdir.cpp


//      ディレクトリ名を取得するダイアログを表示する
//      エディットボックスには選択されたフルパス名が表示される
//      Visual C++ 2008/2013 Express

#include <windows.h>
#include <tchar.h>
#include <shlobj.h>

//      ディレクトリ名を取得するダイアログを表示する

bool GetDir(HWND hWnd, TCHAR* def_dir,TCHAR* path);


// 最初に呼び出される関数

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
        TCHAR dir[MAX_PATH];            //      選択されたフォルダー名
        TCHAR def_dir[MAX_PATH];        //      初期フォルダー

        _tcscpy_s(def_dir,sizeof(def_dir)/sizeof(TCHAR),_TEXT("D:"));
        if(GetDir(0,def_dir,dir)==TRUE){
                MessageBox(0,dir,_TEXT("選択されたフォルダー名"),MB_OK);
        }
        return 0;
}

//      ディレクトリ名を取得するダイアログを表示する
//      GetDir(親ウィンドウのハンドル,初期時選択フォルダー,選択されたフォルダー名);
//      trueが返された場合有効なフォルダー名が取得できた場合

int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);


bool GetDir(HWND hWnd, TCHAR* def_dir,TCHAR* path){
        BROWSEINFO bInfo;
        LPITEMIDLIST pIDList;

        memset(&bInfo,0,sizeof(bInfo));
        bInfo.hwndOwner = hWnd; // ダイアログの親ウインドウのハンドル 
        bInfo.pidlRoot = NULL; // ルートフォルダをデスクトップフォルダとする 
        bInfo.pszDisplayName = path; //フォルダ名を受け取るバッファへのポインタ 
        bInfo.lpszTitle = TEXT("フォルダの選択"); // ツリービューの上部に表示される文字列 
        bInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE | BIF_NEWDIALOGSTYLE; // 表示されるフォルダの種類を示すフラグ 
        bInfo.lpfn = BrowseCallbackProc; // BrowseCallbackProc関数のポインタ 
        bInfo.lParam = (LPARAM)def_dir;
        pIDList = SHBrowseForFolder(&bInfo);
        if (pIDList == NULL){
                path[0] = _TEXT('\0');
                return false; //何も選択されなかった場合 
        }
        else{
                if (!SHGetPathFromIDList(pIDList, path))
                        return false;//変換に失敗 
                CoTaskMemFree(pIDList);// pIDListのメモリを開放 
                return true;
        }
}
//      上記ダイアログのコールバック関数

int __stdcall BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData){
    TCHAR dir[MAX_PATH];
    ITEMIDLIST *lpid;
        HWND hEdit;

        switch (uMsg){
        case BFFM_INITIALIZED:  //      ダイアログボックス初期化時
                SendMessage(hWnd, BFFM_SETSELECTION, (WPARAM)TRUE, lpData);     //      コモンダイアログの初期ディレクトリ
                break;
        case BFFM_VALIDATEFAILED:       //      無効なフォルダー名が入力された
                MessageBox(hWnd,(TCHAR*)lParam,_TEXT("無効なフォルダー名が入力されました"),MB_OK);
                hEdit=FindWindowEx(hWnd,NULL,_TEXT("EDIT"),NULL);     //      エディットボックスのハンドルを取得する
                SetWindowText(hEdit,_TEXT(""));
                return 1;       //      ダイアログボックスを閉じない
                break;
        case BFFM_IUNKNOWN:
                break;
        case BFFM_SELCHANGED:   //      選択フォルダーが変化した場合
                lpid=(ITEMIDLIST *)lParam;
                SHGetPathFromIDList(lpid,dir);
                hEdit=FindWindowEx(hWnd,NULL,_TEXT("EDIT"),NULL);     //      エディットボックスのハンドルを取得する
                SetWindowText(hEdit,dir);
                break;
        }
        return 0;
}

実行ファイルとソースファイルのダウンロード getdri.zip(22.5kByte)

ウィンドウプロシージャーから読み出す場合

プログラムを起動しファイルメニューより読み込みを選択するとフォルダーの選択ができます。
作成したGetDir関数については_tWinMainから直接呼び出す場合を参照してください。
ウィンドウプロシージャーから呼び出す場合、WM_CREATEメッセージで呼び出すと正常に動作し、WM_COMMANDメッセージで呼び出すとダイアログボックスが表示されず一見暴走したようにみえる現象が発生しました。この状態でAltキーまたはF10を押すと正常に動作します。
いろいろ試してみるとウィンドウプロシージャーのWM_PAINTメッセージを処理後、DefWindowProcを呼び出さないとダイアログボックスへのメッセージが止まるようです。ダイアログボックスからSHBrowseForFolder呼び出した場合は気にならなかったような気がします。
以下に全ソースコードのうちウィンドウプロシージャーの部分の説明に必要な部分のみ抜き出して記載しております。
WM_PAINTメッセージでディフォルト処理以外何もしていないのでcase WM_PAINT:処理を削除してdefault:のみ残せばよいが、あえてWM_PAINTの処理を記載している。

不都合が生じるソースコード


LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
        switch (msg) {
        case WM_PAINT:  // ウィンドウの描画が必要な場合に呼び出される。
                return 0L;
        case WM_COMMAND:
                switch(LOWORD(wp)) {
                        case IDM_READ:  //      ファイルメニュー・読み込み
                                if(GetDir(hWnd,_TEXT("C:"),dir)==true){
                                        MessageBox(hWnd,dir,_TEXT("選択されたフォルダ"),MB_OK);
                                }
                                break;
                }
                break;
        default:
                return (DefWindowProc(hWnd, msg, wp, lp));
        }
        return 0L;
}

不都合が発生する実行ファイルとソースファイルのダウンロード getdir2err.zip(24.3kByte)

正常に動作するソースコード


LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
        switch (msg) {
        case WM_PAINT:  // ウィンドウの描画が必要な場合に呼び出される。
                return (DefWindowProc(hWnd, msg, wp, lp));
        case WM_COMMAND:
                switch(LOWORD(wp)) {
                        case IDM_READ:  //      ファイルメニュー・読み込み
                                if(GetDir(hWnd,_TEXT("C:"),dir)==true){
                                        MessageBox(hWnd,dir,_TEXT("選択されたフォルダ"),MB_OK);
                                }
                                break;
                }
                break;
        default:
                return (DefWindowProc(hWnd, msg, wp, lp));
        }
        return 0L;
}

正常に動作するプログラムの全ソースコード

getdir2.cpp


//      ディレクトリ名を取得するコモンダイアログを表示する
//      エディットボックスには選択されたフルパス名が表示される
//      Visual C++ 2008/2013 Express

#include <windows.h>
#include <tchar.h>
#include <shlobj.h>
#include "resource.h"


//      ディレクトリ名を取得するコモンダイアログを表示する

bool GetDir(HWND hWnd, TCHAR* def_dir,TCHAR* path);

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

BOOL InitApp(HINSTANCE, WNDPROC, TCHAR*);
BOOL InitInstance(HINSTANCE, TCHAR*, int);


TCHAR dir[MAX_PATH];            //      選択されたフォルダー名

HINSTANCE hInst;

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
        MSG msg;
        BOOL b;

        TCHAR szClassName[] = TEXT("getdir2");

        hInst=hCurInst;
        if (!hPrevInst) {
                if (!InitApp(hCurInst,WndProc, szClassName))    //    ウィンドウクラスの登録
                        return FALSE;
        }
        if (!InitInstance(hCurInst, szClassName, nCmdShow)) {    // ウィンドウの作成
                return FALSE;
        }

// 見慣れないメッセージループだがエラーの場合-1が返る場合があるのでこのように記述
// MSDNを見てください。

        while ((b=GetMessage(&msg, NULL, NULL, NULL))!=0 && b!=-1) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
        return (int)msg.wParam;
}

// ウィンドウを作成/閉じる/移動等のメッセージにより起動される関数

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
        switch (msg) {
        case WM_PAINT:  // ウィンドウの描画が必要な場合に呼び出される。
                return (DefWindowProc(hWnd, msg, wp, lp));
        case WM_COMMAND:
                switch(LOWORD(wp)) {
                        case IDM_READ:  //      ファイルメニューの読み込み
                                if(GetDir(hWnd,_TEXT("C:"),dir)==true){
                                        MessageBox(hWnd,dir,_TEXT("選択されたフォルダ"),MB_OK);
                                }
                                break;
                        case IDM_END:   //      ファイルメニューの終了
                                SendMessage(hWnd,WM_DESTROY,0,0);
                                break;
                }
                break;
        case WM_DESTROY:        // ウィンドウを閉じる場合に呼び出される。
                PostQuitMessage(0);
                break;
        default:
                return (DefWindowProc(hWnd, msg, wp, lp));
        }
        return 0L;
}



//      ディレクトリ名を取得するコモンダイアログを表示する
//      GetDir(親ウィンドウのハンドル,初期時選択フォルダー,選択されたフォルダー名);
//      trueが返された場合有効なフォルダー名が取得できた場合

int __stdcall BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData);


bool GetDir(HWND hWnd, TCHAR* def_dir,TCHAR* path){
        BROWSEINFO bInfo;
        LPITEMIDLIST pIDList;

        memset(&bInfo,0,sizeof(bInfo));
        bInfo.hwndOwner = hWnd; // ダイアログの親ウインドウのハンドル 
        bInfo.pidlRoot = NULL; // ルートフォルダをデスクトップフォルダとする 
        bInfo.pszDisplayName = path; //フォルダ名を受け取るバッファへのポインタ 
        bInfo.lpszTitle = TEXT("フォルダの選択"); // ツリービューの上部に表示される文字列 
        bInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_VALIDATE | BIF_NEWDIALOGSTYLE; // 表示されるフォルダの種類を示すフラグ 
        bInfo.lpfn = BrowseCallbackProc; // BrowseCallbackProc関数のポインタ 
        bInfo.lParam = (LPARAM)def_dir;
        pIDList = SHBrowseForFolder(&bInfo);
        if (pIDList == NULL){
                path[0] = _TEXT('\0');
                return false; //何も選択されなかった場合 
        }
        else{
                if (!SHGetPathFromIDList(pIDList, path))
                        return false;//変換に失敗 
                CoTaskMemFree(pIDList);// pIDListのメモリを開放 
                return true;
        }
}

//      上記ダイアログのコールバック関数

int __stdcall BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData){
    TCHAR dir[MAX_PATH];
    ITEMIDLIST *lpid;
        HWND hEdit;

        switch (uMsg){
        case BFFM_INITIALIZED:  //      ダイアログボックス初期化時
                if(lpData)
                        SendMessage(hWnd, BFFM_SETSELECTION, (WPARAM)TRUE, lpData);     //      コモンダイアログの初期ディレクトリ
                break;
        case BFFM_VALIDATEFAILED:       //      無効なフォルダー名が入力された
                MessageBox(hWnd,(TCHAR*)lParam,_TEXT("無効なフォルダー名が入力されました"),MB_OK);
                hEdit=FindWindowEx(hWnd,NULL,_TEXT("EDIT"),NULL);     //      エディットボックスのハンドルを取得する
                SetWindowText(hEdit,_TEXT(""));
                return 1;       //      ダイアログボックスを閉じない
                break;
        case BFFM_IUNKNOWN:
                break;
        case BFFM_SELCHANGED:   //      選択フォルダーが変化した場合
                lpid=(ITEMIDLIST *)lParam;
                SHGetPathFromIDList(lpid,dir);
                hEdit=FindWindowEx(hWnd,NULL,_TEXT("EDIT"),NULL);     //      エディットボックスのハンドルを取得する
                SetWindowText(hEdit,dir);
                break;
        }
        return 0;
}

//      ウィンドウクラスの登録 1回しか呼ばれないのに関数化しているのは、ウィンドウを2回呼び出す場合に
//      この関数を再利用できるからです。

BOOL InitApp(HINSTANCE hInst,WNDPROC WndProc,TCHAR* szClassName){
        WNDCLASS wc;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInst;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName = _TEXT("IDM_MENU");
        wc.lpszClassName = (TCHAR*)szClassName;
        return (RegisterClass(&wc));
}


//      ウィンドウの作成 1回しか呼ばれないのに関数化しているのは、ウィンドウを2回呼び出す場合に
//      この関数を再利用できるからです。

BOOL InitInstance(HINSTANCE hInst, TCHAR* szClassName, int nCmdShow){
        HWND hWnd;
        hWnd = CreateWindow(szClassName,
                        TEXT("getdir2"),
                        WS_OVERLAPPEDWINDOW ,//| WS_VSCROLL,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        CW_USEDEFAULT,
                        NULL,
                        NULL,
                        hInst,
                        NULL);
        if (!hWnd)
                return FALSE;
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);
        return TRUE;
}

resource.h


#define IDM_READ        2000
#define IDM_END         2010

resource.rc


#include <windows.h>
#include "resource.h"

IDM_MENU MENU DISCARDABLE
{
        POPUP "ファイル(&F)"
        {
                MENUITEM "読み込み(&R)",IDM_READ
                MENUITEM "終了(&X)", IDM_END
        }
}

実行ファイルとソースファイルのダウンロード getdir2.zip(24.3kByte)