1

我试图在文件选择对话框中仅显示与远程桌面共享的驱动器。以下是我要展示的内容:

但左侧窗格中的驱动器和文件夹列表根据 Windows 操作系统版本而有所不同。屏幕截图的链接在回复评论中。

我们如何在多个 Windows 版本中显示相同的驱动器和文件夹列表?

这是实现:

    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(&currently_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(""));
    }

我所做的是:

  1. 构造 CFiledialog 的一个实例。
  2. 通过 CFiledialog::GetIFileDialogCustomize() 检索 IFileDialogCustomize 的实例。
  3. 从 IFileDialogCustomize 的实例中检索 IFileDialog2 的实例。
  4. 通过 IFileDialog::SetFilter() 将 IShellItemFilter 的实例设置为文件对话框。
  5. 通过 IFileDialog::SetDefaultFolder() 将默认文件夹设置为 PC(我的电脑)文件夹。
  6. 通过 IFileDialog::SetNavigationRoot() 将导航根设置为 PC(我的电脑)文件夹。
  7. 如果当前文件夹不是共享驱动器或其后代文件夹,则通过 IFileDialog::SetFolder() 将文件夹设置为 PC(我的电脑)文件夹。
  8. 显示文件对话框。

我做了 2. 从 IFileDialogCustomize 的实例中检索 IFileDialog2 的实例。这是因为我想通过同一个例程同时支持“打开”和“保存”文件对话框。

重点是 4.. 后面我会展示 IShellItemFilter 的实现。

我做了 5. 因为如果默认文件夹不是共享驱动器或其后代文件夹,则会显示共享驱动器或其后代文件夹以外的文件夹。

我做了 6. 因为我不想在文件对话框中显示最小内容,也不想显示桌面文件夹。

我做了 7. 因为如果当前文件夹不是共享驱动器或其后代文件夹,则会显示共享驱动器或其后代文件夹以外的文件夹。

IShellItemFilter 的实现是:

    // 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;
    };

如果文件的桌面绝对解析为“::{20D04FE0-3AEA-1069-A2D8-08002B30309D}”或以“\tsclient\”开头,CVisibleShellItemFilter::IncludeItem() 返回 S_OK。“::{20D04FE0-3AEA-1069-A2D8-08002B30309D}”表示PC(我的电脑)文件夹,“\tsclient\”是共享驱动器的文件路径前缀。

谢谢,

更新(2017/09/07 11:54 JST) “内容”->“驱动器和文件夹列表”

4

0 回答 0