关于C#:使用带有multiselect标志的GetOpenFileName()时,如何获取所选文件的列表?

How to get list of selected files when using GetOpenFileName() with multiselect flag?

我曾尝试使用Google搜索,但人们似乎也遇到了同样的问题:我们无法获取所选文件的列表。

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

1
2
3
4
5
6
7
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结尾(有效地以空字符串结尾)。

If the OFN_ALLOWMULTISELECT flag is set and the user selects multiple files, the buffer contains the current directory followed by the file names of the selected files. For Explorer-style dialog boxes, the directory and file name strings are NULL separated, with an extra NULL character after the last file name. For old-style dialog boxes, the strings are space separated and the function uses short file names for file names with spaces. You can use the FindFirstFile function to convert between long and short file names. If the user selects only one file, the lpstrFile string does not have a separator between the path and file name.

来自MSDN。

解析内容的可能实现可能是;

1
2
3
4
5
6
7
8
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
}


检查nFileExtension可能不可靠,因为如果用户未输入文件扩展名(仅输入点,如" file。"),该值也可能为0。
我认为要区分单文件选择和多文件选择,必须检查位置nFileOffset-1.是否存在空字符(终止符)。


这是Niall和Remy的答案的更完整版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
        }
    }
}

我们还具有以下功能:

1
2
3
4
5
6
7
8
9
10
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;
}

如果在使用OFN_ALLOWMULTISELECT时选择单个文件,则nFileExtension字段包含扩展名的偏移量。如果选择多个文件,则nFileExtension字段包含0。

这样,您可以确定是否选择了单个文件,并且只需读取/复制lpstrFile字段所指向的缓冲区(该缓冲区将是一个包含完整路径和文件名(包括扩展名)的空终止字符串)。

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


尝试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}