none
SHBrowseForFolder() で「ドキュメント」フォルダーまたはそのサブフォルダーを選択してもらうためのダイアログを表示したがサブフォルダーを選べない RRS feed

  • 質問

  • Windows 10 で「ドキュメント」フォルダーまたはそのサブフォルダーを (作って) 選択してもらうために SHBrowseForFolder() に CSIDL_PERSONAL を pidlRoot に BIF_NEWDIALOGSTYLE を指定していますが、ツリーのルートに表示された「ドキュメント」フォルダーを弄ってもサブフォルダーが現れないし、「新しいフォルダーの作成 (N)」をクリックしても作成された「新しいフォルダー (2)」がダイアログに現れません。

    • Windows 7 ではできます。Windows 10 と 2016 ではできませんでした。
    • CSIDL_PERSONAL ではなく CSIDL_COMMON_DOCUMENTS にするとフォルダーを選べて新しいフォルダーも作れます。
    • BIF_NEWDIALOGSTYLE を指定しなければサブフォルダーを選べますが、新しいフォルダーは作れません。

    どうしたらサブフォルダーが見れるでしょうか?

    または、代わりのダイアログがあったら教えてください。

    #include <windows.h>
    #include <shlobj.h>
    #include <iostream>
    
    inline void HRESULTCheck(const HRESULT hr) { if (FAILED(hr)) throw hr; }
    
    struct OleUninitialize
    {
    	OleUninitialize(const OleUninitialize &);
    	inline OleUninitialize(void) { HRESULTCheck(::OleInitialize(nullptr)); }
    	inline ~OleUninitialize(void) noexcept { ::OleUninitialize(); }
    };
    
    struct CoTaskMemFree
    {
    	const LPCITEMIDLIST ITEMIDLIST;
    	CoTaskMemFree(const CoTaskMemFree &);
    	inline CoTaskMemFree(const LPCITEMIDLIST i) noexcept : ITEMIDLIST(i) {}
    	inline ~CoTaskMemFree(void) noexcept { if (ITEMIDLIST) { ::CoTaskMemFree((void*)ITEMIDLIST); }  }
    };
    
    int main()
    {
    	const struct OleUninitialize o;
    	BROWSEINFO bi;
    	HRESULTCheck(::SHGetSpecialFolderLocation(0, CSIDL_COMMON_DOCUMENTS, (LPITEMIDLIST*) & (bi.pidlRoot)));
    	const struct CoTaskMemFree a(bi.pidlRoot);
    	bi.hwndOwner = 0;
    	bi.ulFlags = BIF_NEWDIALOGSTYLE;
    	bi.lpszTitle = L"BIF_NEWDIALOGSTYLE";
    	bi.pszDisplayName = nullptr;
    	bi.lpfn = nullptr;
    	bi.lParam = 0;
    	const struct CoTaskMemFree i = SHBrowseForFolder(&bi);
    	if (i.ITEMIDLIST) {
    		wchar_t p[MAX_PATH];
    		if (!::SHGetPathFromIDList(i.ITEMIDLIST, p)) throw "SHGetPathFromIDList";
    		std::wcout << p;
    	}
    	return 0;
    }



    • 編集済み tejima 2019年9月25日 6:21 タイトルが変だった
    2019年9月25日 5:31

回答

  • CSIDLにはCSIDL_FLAG_NO_ALIASが用意されています。CSIDL_PERSONAL | CSIDL_FLAG_NO_ALIASと指定した場合は仮想フォルダでなくディスク上のディレクトリが得られたため、新しいフォルダーが作成できました。

    なお、CSIDLはWindows Vista以降はKnown Foldersで置き換えられているため、XP以前を対象としないのであればこちらを使用すべきです。Known Foldersの場合は、FOLDERID_DocumentsとKF_FLAG_NO_ALIASの指定でいけました。

    • 回答としてマーク tejima 2019年10月1日 0:57
    2019年9月30日 9:36

すべての返信

  • SHGetKnownFolderIDList() で FOLDERID_Documents を使ってみてはどうでしょう?

    Windows Vista 以上が対象なのであれば、SHBrowseForFolder ではなく、CFolderPickerDialog(MFCを利用している場合)もしくは、IFileOpenDialog を利用してファイルダイアログのフォルダ選択モードを利用するというやり方もあります。

    自分のブログで申し訳ないですが、ずいぶん前に C++/CLI で書いた例があるのでサンプル兼用でリンクを貼っておきます。

    ここの ShowDialog() メソッドで閉じた形で利用しているのでそれほど複雑なコードではないと思います。

    http://blogs.wankuma.com/tocchann/archive/2013/04/22/327859.aspx



    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    • 回答としてマーク tejima 2019年9月26日 5:05
    • 回答としてマークされていない tejima 2019年10月1日 0:57
    2019年9月25日 6:39
  • ありがとうございます。

    ::SHBrowseForFolder() のほうは ::SHGetSpecialFolderLocation の代わりに ::SHCreateItemInKnownFolder(FOLDERID_Documents) → ::SHGetIDListFromObject() で取得した「ドキュメント」でも同じでした。

    IFileOpenDialog も作ってみましたが、"「ドキュメント」フォルダーまたはそのサブフォルダー"だけでない、任意のフォルダーが選べました。

    (なので、とりあえず1つ上の CSIDL_PROFILE にしようかと思っています)

    #include <windows.h>
    #include <shlobj.h>
    #include <iostream>
    
    namespace my
    {
    	inline void HRESULTCheck(const HRESULT hr) { if (FAILED(hr)) throw hr; }
    	struct OleUninitialize
    	{
    		OleUninitialize(const OleUninitialize&);
    		inline OleUninitialize(void) { HRESULTCheck(::OleInitialize(nullptr)); }
    		inline ~OleUninitialize(void) noexcept { ::OleUninitialize(); }
    	};
    	template <typename T> struct CoTaskMemFree
    	{
    		const T& Pointer;
    		CoTaskMemFree(const CoTaskMemFree&);
    		inline CoTaskMemFree(const T& p) noexcept : Pointer(p) {}
    		inline ~CoTaskMemFree() noexcept { if (Pointer) { ::CoTaskMemFree((void*)Pointer); } }
    	};
    	template <typename T> struct Release
    	{
    		const T& Pointer;
    		inline Release(const T& p) noexcept : Pointer(p) {}
    		inline ~Release() noexcept { Pointer->Release(); }
    	};
    };
    
    int main()
    {
    	const my::OleUninitialize o;
    
    	IFileOpenDialog* d = nullptr;
    	my::HRESULTCheck(::CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, __uuidof(IFileOpenDialog), reinterpret_cast<void**>(&d)));
    	const my::Release<IFileOpenDialog*> dd(d);
    
    	IShellItem* s = nullptr;
    	my::HRESULTCheck(::SHCreateItemInKnownFolder(FOLDERID_Documents, KF_FLAG_DEFAULT, nullptr, __uuidof(IShellItem), reinterpret_cast<void**>(&s)));
    	const my::Release<IShellItem*> ss(s);
    
    	my::HRESULTCheck(d->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM));
    	my::HRESULTCheck(d->SetFolder(s)) /* my::HRESULTCheck(d->SetDefaultFolder(s)) */;
    	HRESULT hr = d->Show(0);
    	if (hr == ERROR_CANCELLED) { throw L"ERROR_CANCELLED"; }
    
    	IShellItem* r = nullptr;
    	my::HRESULTCheck(d->GetResult(&r));
    	const my::Release<IShellItem*> rr(r);
    
    	wchar_t* n = nullptr;
    	my::HRESULTCheck(r->GetDisplayName(SIGDN_FILESYSPATH, &n));
    	const my::CoTaskMemFree<wchar_t*> nn(n);
    
    	std::wcout << n;
    
    	return 0;
    }
    

    2019年9月26日 5:03
  • ::SHGetSpecialFolderPath(, p, CSIDL_PERSONAL, ) → ::SHSimpleIDListFromPath(p) で取得した「ドキュメント」で試したら、::SHBrowseForFolder() で新しいフォルダーを作れてサブフォルダーも選べました。理由は分かりませんが、この方法を解決策としました。

    #include <windows.h>
    #include <shlobj.h>
    #include <iostream>
    
    namespace my
    {
    	inline void HRESULTCheck(const HRESULT hr) { if (FAILED(hr)) throw hr; }
    	struct OleUninitialize
    	{
    		OleUninitialize(const OleUninitialize&);
    		inline OleUninitialize(void) { HRESULTCheck(::OleInitialize(nullptr)); }
    		inline ~OleUninitialize(void) noexcept { ::OleUninitialize(); }
    	};
    	template <typename T> struct CoTaskMemFree
    	{
    		const T& Pointer;
    		CoTaskMemFree(const CoTaskMemFree&);
    		inline CoTaskMemFree(const T& p) noexcept : Pointer(p) {}
    		inline ~CoTaskMemFree() noexcept { if (Pointer) { ::CoTaskMemFree((void*)Pointer); } }
    	};
    	template <typename T> struct Release
    	{
    		const T& Pointer;
    		inline Release(const T& p) noexcept : Pointer(p) {}
    		inline ~Release() noexcept { Pointer->Release(); }
    	};
    };
    
    int main()
    {
    	const my::OleUninitialize o;
    	wchar_t p[MAX_PATH];
    	if (!::SHGetSpecialFolderPath(0, p, CSIDL_PERSONAL, FALSE)) { throw "SHGetSpecialFolderPath"; }
    	BROWSEINFO bi;
    	bi.pidlRoot = ::SHSimpleIDListFromPath(p);
    	if (!bi.pidlRoot) { throw "SHSimpleIDListFromPath"; }
    	const my::CoTaskMemFree<LPCITEMIDLIST> a(bi.pidlRoot);
    	bi.hwndOwner = 0;
    	bi.ulFlags = BIF_NEWDIALOGSTYLE;
    	bi.lpszTitle = L"SHGetSpecialFolderPath, CSIDL_PERSONAL, .ulFlags = BIF_NEWDIALOGSTYLE";
    	bi.pszDisplayName = nullptr;
    	bi.lpfn = nullptr;
    	bi.lParam = 0;
    	const my::CoTaskMemFree<LPCITEMIDLIST> i = SHBrowseForFolder(&bi);
    	if (i.Pointer) {
    		wchar_t p[MAX_PATH];
    		if (!::SHGetPathFromIDList(i.Pointer, p)) throw "SHGetPathFromIDList";
    		std::wcout << p;
    	}
    	return 0;
    }
    

    2019年9月30日 0:58
  • 思い出した!Windows 10 でのみの話でしたよね?

    SHSimpleIDListFromPath で返してくる PIDL が、CSIDL_PERSONAL のものとは異なる値になっていると思います。

    CSIDL_PERSONAL が返すドキュメントは、ちょっと特殊なフォルダった気がします。

    自分のソースはたどってませんが、Documents フォルダのアクセスとかWindows10で挙動がおかしくて、あれこれ修正した記憶がよみがえった。。。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    2019年9月30日 1:39
  • CSIDLにはCSIDL_FLAG_NO_ALIASが用意されています。CSIDL_PERSONAL | CSIDL_FLAG_NO_ALIASと指定した場合は仮想フォルダでなくディスク上のディレクトリが得られたため、新しいフォルダーが作成できました。

    なお、CSIDLはWindows Vista以降はKnown Foldersで置き換えられているため、XP以前を対象としないのであればこちらを使用すべきです。Known Foldersの場合は、FOLDERID_DocumentsとKF_FLAG_NO_ALIASの指定でいけました。

    • 回答としてマーク tejima 2019年10月1日 0:57
    2019年9月30日 9:36
  • ありがとうございます。::SHGetKnownFolderIDList(FOLDERID_Documents, KF_FLAG_NO_ALIAS, ,) で取得した「ドキュメント」で ::SHBrowseForFolder() したらできました。KF_FLAG_NO_ALIAS を指定しないとサブフォルダーが表示されない「ドキュメント」になってしまうことも確認しました。CSIDL_FLAG_NO_ALIAS でもできましたが、::SHGetKnownFolderIDList() に修正しました。

    #include <windows.h>
    #include <shlobj.h>
    #include <iostream>
    namespace my
    {
    	inline void HRESULTCheck(const HRESULT hr) { if (FAILED(hr)) throw hr; }
    	struct OleUninitialize
    	{
    		OleUninitialize(const OleUninitialize&);
    		inline OleUninitialize(void) { HRESULTCheck(::OleInitialize(nullptr)); }
    		inline ~OleUninitialize(void) noexcept { ::OleUninitialize(); }
    	};
    	template <typename T> struct CoTaskMemFree
    	{
    		const T& Pointer;
    		CoTaskMemFree(const CoTaskMemFree&);
    		inline CoTaskMemFree(const T& p) noexcept : Pointer(p) {}
    		inline ~CoTaskMemFree() noexcept { if (Pointer) { ::CoTaskMemFree((void*)Pointer); } }
    	};
    	template <typename T> struct Release
    	{
    		const T& Pointer;
    		inline Release(const T& p) noexcept : Pointer(p) {}
    		inline ~Release() noexcept { Pointer->Release(); }
    	};
    };
    int main()
    {
    	const my::OleUninitialize o;
    	BROWSEINFO bi;
    	my::HRESULTCheck(::SHGetKnownFolderIDList(FOLDERID_Documents, KF_FLAG_NO_ALIAS, 0, const_cast<LPITEMIDLIST*>(&(bi.pidlRoot)))) /*
    	サブフォルダーを選べなくなる:
    		::SHGetSpecialFolderLocation(0, CSIDL_PERSONAL, const_cast<LPITEMIDLIST*>(&(bi.pidlRoot)))
    		::SHGetKnownFolderIDList(FOLDERID_Documents, 0, 0, const_cast<LPITEMIDLIST*>(&(bi.pidlRoot))) */;
    	const my::CoTaskMemFree<LPCITEMIDLIST> a(bi.pidlRoot);
    	bi.hwndOwner = 0;
    	bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
    	bi.lpszTitle = L"FOLDERID_Documents, KF_FLAG_NO_ALIAS, .ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE";
    	bi.pszDisplayName = nullptr;
    	bi.lpfn = nullptr;
    	bi.lParam = 0;
    	const my::CoTaskMemFree<LPCITEMIDLIST> i = SHBrowseForFolder(&bi);
    	if (i.Pointer) {
    		wchar_t p[MAX_PATH];
    		if (!::SHGetPathFromIDList(i.Pointer, p)) throw "SHGetPathFromIDList";
    		std::wcout << p;
    	}
    	return 0;
    }
    

    2019年10月1日 1:23