ファイルダイアログでフォルダも選択できるようにする方法

ここに記載した方法は、Visual Studio 6.0 で開発した時代のものです。
作成した実行ファイルは Windows 10 でもまだ動作しますが、フォルダの選択はできなくなっています。
マイクロソフトが無償で提供している Visual Studio 2019 には、ファイルダイアログと同等の IFileDialogが用意されていて、フォルダも選択できるようになっています。
しかし、残念ながらファイルとフォルダを一緒に選択することはできないようです。


 Visual C++ (VC++) のファイルダイアログ(CFileDialog) は、 文字通りファイルを選択するために用意されたダイアログで、 そのままではフォルダを選択することはできません。 一方、フォルダ選択ダイアログ(SHBrowseForFolder) は、 BIF_BROWSEINCLUDEFILES を指定することで、 ファイルとフォルダの両方を選択できるようになっています。
 しかし、ファイルダイアログを見慣れていると、 長々としたツリーがあるだけのフォルダ選択ダイアログは貧弱で、 採用するのをためらってしまいます。 私が作ったフリーソフト PairSender (ペアセンダー) では、 ファイルを選択するのがメインで、ついでにフォルダも選択したいだけなので、 ファイルダイアログを捨ててフォルダ選択ダイアログに変えることには、 かなり抵抗があったのです。

 ファイルダイアログでフォルダも選択できる方法をネット検索してみましたが、 こうすればよいという明快な方法は見付かりませんでした。 しかし色々とヒントが得られ、 ファイルダイアログでフォルダも選択できるようにすることについに成功しました。 結果はかなりシンプルですが、方法を思い付くのに結構苦心しました。 これは PairSender (ペアセンダー) で実際に使っており、 ファイル名が空欄になっている状態で「開く」ボタンをクリックすると、 フォルダが選択できます。
 折角なのでソースを公開します。ご自由にご利用ください。 ただし、このソースを使って何らかの不都合が発生しても、一切保証はできません。

 まずは、CFileDialog を継承した CFileFolderDialog クラスを作ります。
 メンバー変数 m_oldFolder を追加します。

private:
    CString m_oldFolder;

 コンストラクタで、m_oldFolder を初期化します。

CFileFolderDialog::CFileFolderDialog(BOOL bOpenFileDialog, LPCTSTR lpszDefExt,
    LPCTSTR lpszFileName, DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :
    CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName, dwFlags, lpszFilter, pParentWnd)
{
    m_oldFolder = _T("");
}

 OnFolderChange をオーバーライドして、下記のようにします。

void CFileFolderDialog::OnFolderChange()
{
    CString strFolderPath = GetFolderPath();
    if (m_oldFolder != strFolderPath)
        m_oldFolder = strFolderPath;
    else {
        CPoint point;
        GetCursorPos(&point);
        CRect rect;
        GetParent()->GetDlgItem(IDOK)->GetWindowRect(&rect);
        // マウスポインタが「開く」ボタンの上にある
        if (point.x >= rect.left && point.x <= rect.right &&
            point.y >= rect.top && point.y <= rect.bottom) {
            // フォルダ名をセット
            strncpy(m_ofn.lpstrFile, (LPCTSTR)strFolderPath, m_ofn.nMaxFile);
            if (OnFileNameOK() == FALSE) {
                // ダイアログを閉じる
                CDialog *pParent = (CDialog *)GetParent();
                if (pParent != NULL)
                    pParent->EndDialog(IDOK);
            }
        }
    }
    CFileDialog::OnFolderChange();
}

 以上で完成です。あとは CFileDialog と同じ要領で使用します。

《解説です》 (質問はしないでください ^^;)
 OnFolderChange が呼び出された時点で、 GetFolderPath で得られた内容を m_oldFolder に記憶しておきます。 OnFolderChange は色々なタイミングで呼び出されますが、 「開く」ボタンをクリックしたときにも呼び出されます。 ファイル名が空欄になっている状態で「開く」ボタンをクリックしたことは、 m_oldFolder が変化しないので判別できます。
 ところが「ファイルの場所」のリストボックスを表示させて、 何も選択しないで閉じた場合も同じ状況になります。 それを区別するために、 マウスポインタが「開く」ボタンの上にあることを条件に加えています。
 厳密に言うと、リストボックスを閉じるときに、 偶然マウスポインタが「開く」ボタンの上にあった場合が区別できないのですが、 そのような重箱の隅を突付くような無意味なケチを付ける人がいても 相手にしないことが必要でしょう。 現実的には、そういうことは故意に行わない限り発生しないからです。


BALCONY   WIN道具箱へ戻る