CFileDialog показывает различное содержимое в соответствии с версиями Windows

Я пытался отобразить только те диски, к которым подключен удаленный рабочий стол, в диалоговом окне выбора файлов. Ниже то, что я хочу показать:

Но список дисков и папок на левой панели отличается в зависимости от версии ОС 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. Получил экземпляр IFileDialogCustomize с помощью CFiledialog::GetIFileDialogCustomize().
  3. Получил экземпляр IFileDialog2 из версии IFileDialogCustomize.
  4. Установите экземпляр IShellItemFilter для диалога файлов с помощью IFileDialog::SetFilter().
  5. Установите папку по умолчанию в папку ПК (Мой компьютер) с помощью IFileDialog::SetDefaultFolder().
  6. Установите корневой каталог навигации в папку "Мой компьютер" с помощью IFileDialog::SetNavigationRoot().
  7. Установите папку в папку ПК (Мой компьютер) с помощью IFileDialog::SetFolder(), если текущая папка не является общим диском или его дочерними папками.
  8. Показать диалоговое окно файла.

Я сделал 2., чтобы получить экземпляр IFileDialog2 из экземпляра IFileDialogCustomize. Это потому, что я хочу поддерживать диалоги "Открыть" и "Сохранить" в одной и той же процедуре.

Дело в том, что 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;
    };

CVisibleShellItemFilter:: IncludeItem () возвращает S_OK, если абсолютный разбор файла на рабочем столе - "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" или начинается с "\tsclient\". "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" означает папку ПК ("Мой компьютер"), а "\ tsclient \" - это префикс пути к файлу общих дисков.

Спасибо,

Обновлен (2017/09/07 11:54 JST)"контент" -> "список дисков и папок"

0 ответов

Другие вопросы по тегам