使用带有多选标志的 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 有一个额外的下拉

如何的制作带有多选框的treeview?

带有 JS 的 Django 多选

需要使用 jQuery 的带有复选框的(自动完成 + 多选)控件

使用带有多选复选框的 Ajax 帖子过滤器获取帖子

使用游标适配器实现带有过滤器的多选列表视图