最近要做一个可以多选文件夹的功能,在网上查阅了多个资料,发现github有一段代码可以实现该功能,于是将其收入进行改造。另外qt自带的 getExistingDirectory 和 getOpenFileNames 不具有记忆上一次打开的文件夹位置。要实现多选文件夹和记忆上一次打开位置能用到的就只有IFileOpenDialog 接口了。
原代码如下
出处:https://gist.github.com/0xF5T9/3f3203950f480d348aa6d99850a26016
#include <iostream>
#include <windows.h>
#include <shobjidl.h>
#include <string>
#include <vector>
/**
* @brief Open a dialog to select item(s) or folder(s).
* @param paths Specifies the reference to the string vector that will receive the file or folder path(s). [IN]
* @param selectFolder Specifies whether to select folder(s) rather than file(s). (optional)
* @param multiSelect Specifies whether to allow the user to select multiple items. (optional)
* @note If no item(s) were selected, the function still returns true, and the given vector is unmodified.
* @note `<windows.h>`, `<string>`, `<vector>`, `<shobjidl.h>`
* @return Returns true if all the operations are successfully performed, false otherwise.
*/
bool OpenFileDialog(std::vector<std::wstring> &paths, bool selectFolder = false, bool multiSelect = false)
{
IFileOpenDialog *p_file_open = nullptr;
bool are_all_operation_success = false;
while (!are_all_operation_success)
{
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void **>(&p_file_open));
if (FAILED(hr))
break;
if (selectFolder || multiSelect)
{
FILEOPENDIALOGOPTIONS options = 0;
hr = p_file_open->GetOptions(&options);
if (FAILED(hr))
break;
if (selectFolder)
options |= FOS_PICKFOLDERS;
if (multiSelect)
options |= FOS_ALLOWMULTISELECT;
hr = p_file_open->SetOptions(options);
if (FAILED(hr))
break;
}
hr = p_file_open->Show(NULL);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) // No items were selected.
{
are_all_operation_success = true;
break;
}
else if (FAILED(hr))
break;
IShellItemArray *p_items;
hr = p_file_open->GetResults(&p_items);
if (FAILED(hr))
break;
DWORD total_items = 0;
hr = p_items->GetCount(&total_items);
if (FAILED(hr))
break;
for (int i = 0; i < static_cast<int>(total_items); ++i)
{
IShellItem *p_item;
p_items->GetItemAt(i, &p_item);
if (SUCCEEDED(hr))
{
PWSTR path;
hr = p_item->GetDisplayName(SIGDN_FILESYSPATH, &path);
if (SUCCEEDED(hr))
{
paths.push_back(path);
CoTaskMemFree(path);
}
p_item->Release();
}
}
p_items->Release();
are_all_operation_success = true;
}
if (p_file_open)
p_file_open->Release();
return are_all_operation_success;
}
/**
* @brief Open a dialog to save an item.
* @param path Specifies the reference to the string that will receive the target save path. [IN]
* @param defaultFileName Specifies the default save file name. (optional)
* @param pFilterInfo Specifies the pointer to the pair that contains filter information. (optional)
* @note If no path was selected, the function still returns true, and the given string is unmodified.
* @note `<windows.h>`, `<string>`, `<vector>`, `<shobjidl.h>`
* @return Returns true if all the operations are successfully performed, false otherwise.
*/
bool SaveFileDialog(std::wstring &path, std::wstring defaultFileName = L"", std::pair<COMDLG_FILTERSPEC *, int> *pFilterInfo = nullptr)
{
IFileSaveDialog *p_file_save = nullptr;
bool are_all_operation_success = false;
while (!are_all_operation_success)
{
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
IID_IFileSaveDialog, reinterpret_cast<void **>(&p_file_save));
if (FAILED(hr))
break;
if (!pFilterInfo)
{
COMDLG_FILTERSPEC save_filter[1];
save_filter[0].pszName = L"All files";
save_filter[0].pszSpec = L"*.*";
hr = p_file_save->SetFileTypes(1, save_filter);
if (FAILED(hr))
break;
hr = p_file_save->SetFileTypeIndex(1);
if (FAILED(hr))
break;
}
else
{
hr = p_file_save->SetFileTypes(pFilterInfo->second, pFilterInfo->first);
if (FAILED(hr))
break;
hr = p_file_save->SetFileTypeIndex(1);
if (FAILED(hr))
break;
}
if (!defaultFileName.empty())
{
hr = p_file_save->SetFileName(defaultFileName.c_str());
if (FAILED(hr))
break;
}
hr = p_file_save->Show(NULL);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) // No item was selected.
{
are_all_operation_success = true;
break;
}
else if (FAILED(hr))
break;
IShellItem *p_item;
hr = p_file_save->GetResult(&p_item);
if (FAILED(hr))
break;
PWSTR item_path;
hr = p_item->GetDisplayName(SIGDN_FILESYSPATH, &item_path);
if (FAILED(hr))
break;
path = item_path;
CoTaskMemFree(item_path);
p_item->Release();
are_all_operation_success = true;
}
if (p_file_save)
p_file_save->Release();
return are_all_operation_success;
}
int main()
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr))
{
std::cout << "Failed to initialize COM library.\n";
return -1;
}
// Select an example.
std::cout << "1. Select an item.\n";
std::cout << "2. Select a folder.\n";
std::cout << "3. Select multiple items.\n";
std::cout << "4. Save an item.\n";
std::cout << "5. Save an item with filters.\n";
std::cout << "Select an example: ";
int choice = 0;
std::cin >> choice;
switch (choice)
{
// Example: Select an item.
case 1:
{
std::vector<std::wstring> paths;
if (OpenFileDialog(paths))
{
if (!paths.empty())
{
std::cout << "Total items: " << paths.size() << "\n";
for (int i = 0; i < static_cast<int>(paths.size()); ++i)
std::wcout << L"Path " << std::to_wstring(i + 1) << L": " << paths[i] << L"\n";
}
else
std::cout << "No item were selected.\n";
}
break;
}
// Example: Select a folder.
case 2:
{
std::vector<std::wstring> paths;
if (OpenFileDialog(paths, true))
{
if (!paths.empty())
{
std::cout << "Total items: " << paths.size() << "\n";
for (int i = 0; i < static_cast<int>(paths.size()); ++i)
std::wcout << L"Path " << std::to_wstring(i + 1) << L": " << paths[i] << L"\n";
}
else
std::cout << "No item were selected.\n";
}
break;
}
// Example: Select multiple items.
case 3:
{
std::vector<std::wstring> paths;
if (OpenFileDialog(paths, false, true))
{
if (!paths.empty())
{
std::cout << "Total items: " << paths.size() << "\n";
for (int i = 0; i < static_cast<int>(paths.size()); ++i)
std::wcout << L"Path " << std::to_wstring(i + 1) << L": " << paths[i] << L"\n";
}
else
std::cout << "No item were selected.\n";
}
break;
}
// Example: Save an item.
case 4:
{
std::wstring path = L"";
if (SaveFileDialog(path, L"Some file.txt"))
{
if (!path.empty())
{
std::wcout << L"Selected save path: " << path << L"\n";
}
else
std::cout << "No item were selected.\n";
}
break;
}
// Example: Save an item with filters.
case 5:
{
std::wstring path = L"";
const unsigned int total_filters = 3;
COMDLG_FILTERSPEC filters[total_filters];
filters[0].pszName = L"All files. (*.*)";
filters[0].pszSpec = L"*.*";
filters[1].pszName = L"Image files. (.bmp, .jpg, .png)";
filters[1].pszSpec = L"*.bmp;*.jpg;*.png";
filters[2].pszName = L"Specific file. (unique_file.txt)";
filters[2].pszSpec = L"unique_file.txt";
std::pair<COMDLG_FILTERSPEC *, int> filter_info = std::make_pair<COMDLG_FILTERSPEC *, int>(filters, total_filters);
if (SaveFileDialog(path, L"", &filter_info))
{
if (!path.empty())
{
std::wcout << L"Selected save path: " << path << L"\n";
}
else
std::cout << "No item were selected.\n";
}
break;
}
}
CoUninitialize();
return 0;
}
std::vector<std::pair<std::wstring, std::wstring>> filters = {
{L"文件类型(*.txt)", L"*.txt"}, // 过滤 .txt 文件
};
if (auto files = GetOpenFileNames(L"导入转换的TXT文件", true, filters);files.has_value())
{
for (const auto& filename : files.value())
{
...
}
}
于是将其中的OpenFileDialog函数拿来进行改造:分别是选择文件 GetOpenFileNames 和文件夹 GetExistingDirectorys
使用时需要加入以下几个头文件:
#include <windows.h>
#include <shobjidl.h>
#include <string>
#include <vector>
#include<optional>
GetOpenFileNames 函数定义如下:
std::optional<std::vector <std::wstring>> GetOpenFileNames(const std::wstring& dialogTitle, bool multiSelect, const std::vector<std::pair<std::wstring, std::wstring>>& filters)
{
IFileOpenDialog* p_file_open = nullptr;
bool are_all_operation_success = false;
std::optional<std::vector <std::wstring>>files;
while (!are_all_operation_success)
{
// Create the file dialog instance
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&p_file_open));
if (FAILED(hr))
break;
// Set dialog title if specified
if (!dialogTitle.empty())
{
hr = p_file_open->SetTitle(dialogTitle.c_str());
if (FAILED(hr))
break;
}
// Handle folder selection and multi-select options
if ( multiSelect)
{
FILEOPENDIALOGOPTIONS options = 0;
hr = p_file_open->GetOptions(&options);
if (FAILED(hr))
break;
options |= FOS_ALLOWMULTISELECT;
hr = p_file_open->SetOptions(options);
if (FAILED(hr))
break;
}
if (!filters.empty())
{
std::vector<COMDLG_FILTERSPEC> filterSpecs;
for (const auto& filter : filters)
{
COMDLG_FILTERSPEC spec;
spec.pszName = filter.first.c_str(); // Filter name as LPCWSTR
spec.pszSpec = filter.second.c_str(); // File spec (e.g. "*.txt") as LPCWSTR
filterSpecs.push_back(spec);
}
// Now, we correctly call SetFileTypes to set the filter
hr = p_file_open->SetFileTypes(static_cast<UINT>(filterSpecs.size()), filterSpecs.data());
if (FAILED(hr))
{
// If it failed, we break the loop
break;
}
}
// Show the dialog
hr = p_file_open->Show(NULL);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) // No items were selected.
{
are_all_operation_success = true;
break;
}
else if (FAILED(hr))
break;
// Retrieve the selected items
IShellItemArray* p_items;
hr = p_file_open->GetResults(&p_items);
if (FAILED(hr))
break;
DWORD total_items = 0;
hr = p_items->GetCount(&total_items);
if (FAILED(hr))
break;
// Iterate over the selected items and add their paths to the vector
for (int i = 0; i < static_cast<int>(total_items); ++i)
{
IShellItem* p_item;
p_items->GetItemAt(i, &p_item);
if (SUCCEEDED(hr))
{
PWSTR path;
hr = p_item->GetDisplayName(SIGDN_FILESYSPATH, &path);
if (SUCCEEDED(hr))
{
if(!files.has_value())
{
files.emplace();
}
files->emplace_back(path);
CoTaskMemFree(path);
}
p_item->Release();
}
}
p_items->Release();
are_all_operation_success = true;
}
if (p_file_open)
p_file_open->Release();
CoUninitialize();
return files ;
}
GetExistingDirectorys函数定义如下:
std::optional<std::vector<std::wstring>> GetExistingDirectorys(const std::wstring& dialogTitle, bool multiSelect)
{
IFileOpenDialog* p_file_open = nullptr;
bool are_all_operation_success = false;
std::optional<std::vector<std::wstring>> paths;
while (!are_all_operation_success)
{
// Create the file dialog instance
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&p_file_open));
if (FAILED(hr))
break;
// Set dialog title if specified
if (!dialogTitle.empty())
{
hr = p_file_open->SetTitle(dialogTitle.c_str());
if (FAILED(hr))
break;
}
// Handle folder selection and multi-select options
if ( multiSelect)
{
FILEOPENDIALOGOPTIONS options = 0;
hr = p_file_open->GetOptions(&options);
if (FAILED(hr))
break;
options |= FOS_ALLOWMULTISELECT|FOS_PICKFOLDERS;
hr = p_file_open->SetOptions(options);
if (FAILED(hr))
break;
}
// Show the dialog
hr = p_file_open->Show(NULL);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) // No items were selected.
{
are_all_operation_success = true;
break;
}
else if (FAILED(hr))
break;
// Retrieve the selected items
IShellItemArray* p_items;
hr = p_file_open->GetResults(&p_items);
if (FAILED(hr))
break;
DWORD total_items = 0;
hr = p_items->GetCount(&total_items);
if (FAILED(hr))
break;
// Iterate over the selected items and add their paths to the vector
for (int i = 0; i < static_cast<int>(total_items); ++i)
{
IShellItem* p_item;
p_items->GetItemAt(i, &p_item);
if (SUCCEEDED(hr))
{
PWSTR path;
hr = p_item->GetDisplayName(SIGDN_FILESYSPATH, &path);
if (SUCCEEDED(hr))
{
if(!paths.has_value())
{
paths.emplace();
}
paths->emplace_back(path);
CoTaskMemFree(path);
}
p_item->Release();
}
}
p_items->Release();
are_all_operation_success = true;
}
if (p_file_open)
p_file_open->Release();
CoUninitialize();
return paths;
}
//使用例子:
std::wstring dialogTitle = L"选择资料的文件夹";
if (auto selectedPaths = GetExistingDirectorys(dialogTitle, true); selectedPaths.has_value())
{
for (const auto& dir : selectedPaths.value())
{
...
}
}