fix: fix bug for windows dialog

This commit is contained in:
2025-08-21 11:00:04 +08:00
parent f9365481b9
commit 580b096cb3
7 changed files with 64 additions and 910 deletions

View File

@ -1,378 +0,0 @@
#include "DialogHelper.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
namespace YYCC::DialogHelper {
#pragma region FileFilters
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
// assign filter name
if (filter_name == nullptr) return false;
FilterName name(filter_name);
// assign filter patterns
FilterModes modes;
for (const yycc_char8_t* pattern : il) {
if (pattern != nullptr)
modes.emplace_back(yycc_u8string(pattern));
}
// check filter patterns
if (modes.empty()) return false;
// add into pairs and return
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
return true;
}
bool FileFilters::Generate(WinFileFilters& win_result) const {
// clear Windows oriented data
win_result.Clear();
// build new Windows oriented string vector first
for (const auto& it : m_Filters) {
// convert name to wchar
WinFileFilters::WinFilterName name;
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name))
return false;
// convert pattern and join them
const auto& filter_modes = it.second;
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
WinFileFilters::WinFilterModes modes;
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
return false;
// append new pair
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
}
// check filter size
// if it overflow the maximum value, return false
size_t count = win_result.m_WinFilters.size();
if (count > std::numeric_limits<UINT>::max())
return false;
// create new win data struct
// and assign string pointer from internal built win string vector.
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
for (size_t i = 0u; i < count; ++i) {
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
}
// everything is okey
return true;
}
#pragma endregion
#pragma region File Dialog
bool FileDialog::Generate(WinFileDialog& win_result) const {
// clear Windows oriented data
win_result.Clear();
// set owner
win_result.m_WinOwner = m_Owner;
// build file filters
if (!m_FileTypes.Generate(win_result.m_WinFileTypes))
return false;
// check default file type index
// check value overflow (comparing with >= because we need plus 1 for file type index later)
if (m_DefaultFileTypeIndex >= std::numeric_limits<UINT>::max())
return false;
// check invalid index (overflow the length or registered file types if there is file type)
if (m_FileTypes.Count() != 0u && m_DefaultFileTypeIndex >= m_FileTypes.Count())
return false;
// set index with additional plus according to Windows specification.
win_result.m_WinDefaultFileTypeIndex = static_cast<UINT>(m_DefaultFileTypeIndex + 1);
// build title and init file name
if (m_HasTitle) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title, win_result.m_WinTitle))
return false;
win_result.m_HasTitle = true;
}
if (m_HasInitFileName) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, win_result.m_WinInitFileName))
return false;
win_result.m_HasInitFileName = true;
}
// fetch init directory
if (m_HasInitDirectory) {
// convert to wpath
std::wstring w_init_directory;
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory, w_init_directory))
return false;
// fetch IShellItem*
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
IShellItem* init_directory = NULL;
HRESULT hr = SHCreateItemFromParsingName(w_init_directory.c_str(), NULL, IID_PPV_ARGS(&init_directory));
if (FAILED(hr)) return false;
// assign IShellItem*
win_result.m_WinInitDirectory.reset(init_directory);
}
// everything is okey
return true;
}
#pragma endregion
#pragma region Windows Dialog Code
enum class CommonFileDialogType {
OpenFile,
OpenMultipleFiles,
SaveFile,
OpenFolder
};
/**
* @brief Extract display name from given IShellItem*.
* @param item[in] The pointer to IShellItem for extracting.
* @param ret[out] Extracted display name container.
* @return True if success, otherwise false.
* @remarks This is an assist function of CommonFileDialog.
*/
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
// fetch display name from IShellItem*
LPWSTR _name;
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
if (FAILED(hr)) return false;
COMHelper::SmartLPWSTR display_name(_name);
// convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
return false;
// finished
return true;
}
/**
* @brief General file dialog.
* @param params[in] User specified parameter controlling the behavior of this file dialog,
* including title, file types and etc.
* @param ret[out] The path to user selected files or folders.
* For multiple selection, the count of items >= 1. For other scenario, the count of item is 1.
* @return True if success, otherwise false (input parameters is wrong or user click "Cancel" in popup window).
* @remarks This function is the real underlying function of all dialog functions.
*/
template<CommonFileDialogType EDialogType>
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
// prepare result variable
HRESULT hr;
// check whether COM environment has been initialized
if (!COMHelper::IsInitialized()) return false;
// create file dialog instance
// fetch dialog CLSID first
CLSID dialog_clsid;
switch (EDialogType) {
case CommonFileDialogType::OpenFile:
case CommonFileDialogType::OpenMultipleFiles:
case CommonFileDialogType::OpenFolder:
dialog_clsid = CLSID_FileOpenDialog;
break;
case CommonFileDialogType::SaveFile:
dialog_clsid = CLSID_FileSaveDialog;
break;
default:
return false;
}
// create raw dialog pointer
IFileDialog* _pfd = nullptr;
hr = CoCreateInstance(
dialog_clsid,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&_pfd)
);
if (FAILED(hr)) return false;
// create memory-safe dialog pointer
COMHelper::SmartIFileDialog pfd(_pfd);
// set options for dialog
// before setting, always get the options first in order.
// not to override existing options.
DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags);
if (FAILED(hr)) return false;
// modify options
switch (EDialogType) {
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
// Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR
// Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR
// Pick folder: FOS_PICKFOLDERS
case CommonFileDialogType::OpenFile:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
break;
case CommonFileDialogType::OpenMultipleFiles:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
dwFlags |= FOS_ALLOWMULTISELECT;
break;
case CommonFileDialogType::SaveFile:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
break;
case CommonFileDialogType::OpenFolder:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
dwFlags |= FOS_PICKFOLDERS;
break;
default:
return false;
}
// set folder dialog options
hr = pfd->SetOptions(dwFlags);
if (FAILED(hr)) return false;
// build Windows used file dialog parameters
WinFileDialog win_params;
if (!params.Generate(win_params))
return false;
// setup title and init file name
if (win_params.HasTitle()) {
hr = pfd->SetTitle(win_params.GetTitle());
if (FAILED(hr)) return false;
}
if (win_params.HasInitFileName()) {
hr = pfd->SetFileName(win_params.GetInitFileName());
if (FAILED(hr)) return false;
}
// setup init directory
if (win_params.HasInitDirectory()) {
hr = pfd->SetFolder(win_params.GetInitDirectory());
}
// set file types and default file index when we picking file
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) {
// set file types list
const auto& file_filters = win_params.GetFileTypes();
hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
if (FAILED(hr)) return false;
// set default file type index
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
if (FAILED(hr)) return false;
}
// show the dialog
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
if (FAILED(hr)) return false;
// obtain result when user click "OK" button.
switch (EDialogType) {
case CommonFileDialogType::OpenFile:
case CommonFileDialogType::OpenFolder:
case CommonFileDialogType::SaveFile:
{
// obtain one file entry
IShellItem* _item;
hr = pfd->GetResult(&_item);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item);
// extract display name
yycc_u8string result_name;
if (!ExtractDisplayName(result_item.get(), result_name))
return false;
// append result
ret.emplace_back(std::move(result_name));
}
break;
case CommonFileDialogType::OpenMultipleFiles:
{
// try casting file dialog to file open dialog
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
IFileOpenDialog* _pfod = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
if (FAILED(hr)) return false;
COMHelper::SmartIFileOpenDialog pfod(_pfod);
// obtain multiple file entires
IShellItemArray* _items;
hr = pfod->GetResults(&_items);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItemArray result_items(_items);
// analyze file entries
// get array count first
DWORD result_items_count = 0u;
hr = result_items->GetCount(&result_items_count);
if (FAILED(hr)) return false;
// iterate array
for (DWORD i = 0u; i < result_items_count; ++i) {
// fetch item by index
IShellItem* _item;;
hr = result_items->GetItemAt(i, &_item);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item);
// extract display name
yycc_u8string result_name;
if (!ExtractDisplayName(result_item.get(), result_name))
return false;
// append result
ret.emplace_back(std::move(result_name));
}
}
break;
default:
return false;
}
// everything is okey
return true;
}
#pragma endregion
#pragma region Wrapper Functions
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
if (isok) ret = cache.front();
return isok;
}
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
}
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
if (isok) ret = cache.front();
return isok;
}
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
if (isok) ret = cache.front();
return isok;
}
#pragma endregion
}
#endif

View File

@ -1,312 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "COMHelper.hpp"
#include <string>
#include <vector>
#include <initializer_list>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shlobj_core.h>
#include "WinImportSuffix.hpp"
/**
* @brief The namespace providing Windows universal dialog features.
* @details
* This namespace only available on Windows platform.
* See also \ref dialog_helper.
*/
namespace YYCC::DialogHelper {
/**
* @brief The class representing the file types region in file dialog.
* @details
* This class is served for Windows used.
* Programmer should \b not create this class manually.
*/
class WinFileFilters {
friend class FileFilters;
friend class WinFileDialog;
public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
/// @brief Get the count of available file filters
UINT GetFilterCount() const {
return static_cast<UINT>(m_WinFilters.size());
}
/// @brief Get pointer to Windows used file filters declarations
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
return m_WinDataStruct.get();
}
protected:
using WinFilterModes = std::wstring;
using WinFilterName = std::wstring;
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
std::vector<WinFilterPair> m_WinFilters;
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
/// @brief Clear all current file filters
void Clear() {
m_WinDataStruct.reset();
m_WinFilters.clear();
}
};
/**
* @brief The class representing the file types region in file dialog.
* @details
* This class is served for programmer using.
* But you don't need create it on your own.
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
* because this class is a part of FileDialog.
*/
class FileFilters {
public:
FileFilters() : m_Filters() {}
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
/**
* @brief Add a filter pair in file types list.
* @param[in] filter_name The friendly name of the filter.
* @param[in] il
* A C++ initialize list containing acceptable file filter pattern.
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
* The list at least should have one valid pattern.
* This function will not validate these filter patterns, so please write them carefully.
* @return True if added success, otherwise false.
* @remarks
* This function allow you register multiple filter patterns for single friendly name.
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
*/
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
/**
* @brief Get the count of added filter pairs.
* @return The count of already added filter pairs.
*/
size_t Count() const { return m_Filters.size(); }
/// @brief Clear filter pairs for following re-use.
void Clear() { m_Filters.clear(); }
/**
* @brief Generate Windows dialog system used data struct.
* @param[out] win_result The class receiving the generated filter data struct.
* @return True if generation success, otherwise false.
* @remarks
* Programmer should not call this function,
* this function is used as YYCC internal code.
*/
bool Generate(WinFileFilters& win_result) const;
protected:
using FilterModes = std::vector<yycc_u8string>;
using FilterName = yycc_u8string;
using FilterPair = std::pair<FilterName, FilterModes>;
std::vector<FilterPair> m_Filters;
};
/**
* @brief The class representing the file dialog.
* @details
* This class is served for Windows used.
* Programmer should \b not create this class manually.
*/
class WinFileDialog {
friend class FileDialog;
public:
WinFileDialog() :
m_WinOwner(NULL),
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
m_WinInitDirectory(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
/// @brief Get whether this dialog has owner.
bool HasOwner() const { return m_WinOwner != NULL; }
/// @brief Get the \c HWND of dialog owner.
HWND GetOwner() const { return m_WinOwner; }
/// @brief Get the struct holding Windows used file filters data.
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
/// @brief Get the index of default selected file filter.
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
/// @brief Get whether dialog has custom title.
bool HasTitle() const { return m_HasTitle; }
/// @brief Get custom title of dialog.
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
/// @brief Get whether dialog has custom initial file name.
bool HasInitFileName() const { return m_HasInitFileName; }
/// @brief Get custom initial file name of dialog
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
/// @brief Get whether dialog has custom initial directory.
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
/// @brief Get custom initial directory of dialog.
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
protected:
HWND m_WinOwner;
WinFileFilters m_WinFileTypes;
/**
* @brief The default selected file type in dialog
* @remarks
* This is 1-based index according to Windows specification.
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
* Because the same field located in FileDialog is 0-based index.
*/
UINT m_WinDefaultFileTypeIndex;
bool m_HasTitle, m_HasInitFileName;
std::wstring m_WinTitle, m_WinInitFileName;
COMHelper::SmartIShellItem m_WinInitDirectory;
/// @brief Clear all data and reset them to default value.
void Clear() {
m_WinOwner = nullptr;
m_WinFileTypes.Clear();
m_WinDefaultFileTypeIndex = 0u;
m_HasTitle = m_HasInitFileName = false;
m_WinTitle.clear();
m_WinInitFileName.clear();
m_WinInitDirectory.reset();
}
};
/**
* @brief The class representing the file dialog.
* @details
* This class is served for programming using to describe every aspectes of the dialog.
* For how to use this struct, see \ref dialog_helper.
*/
class FileDialog {
public:
FileDialog() :
m_Owner(NULL),
m_FileTypes(),
m_DefaultFileTypeIndex(0u),
m_Title(), m_InitFileName(), m_InitDirectory(),
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
/**
* @brief Set the owner of dialog.
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
*/
void SetOwner(HWND owner) { m_Owner = owner; }
/**
* @brief Set custom title of dialog
* @param[in] title The string pointer to custom title, or nullptr to remove it.
*/
void SetTitle(const yycc_char8_t* title) {
if (m_HasTitle = title != nullptr)
m_Title = title;
}
/**
* @brief Fetch the struct describing file filters for future configuration.
* @return The reference to the struct describing file filters.
*/
FileFilters& ConfigreFileTypes() {
return m_FileTypes;
}
/**
* @brief Set the index of default selected file filter.
* @param[in] idx
* The index to default one.
* This must be a valid index in file filters.
*/
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
/**
* @brief Set the initial file name of dialog
* @details If set, the file name will always be same one when opening dialog.
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
*/
void SetInitFileName(const yycc_char8_t* init_filename) {
if (m_HasInitFileName = init_filename != nullptr)
m_InitFileName = init_filename;
}
/**
* @brief Set the initial directory of dialog
* @details If set, the opended directory will always be the same one when opening dialog
* @param[in] init_dir
* String pointer to initial directory.
* Invalid path or nullptr will remove this feature.
*/
void SetInitDirectory(const yycc_char8_t* init_dir) {
if (m_HasInitDirectory = init_dir != nullptr)
m_InitDirectory = init_dir;
}
/// @brief Clear file dialog parameters for following re-use.
void Clear() {
m_Owner = nullptr;
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
m_Title.clear();
m_InitFileName.clear();
m_InitDirectory.clear();
m_FileTypes.Clear();
m_DefaultFileTypeIndex = 0u;
}
/**
* @brief Generate Windows dialog system used data struct.
* @param[out] win_result The class receiving the generated filter data struct.
* @return True if generation is success, otherwise false.
* @remarks
* Programmer should not call this function.
* This function is used as YYCC internal code.
*/
bool Generate(WinFileDialog& win_result) const;
protected:
HWND m_Owner;
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
yycc_u8string m_Title, m_InitFileName, m_InitDirectory;
FileFilters m_FileTypes;
/**
* @brief The default selected file type in dialog
* @remarks
* The index Windows used is 1-based index.
* But for universal experience, we order this is 0-based index.
* And do convertion when generating Windows used struct.
*/
size_t m_DefaultFileTypeIndex;
};
/**
* @brief Open the dialog which order user select single file to open.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected file.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret);
/**
* @brief Open the dialog which order user select multiple file to open.
* @param[in] params The configuration of dialog.
* @param[out] ret The list of full path of user selected files.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret);
/**
* @brief Open the dialog which order user select single file to save.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected file.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret);
/**
* @brief Open the dialog which order user select single directory to open.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected directory.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret);
}
#endif

View File

@ -70,8 +70,8 @@ namespace yycc::windows::dialog {
// Assign data one by one
for (auto i = 0; i < n; ++i) {
// Fetch item
const auto& filter = m_WinFilters[n];
auto& data_struct = m_WinDataStruct[n];
const auto& filter = m_WinFilters[i];
auto& data_struct = m_WinDataStruct[i];
// Assign pointer
data_struct.pszName = filter.first.c_str();
data_struct.pszSpec = filter.second.c_str();
@ -151,6 +151,8 @@ namespace yycc::windows::dialog {
rv.update();
// okey, return value
// please note that we do not check whether this list is empty in there.
// this was done in generic_file_dialog() function.
return rv;
}
@ -337,10 +339,6 @@ namespace yycc::windows::dialog {
else rv.m_WinFileTypes = std::move(win_file_types.value());
// check and assign default file type index
// check whether it is out of filters range
if (m_DefaultFileTypeIndex >= m_FileTypes.get_count()) {
return std::unexpected(DialogError::IndexOutOfRange);
}
// check whether it can be safely casted into UINT.
auto win_def_index_base0 = SAFECAST::try_to<UINT>(m_DefaultFileTypeIndex);
if (!win_def_index_base0.has_value()) {
@ -351,7 +349,9 @@ namespace yycc::windows::dialog {
if (!win_def_index.has_value()) {
return std::unexpected(DialogError::IndexOverflow);
}
// okey, assign it
// okey, assign it.
// please note we do not check the relation between this variable and file filters.
// this was done in generic_file_dialog() function.
rv.m_WinDefaultFileTypeIndex = win_def_index.value();
// everything is okey
@ -485,11 +485,21 @@ namespace yycc::windows::dialog {
// set file types and default file index when we picking file
if constexpr (EDialogType != GenericFileDialogType::OpenFolder) {
// set file types list
// fetch file filters
const auto& file_filters = win_params.get_file_types();
// first, check whether file filters is empty
if (file_filters.get_filter_count() == 0) {
return std::unexpected(DialogError::EmptyFilters);
}
// then validate index
if (win_params.get_default_file_type_index() > file_filters.get_filter_count()) {
return std::unexpected(DialogError::IndexOutOfRange);
}
// set file types list
hr = pfd->SetFileTypes(file_filters.get_filter_count(), file_filters.get_filter_specs());
if (FAILED(hr)) return BAD_COM_CALL;
// set default file type index
hr = pfd->SetFileTypeIndex(win_params.get_default_file_type_index());
if (FAILED(hr)) return BAD_COM_CALL;
@ -578,7 +588,7 @@ namespace yycc::windows::dialog {
if (inner.has_value()) {
const auto& vec = inner.value();
if (vec.size() != 1u) throw std::logic_error("return value doesn't contain exactly one item");
else return std::move(vec.front());
else return vec.front();
} else {
return std::nullopt;
}

View File

@ -32,6 +32,7 @@ namespace yycc::windows::dialog {
BadEncoding, ///< Error occurs when perform encoding convertion.
TooManyFilters, ///< The size of file filters list is too large for Windows.
IndexOverflow, ///< Default filter index is too large for Windows.
EmptyFilters, ///< File filters is empty when picking file.
IndexOutOfRange, ///< Default filter index is out of range of filters list.
NoSuchDir, ///< Given initial directory path is invalid.
BadCOMCall, ///< Some COM function calls failed.