使用带有多选标志的 GetOpenFileName() 时如何获取选定文件的列表?
Posted
技术标签:
【中文标题】使用带有多选标志的 GetOpenFileName() 时如何获取选定文件的列表?【英文标题】:How to get list of selected files when using GetOpenFileName() with multiselect flag? 【发布时间】:2014-10-11 17:56:24 【问题描述】:我尝试过谷歌搜索,但人们似乎遇到了同样的问题:我们无法获得所选文件的列表。
这是一段与我使用的类似的简单工作代码:
OPENFILENAME ofn = sizeof ofn ;
wchar_t file[1024];
file[0] = '\0';
ofn.lpstrFile = file;
ofn.nMaxFile = 1024;
ofn.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;
GetOpenFileName(&ofn);
我如何实际获得我选择的文件名?目前我只能让它在没有 OFN_ALLOWMULTISELECT 标志的情况下工作,因此它将一个选定的文件名返回到ofn.lpstrFile
。我试图打印出该结构内的所有字符串变量,但一无所获。它只显示所选文件的主文件夹。
【问题讨论】:
它在结构OPENFILENAME
的文档中。多个选择由连续缓冲区中的空字符分隔,last 选择由 两个 空字符终止。收集它们的常用方法是在while (*s) ... s = s+lstrlen(s)+1;
中遍历字符串,其中s
最初是所述缓冲区的起始地址。
使用起来更方便IFileDialog
【参考方案1】:
看起来ofn.lpstrFile
包含所有文件名,以 NULL 分隔并以另一个 NULL 结尾(实际上以空字符串结尾)。
如果设置了 OFN_ALLOWMULTISELECT 标志并且用户选择了多个文件,则缓冲区包含当前目录,后跟所选文件的文件名。对于资源管理器样式的对话框,目录和文件名字符串以 NULL 分隔,最后一个文件名后有一个额外的 NULL 字符。 对于旧样式对话框,字符串以空格分隔,函数使用短文件带空格的文件名的名称。您可以使用 FindFirstFile 函数在长文件名和短文件名之间进行转换。 如果用户只选择一个文件,lpstrFile 字符串在路径和文件名之间没有分隔符。
来自MSDN。
解析内容的可能实现可能是;
wchar_t* str = ofn.lpstrFile;
std::wstring directory = str;
str += ( directory.length() + 1 );
while ( *str )
std::wstring filename = str;
str += ( filename.length() + 1 );
// use the filename, e.g. add it to a vector
【讨论】:
请记住缓冲区中的第一项是目录:wchar_t* str = ofn.lpstrFile; std::wstring dir = str; str += (dir.length()+1); while (*str) ...
谢谢@Remy,我忘记了,只关注 NULL 解析
实际上,更复杂的是,如果只选择一个文件,缓冲区中唯一的项目是路径+文件名,所以你也必须考虑到这一点。如果存在一项,请按原样使用。如果存在多个项目,则第一个是目录,其余是文件名。该结构不会告诉您哪种情况是哪种情况,您必须解析缓冲区才能发现它。
@Remy,这确实使事情复杂化了。我想这里可以使用 nFileOffset 进行检查,我不确定 OP 如何将其集成到他们自己的代码中。
nFileOffset
在单文件和多文件场景中都有意义。它指向第一个文件名,因此确实使事情变得容易一些。在这两种情况下,nFileOffset
之前的所有内容都是一个目录,它可能会或可能不会以 null 结尾,而从 nFileOffset
开始的所有内容都是 1 个或多个文件名。【参考方案2】:
检查 nFileExtension 可能不可靠,因为如果用户没有输入文件扩展名(但只是点,如“file.”),它也可能为 0。 我认为要区分单文件选择和多文件选择,必须检查 nFileOffset - 1 位置是否有空字符(终止符)。
【讨论】:
【参考方案3】:试试这个:
wchar_t file[1025] = 0; // room for an extra null terminator, just in case
...
ofn.nMaxFile = 1024;
...
wchar_t* ptr = ofn.lpstrFile;
ptr[ofn.nFileOffset-1] = 0;
std::wcout << L"Directory: " << ptr << std::endl;
ptr += ofn.nFileOffset;
while (*ptr)
std::wcout << L"File: " << ptr << std::endl;
ptr += (lstrlenW(ptr)+1);
【讨论】:
当然ofn.nMaxFile
应该是 1025,当然这并不重要,但文档说这是缓冲区的大小,以字符为单位。
我故意使缓冲区大于nMaxFile
,因此无论GetOpenFileName()
如何填充缓冲区,总会有一个空终止符。这是寻找空终止符的循环的安全捕获。我不在乎GetOpenFileName()
是否输出自己的空终止符。在使用大小缓冲区时,我宁愿谨慎行事。缓冲区中的额外插槽不会受到伤害。
如果缓冲区不够大,GetOpenFileName
将返回 FALSE
。并且足够大包括能够编写所有必要的空终止符。
我不在乎。我更喜欢在 API 提供的任何东西之上拥有自己的安全措施。如果您不喜欢它,则不必使用它。但不要批评我选择编写代码的方式。
我会批评我喜欢的任何东西,非常感谢。你可以这样编码。我只是说这是不必要的悲观。【参考方案4】:
如果您在使用 OFN_ALLOWMULTISELECT 时选择单个文件,则 nFileExtension 字段包含扩展名的偏移量。如果选择多个文件,则 nFileExtension 字段包含 0。
通过这种方式,您可以确定是否选择了单个文件,并且只需读取/复制 lpstrFile 字段指向的缓冲区(这将是一个包含完整路径和文件名的单个以空字符结尾的字符串,包括扩展名)
或者如果选择了多个文件,则解析 lpstrFile 字段指向的缓冲区,首先使用 nFileOffset 读取/复制文件夹(例如使用 lstrcpyn 并将要读取的长度指定为 nFileOffset 值)然后读取/从 nFileOffset 复制到下一个 null ,即 file1 字符串,添加文件字符串长度 +1 以获得下一个位置以读取/复制下一个文件字符串等,直到您到达以 null 开头的文件字符串 - 这是所有结尾的双 null文件(作为在此之前的最后一个字符串以空终止)
【讨论】:
【参考方案5】:这里是 Niall 和 Remy 答案的更完整版本。
vector<string> &filePaths;
if ( GetOpenFileName( &ofn ) == TRUE )
wchar_t *p = ofn.lpstrFile;
wstring path = p;
p += path.size() + 1;
if ( *p == 0 )
// there is only one string, being the full path to the file
filePaths.push_back( ConvertWideCharToUtf8( path.c_str() ) );
else
// multiple files follow the directory
for ( ; *p != 0 ; )
wstring fileName = p;
filePaths.push_back( ConvertWideCharToUtf8( ( path + L"\\" + fileName ).c_str() ) );
p += fileName.size() + 1;
我们也有这个功能:
string ConvertWideCharToUtf8( const wchar_t *wideText )
int len = WideCharToMultiByte( CP_UTF8, 0, wideText, -1, NULL, 0, NULL, NULL );
char *buffer = (char *)malloc( len );
WideCharToMultiByte( CP_UTF8, 0, wideText, -1, buffer, len, NULL, NULL );
string s = buffer;
free( buffer );
return s;
【讨论】:
以上是关于使用带有多选标志的 GetOpenFileName() 时如何获取选定文件的列表?的主要内容,如果未能解决你的问题,请参考以下文章
修改 QFileDialog::getOpenFileName 有一个额外的下拉