C++ 使用带有multiselect标志的GetOpenFileName()时,如何获取选定文件的列表?

C++ 使用带有multiselect标志的GetOpenFileName()时,如何获取选定文件的列表?,c++,windows,winapi,visual-c++,C++,Windows,Winapi,Visual C++,我尝试过谷歌搜索,但人们似乎也有同样的问题:我们无法获得所选文件的列表 这是一段简单的工作代码,与我使用的代码类似: 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);

我尝试过谷歌搜索,但人们似乎也有同样的问题:我们无法获得所选文件的列表

这是一段简单的工作代码,与我使用的代码类似:

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中。我试图打印出该结构中的所有字符串变量,但什么也找不到。它只显示所选文件的主文件夹。

看起来ofn.lpstrFile包含所有文件名,以NULL分隔,以另一个NULL结尾,实际上以空字符串结尾

如果设置了OFN_ALLOWMULTISELECT标志并且用户选择了多个文件,则缓冲区包含当前目录,后跟所选文件的文件名。对于资源管理器样式的对话框,目录和文件名字符串是空分隔的,最后一个文件名后面有一个额外的空字符。对于旧式对话框,字符串以空格分隔,函数使用短文件名作为带空格的文件名。可以使用FindFirstFile函数在长文件名和短文件名之间进行转换。如果用户仅选择一个文件,则lpstrFile字符串在路径和文件名之间没有分隔符

解析内容的可能实现可以是:

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
}
看起来ofn.lpstrFile包含所有文件名,以NULL分隔,以另一个NULL结尾,实际上以空字符串结尾

如果设置了OFN_ALLOWMULTISELECT标志并且用户选择了多个文件,则缓冲区包含当前目录,后跟所选文件的文件名。对于资源管理器样式的对话框,目录和文件名字符串是空分隔的,最后一个文件名后面有一个额外的空字符。对于旧式对话框,字符串以空格分隔,函数使用短文件名作为带空格的文件名。可以使用FindFirstFile函数在长文件名和短文件名之间进行转换。如果用户仅选择一个文件,则lpstrFile字符串在路径和文件名之间没有分隔符

解析内容的可能实现可以是:

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 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);
}
试试这个:

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_ALLOWMULTISELECT时选择了单个文件,则nFileExtension字段包含扩展名的偏移量。如果选择多个文件,nFileExtension字段将包含0

通过这种方式,您可以确定是否选择了单个文件,只需读取/复制lpstrFile字段指向的缓冲区,该缓冲区将是一个以null结尾的字符串,包含完整路径和文件名(包括扩展名)


或者,如果选择了多个文件,则解析lpstrFile字段指向的缓冲区,首先使用nFileOffset读取/复制文件夹,例如使用lstrcpyn,并指定要读取的长度作为nFileOffset值,然后从nFileOffset读取/复制到下一个null,即file1字符串,添加文件字符串长度+1以获得读取/复制下一个文件字符串的下一个位置,等等,直到到达以null开头的文件字符串-这是所有文件结尾的双null,作为以null结尾之前的最后一个字符串。

如果使用OFN ALLOWMULTISELECT时选择单个文件,nFileExtension字段包含扩展的偏移量。如果选择多个文件,nFileExtension字段将包含0

通过这种方式,您可以确定是否选择了单个文件,只需读取/复制lpstrFile字段指向的缓冲区,该缓冲区将是一个以null结尾的字符串,包含完整路径和文件名(包括扩展名)


或者,如果选择了多个文件,则解析lpstrFile字段指向的缓冲区,首先使用nFileOffset读取/复制文件夹,例如使用lstrcpyn,并指定要读取的长度作为nFileOffset值,然后从nFileOffset读取/复制到下一个null,即file1字符串,添加文件字符串长度+1以获得下一个位置,以便读取/复制下一个文件字符串等,直到到达以null开头的文件字符串-这是所有文件结尾的双null,因为在该字符串以null结尾之前,最后一个字符串是以null结尾的

检查nfilextension可能不可靠,因为如果用户未输入任何文件扩展名,它也可以是0但只是点,就像文件一样。。
我认为要区分单文件和多文件选择,必须检查nFileOffset-1位置是否有空字符终止符。

检查nFileExtension可能不可靠,因为如果用户输入的不是文件扩展名,而是点,例如文件,它也可以是0。。
我认为要区分单文件选择和多文件选择,必须检查nFileOffset-1位置是否有空字符终止符。

以下是Niall和 雷米


下面是尼尔和雷米的答案的更完整版本

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;
        }
    }
}

它在结构上的文档中。在连续缓冲区中,多个选择由空字符分隔,最后一个选择由两个空字符终止。收集它们的常用方法是在while*s{…s=s+lstrlens+1;}中遍历字符串,其中s最初是所述缓冲区的起始地址。在结构上的文档中使用IFileDialogits更容易。在连续缓冲区中,多个选择由空字符分隔,最后一个选择由两个空字符终止。收集它们的常用方法是在while*s{…s=s+lstrlens+1;}中遍历字符串,其中s最初是所述缓冲区的起始地址;std::wstring dir=str;str+=方向长度+1;虽然*str{…}感谢@Remy,但我忘记了,我只关注了NULL部分,实际上,更复杂的是,如果只选择了一个文件,缓冲区中的唯一项是path+filename,因此您也必须考虑到这一点。如果存在一项,请按原样使用。如果存在多个项目,第一个是目录,其余是文件名。结构没有告诉您哪种情况是哪种情况,您必须解析缓冲区才能发现它。@Remy,这确实使它复杂化了。我想这里可以使用nFileOffset检查,我不确定OP如何将其集成到自己的代码中。nFileOffset在单文件和多文件场景中都有意义。它指向第一个文件名,所以这确实让事情变得简单了一点。在这两种情况下,nFileOffset之前的所有内容都是一个目录,可能以null结尾,也可能不以null结尾,nFileOffset之后的所有内容都是一个或多个文件名。请记住,缓冲区中的第一项是目录:wchar_t*str=ofn.lpstrFile;std::wstring dir=str;str+=方向长度+1;虽然*str{…}感谢@Remy,但我忘记了,我只关注了NULL部分,实际上,更复杂的是,如果只选择了一个文件,缓冲区中的唯一项是path+filename,因此您也必须考虑到这一点。如果存在一项,请按原样使用。如果存在多个项目,第一个是目录,其余是文件名。结构没有告诉您哪种情况是哪种情况,您必须解析缓冲区才能发现它。@Remy,这确实使它复杂化了。我想这里可以使用nFileOffset检查,我不确定OP如何将其集成到自己的代码中。nFileOffset在单文件和多文件场景中都有意义。它指向第一个文件名,所以这确实让事情变得简单了一点。在这两种情况下,nFileOffset之前的所有内容都是一个目录,可能以null结尾,也可能不以null结尾,从nFileOffset开始的所有内容都是一个或多个文件名。当然,ofn.nMaxFile应该是1025,这并不重要,但文档中说这是缓冲区的大小,我故意使缓冲区大于nMaxFile,因此无论GetOpenFileName如何填充缓冲区,始终存在空终止符。它是寻找空终止符的循环的安全陷阱。我不在乎GetOpenFileName是否输出自己的空终止符。在使用大小缓冲区时,我更倾向于谨慎行事。缓冲区中的额外插槽不会有任何影响。如果缓冲区不够大,GetOpenFileName将返回FALSE。足够大的容量包括能够编写所有必要的空终止符。我不在乎。我更喜欢在API提供的任何东西之上有我自己的安全锁销。如果你不喜欢它,你就不必使用它。但是不要批评我选择的代码编写方式。我会批评我喜欢的任何东西。非常感谢。这样编码对您来说很好。我只是说这是不必要的悲观。当然ofn.nMaxFile应该是1025,这并不重要,但文档说这是缓冲区的大小,以字符为单位。我故意使缓冲区大于nMaxFile,因此无论GetOpenFileName如何填充缓冲区,始终存在空终止符。它是寻找空终止符的循环的安全陷阱。我不在乎GetOpenFileName是否输出自己的空终止符。在使用大小缓冲区时,我更倾向于谨慎行事。缓冲区中的额外插槽不会有任何影响。如果缓冲区不够大,GetOpenFileName将返回FALSE。足够大的容量包括能够编写所有必要的空终止符。我不在乎。我更喜欢在API提供的任何东西之上有我自己的安全锁销。如果你不喜欢它,你就不必使用它。但是不要批评我选择的代码编写方式。我会批评我喜欢的任何东西。非常感谢。这样编码对您来说很好。我只是说这是不必要的悲观。