I tried to show only the drives shared with Remote Desktop in a file selection dialog. Below is what I want to show:
But the list of the drives and folders in the left pane are different according to the Windows OS version. The links to the screenshots are in a reply comment.
How can we show the same list of the drives and folders in the multiple Windows versions?
This is the implementation:
CFileDialog my_file_dialog(TRUE);
CComPtr<IFileDialogCustomize> file_dialog = my_file_dialog.GetIFileDialogCustomize();
CComPtr<IFileDialog2> dialog2;
if (FAILED(file_dialog->QueryInterface(&dialog2))) {
::AfxMessageBox(_T("Failed to query the interface of IFileDialog2."));
return;
}
CComPtr<IShellItemFilter> shell_item_filter =
new CVisibleShellItemFilter(REMOTE_APP_VISIBLE_PATHS);
if (FAILED(dialog2->SetFilter(shell_item_filter))) {
::AfxMessageBox(_T("Failed to set the shell item filter to the file dialog."));
return;
}
CComPtr<IShellItem> shell_item;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_ComputerFolder, 0, nullptr, IID_PPV_ARGS(&shell_item)))) {
::AfxMessageBox(_T("Failed to create a shell item for the computer folder."));
return;
}
if (FAILED(dialog2->SetDefaultFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the default folder to the computer folder."));
return;
}
CComPtr<IShellItem> shell_item_desktop;
if (FAILED(::SHCreateItemInKnownFolder(FOLDERID_Desktop, 0, nullptr, IID_PPV_ARGS(&shell_item_desktop)))) {
::AfxMessageBox(_T("Failed to create a shell item for the desktop."));
return;
}
if (FAILED(dialog2->SetNavigationRoot(shell_item_desktop))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
CComPtr<IShellItem> currently_selected_folder;
if (FAILED(dialog2->GetFolder(¤tly_selected_folder))) {
::AfxMessageBox(_T("Failed to set the navigation root to the computer folder."));
return;
}
if (!IsVisible(ToPathPartsList(REMOTE_APP_VISIBLE_PATHS), currently_selected_folder)) {
if (FAILED(dialog2->SetFolder(shell_item))) {
::AfxMessageBox(_T("Failed to set the folder to the computer folder."));
return;
}
}
if (my_file_dialog.DoModal() == IDOK) {
m_message.SetWindowTextW(my_file_dialog.GetPathName());
}
else {
m_message.SetWindowTextW(_T(""));
}
What I did were:
- Construct an instance of CFiledialog.
- Retrieved an instance of IFileDialogCustomize by CFiledialog::GetIFileDialogCustomize().
- Retrieved an instance of IFileDialog2 from the isntance of IFileDialogCustomize.
- Set an instance of IShellItemFilter to the file dialog by IFileDialog::SetFilter().
- Set the default folder to the PC (My Computer) folder by IFileDialog::SetDefaultFolder().
- Set the navigation root to the PC (My Computer) folder by IFileDialog::SetNavigationRoot().
- Set the folder to the PC (My Computer) folder by IFileDialog::SetFolder() if the current folder is not a shared drive or its descendant folders.
- Show the file dialog.
I did 2. to retrieve an instance of IFileDialog2 from the instance of IFileDialogCustomize. This is because I want to support both "Open" and "Save" file dialog by the same routine.
The point is 4.. I will show the implementation of IShellItemFilter later.
I did 5. because a folder other than a shared drive or its descendant folders is shown if the default folder is not than a shared drive or its descendant folders.
I did 6. because I don't want to show the minimum contents in the file dialog, and I don't want to show the desktop folder.
I did 7. because a folder other than a shared drive or its descendant folders is shown if the current folder is not than a shared drive or its descendant folders.
The implementation of IShellItemFilter was:
// If the desktop_absolute_parsing is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", it shows the
// virtual folder that represents My Computer (PC).
// http://www.atmarkit.co.jp/ait/articles/1004/09/news094.html
static const std::wstring MY_COMPUTER_PATH = L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
// If the desktop_absolute_parsing starts with \\tsclient\, it shows a drive shared with
// Remote Desktop (or RemoteApp) or its child folder.
// https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/74673059-17b0-4a80-80ac-66b5dc419b56?forum=vcgeneralja
static const std::wstring REMOTE_APP_SHARED_FOLDER_PATH_PREFIX = L"\\\\tsclient\\";
static const std::vector<std::wstring> REMOTE_APP_VISIBLE_PATHS = {
// Add My Compter to the paths so that users can navigate from the My Compter in the folder
// view.
MY_COMPUTER_PATH,
REMOTE_APP_SHARED_FOLDER_PATH_PREFIX,
};
// Converts a give string to lower cases.
// str String
// return Lower case string.
std::wstring ToLower(std::wstring str) {
std::transform(str.begin(), str.end(), str.begin(),
[](auto ch) { return std::tolower(ch, std::locale()); });
return str;
}
// Split a givn path to the parts. Users need to split a path into the path parts, and pass to
// the function below for the processing speed.
// ex) "c:\\windows\\system32" -> {"c:", "\\", "windows", "system32"}
// file_path Path to be splitted.
// return Path parts.
std::vector<std::wstring> SplitPath(const std::wstring& file_path) {
using std::experimental::filesystem::v1::path;
path p(file_path);
return std::vector<std::wstring>(p.begin(), p.end());
}
// Checks if a path is equal to or an ancestor of another path.
// ancestor_path_parts Given path parts.
// descendant_path_parts Other path parts.
// returns true if ancestor_path_parts is equal to or an ancestor of descendant_path_parts.
// false, otherwise.
bool IsPathSelfOrAncestor(const std::vector<std::wstring>& ancestor_path_parts,
const std::vector<std::wstring>& descendant_path_parts) {
// Do not compare the path strings directly. Because "C:\Windows\System32" matches
// "C:\Windows\System", for example.
return std::search(descendant_path_parts.begin(), descendant_path_parts.end(),
ancestor_path_parts.begin(), ancestor_path_parts.end()) ==
descendant_path_parts.begin();
}
// Checks if two given paths are in a direct line. i.e. A path is equal to, ancestor of, or
// decendant of another path.
// path_parts1 Given path parts.
// path_parts2 Other path parts.
// return true if the given two paths are in a direct line. false, otherwise.
bool IsInDirectLine(const std::vector<std::wstring>& path_parts1, const std::vector<std::wstring>& path_parts2) {
return IsPathSelfOrAncestor(path_parts1, path_parts2) ||
IsPathSelfOrAncestor(path_parts2, path_parts1);
}
// Gets the display name from a given shell item.
// sigdnName SIGDN name.
// shell_item Shell item.
// name Display name.
// return S_OK if this method succeeds. false, otherwise.
HRESULT GetDisplayName(IShellItem* shell_item, SIGDN sigdnName, std::wstring& name) {
LPWSTR raw_name = nullptr;
HRESULT result;
if (FAILED(result = shell_item->GetDisplayName(sigdnName, &raw_name))) {
return result;
}
name = raw_name;
::CoTaskMemFree(raw_name);
raw_name = nullptr;
return S_OK;
}
// Checks if a given shell item is visible in a file/folder view.
// visible_path_parts_list List of the visble paths parts.
// shell_item Shell item to be checked.
// return true if the given shell item is visible. false, otherwise.
bool IsVisible(const std::vector<std::vector<std::wstring> >& visible_path_parts_list, IShellItem* shell_item) {
std::wstring desktop_absolute_parsing;
if (FAILED(GetDisplayName(shell_item, SIGDN_DESKTOPABSOLUTEPARSING,
desktop_absolute_parsing))) {
::AfxMessageBox(_T("Failed to get the diplay name of a shell item."));
return false;
}
auto path_parts = SplitPath(ToLower(desktop_absolute_parsing));
for (const auto& visible_path_parts : visible_path_parts_list) {
// Check if shell_item and visible_path are in the direct line. i.e. shell_item and
// visible_path are same, shell_item is an ancestor of visible_path, or shell_item is
// an descendant of visible_path.
// Let users to navigate in the case that shell_item is an ancestor of visible_path.
// Otherwise, users can not navigate to the visible_path through the folder view in
// the file selection dialog.
if (IsInDirectLine(path_parts, visible_path_parts)) {
return true;
}
}
return false;
}
// Converts the list of paths into the list of the path parts.
std::vector<std::vector<std::wstring> > ToPathPartsList(
const std::vector<std::wstring>& paths) {
std::vector<std::vector<std::wstring> > path_parts_list;
for (const auto& path : paths) {
path_parts_list.push_back(SplitPath(ToLower(path)));
}
return path_parts_list;
}
// CVisibleShellItemFilter show only the visible shell items which are listed in the given list
// of paths.
class CVisibleShellItemFilter : public IShellItemFilter {
public:
CVisibleShellItemFilter(const std::vector<std::wstring>& visible_paths) :
m_visible_path_parts_list(ToPathPartsList(visible_paths)) { }
HRESULT QueryInterface(REFIID riid, void** ppvObject) override {
if (ppvObject == nullptr) {
return E_POINTER;
}
if (riid == IID_IUnknown || riid == IID_IShellItemFilter) {
*ppvObject = static_cast<void*>(this);
AddRef();
return NO_ERROR;
}
else {
*ppvObject = nullptr;
return E_NOINTERFACE;
}
}
ULONG AddRef() override {
return ++m_reference_count;
}
ULONG Release() override {
ULONG reference_count = --m_reference_count;
if (reference_count == 0) {
delete this;
}
return reference_count;
}
HRESULT IncludeItem(IShellItem *psi) override {
return IsVisible(m_visible_path_parts_list, psi) ? S_OK : S_FALSE;
}
HRESULT GetEnumFlagsForItem(IShellItem *psi, SHCONTF *pgrfFlags) override {
*pgrfFlags = static_cast<SHCONTF>(-1);
return S_OK;
}
private:
ULONG m_reference_count = 0;
const std::vector<std::vector<std::wstring> > m_visible_path_parts_list;
};
CVisibleShellItemFilter::IncludeItem() returns S_OK if the desktop absolute parsing of a file is "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" or starts with "\tsclient\". "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" means PC (My Computer) folder, and "\tsclient\" is the file path prefix of the shared drives.
Thanks,
Updated (2017/09/07 11:54 JST) "the content" -> "the list of the drives and folders"