在Win10下使用GetOpenFileName() 以及GetSaveFileName() 打开对话框以后,常常在打开窗口时遇到系统莫名崩溃的问题,上MSDN了解到是这两个API已经过时了,推荐使用IFileDialog 这个接口。于是乎更改了我们的API,果然系统崩溃的问题不再出现。怀疑是之前的API系统handle没有很好的释放,而在IFileDialog 这个接口中,所有的handle和指针都得到了有效的释放。话不多说,来看看如何使用IFileDialog 这个接口。
使用这个API要包含头文件
#include
首先创建com对象
IFileDialog *pfd = NULL;
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
使用CoCreateInstance函数,第一参数是组件类型标示符,如果想开打“另存为对话框”, 则可以设置该参数为CLSID_FileSaveDialog。第二个参数如果为NULL,则表示该对象未作为聚合的一部分创建。如果为非NULL,则指向聚合对象的IUnknown接口。第三个参数是管理新创建的对象的代码将在其中运行的上下文,是一个枚举值。第四个参数是对用于与对象通信的接口的标识符的引用,第五个参数是指针变量的地址,用于接收riid中请求的接口指针。成功返回后,* ppv包含请求的接口指针。失败时,* ppv包含NULL。而我们这里只使用了4个参数,是因为最后两个参数被IID_PPV_ARGS 这个宏定义取代了,这个宏定义里实际上就是我们需要的两个参数,在宏定义里传入我们定义的IFileDialog 对象指针地址即可。
创建好对象后,我们要进行一些窗口设置。
DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags);
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM | FOS_ALLOWMULTISELECT);
COMDLG_FILTERSPEC fileType[] =
{
{ L"All files", L"*.*" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
hr = pfd->SetFileTypeIndex(1);
首先调用GetOptions获取原有对话框设置,然后调用SetOptions设置我们的对话框风格(是否允许多选)。如果我们想要实现“浏览文件夹功能”风格的对话框,可以设置参数为FOS_PICKFOLDERS。如果想要实现文件多选可以设置参数FOS_ALLOWMULTISELECT。然后设置文件类型,即打开对话框文件过滤器的设置,SetFileTypes第一个参数是文件过滤器的数目(fileType数组的大小),第二个参数是该文件过滤器的地址。例如我们设置两三个过滤器,一个可以打开所有文件,一个只允许打开txt文件,一个只允许打开png文件,则可以设置如下,
COMDLG_FILTERSPEC fileType[] =
{
{ L"All files", L"*.*" },
{ L"Text files", L"*.txt*" },
{ L"Pictures", L"*.png" },
};
对应的index 1 就是all files, 2就是Text file是, 3 就是Picture。
hr = pfd->SetFileTypeIndex(1) 可以选择打开对话框时显示哪一个过滤器。
设置好之后,调用
hr = pfd->Show(NULL);
即可显示对话框。这个时候,对话框显示出来,根据返回值hr就可以判断用户是点击去了取消还是选择了一个文件打开,根据不同的用户选择做出反应。如果用户点击了取消,那么就释放所有的接口指针并返回。如果用户点击了打开,就可以继续调用GetResult方法,获取用户选择的项目:
IShellItem *pSelItem;
hr = pfd->GetResult(&pSelItem);
这个方法直接就可以获取文件的IShellItem。通过这个接口指针,就可以很方便的实现如获取上一级目录的IShellItem指针,实现,获取文件名,还是获取完整路径等等。如果我们要获取文件完整路径,调用GetDisplayName()函数即可。第一个参数是一个枚举值,通过设置不同的值,返回的接口就不同,这里我分别设置获取所选择文件的完整路径和文件名。
LPWSTR pszFilePath = NULL;
hr = pSelItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszFilePath);
CoTaskMemFree(pszFilePath);
这里需要注意的是,这里的输入参数是一个指针,但是不用我们分配内存,由COM对象为我们分配,但是需要我们自己来释放,而且必须使用COM的内存管理方式来释放内存,释放类存使用CoTaskMemFree。然后释放IShellItem指针,
pSelItem->Release(); 最后释放对话框文件对象指针pfd->Release();
注意,这里面的指针对象的顺序和时机很重要,一定要在succeeded(hr)成功后再去释放。每次在调用对话框方法后,我们都要通过Succeeded(hr)来判断返回值是否成功,这样才能保证windows资源的顺利获取和释放。
多说一点,如果打开对话框时需要多选文件,在创建对话框接口对象可以使用
IFileOpenDialog *pfd = NULL;
在IFileOpenDialog 对象中有GetResults方法可以获取所勾选全部文件的路径,同时利用IShellItemArray获取文件的路径。
源代码如下以及测试代码如下,
下面是包了一层的打开对话框函数,大家可以根据自己场景需要包自己的函数。
main函数中是测试代码,选择的文件路径不要包含中文,否则不能正确显示结果。当然如果有兴趣的话可以修改测试代码从而能够显示中文字符。
#include
#include
#include
#include
#include
using namespace std;
//Windows API Open dialogbox.
//nType is a sign which deceide the dialog box filter format.
bool OpenWindowsDlg(bool isMultiSelect, bool IsOpen,bool IsPickFolder, int nType, CString *pFilePath, CStringArray *pFilePathArray= NULL)
{
CoInitialize(nullptr);
if (!isMultiSelect)
{
IFileDialog *pfd = NULL;
HRESULT hr = NULL;
if (IsOpen)
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
else
hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags);
if(IsPickFolder)
hr = pfd->SetOptions(dwFlags | FOS_PICKFOLDERS);
else
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
switch (nType)
{
case 0:
{
COMDLG_FILTERSPEC fileType[] =
{
{ L"All files", L"*.*" },
{ L"Text files", L"*.txt*" },
{ L"Pictures", L"*.png" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
break;
}
case 1: //open or save recipe only allow file with extension .7z
{
COMDLG_FILTERSPEC fileType[] =
{
{ L"Reciep",L"*.7z*" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
break;
}
case 2://Load or export file with different file extension
{
COMDLG_FILTERSPEC fileType[] =
{
{ L"Text",L"*.txt" },
{ L"CSV",L".csv" },
{ L"ini",L".ini" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
break;
}
case 3: //Save as a print screen capture as .png format.
{
COMDLG_FILTERSPEC fileType[] =
{
{ L"Picture",L"*.png" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
break;
}
case 4:
{
COMDLG_FILTERSPEC fileType[] =
{
{ L"Xml Document",L"*.xml*" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
break;
}
default:
break;
}
if (!IsOpen) //Save mode get file extension
{
if (nType == 1)
hr = pfd->SetDefaultExtension(L"7z");
if (nType == 3)
hr = pfd->SetDefaultExtension(L"jpg");
if (nType == 4)
hr = pfd->SetDefaultExtension(L"xml");
}
hr = pfd->Show(NULL); //Show dialog
if (SUCCEEDED(hr))
{
if (!IsOpen) //Capture user change when select differen file extension.
{
if (nType == 2)
{
UINT unFileIndex(1);
hr = pfd->GetFileTypeIndex(&unFileIndex);
switch (unFileIndex)
{
case 0:
hr = pfd->SetDefaultExtension(L"txt");
break;
case 1:
hr = pfd->SetDefaultExtension(L"csv");
break;
case 2:
hr = pfd->SetDefaultExtension(L"ini");
break;
default:
hr = pfd->SetDefaultExtension(L"txt");
break;
}
}
}
}
if (SUCCEEDED(hr))
{
IShellItem *pSelItem;
hr = pfd->GetResult(&pSelItem);
if (SUCCEEDED(hr))
{
LPWSTR pszFilePath = NULL;
hr = pSelItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszFilePath);
*pFilePath = pszFilePath;
CoTaskMemFree(pszFilePath);
}
pSelItem->Release();
}
}
pfd->Release();
}
else //Open dialog with multi select allowed;
{
IFileOpenDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags);
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM | FOS_ALLOWMULTISELECT);
COMDLG_FILTERSPEC fileType[] =
{
{ L"All files", L"*.*" },
{ L"Text files", L"*.txt*" },
{ L"Pictures", L"*.png" },
};
hr = pfd->SetFileTypes(ARRAYSIZE(fileType), fileType);
hr = pfd->SetFileTypeIndex(1);
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
IShellItemArray *pSelResultArray;
hr = pfd->GetResults(&pSelResultArray);
if (SUCCEEDED(hr))
{
DWORD dwNumItems = 0; // number of items in multiple selection
hr = pSelResultArray->GetCount(&dwNumItems); // get number of selected items
for (DWORD i = 0; i < dwNumItems; i++)
{
IShellItem *pSelOneItem = NULL;
PWSTR pszFilePath = NULL; // hold file paths of selected items
hr = pSelResultArray->GetItemAt(i, &pSelOneItem); // get a selected item from the IShellItemArray
if (SUCCEEDED(hr))
{
hr = pSelOneItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
if(pFilePathArray)
pFilePathArray->Add(pszFilePath);
if (SUCCEEDED(hr))
{
/*szSelected += pszFilePath;
if (i < (dwNumItems - 1))
szSelected += L"\n";*/
CoTaskMemFree(pszFilePath);
}
pSelOneItem->Release();
}
}
pSelResultArray->Release();
}
}
}
pfd->Release();
}
return true;
}
int main()
{
cout << "Open file with mutil setction allow? " << endl;
cout << "If yes, please input 1, other ipnut will default open file with single select allow only." << endl;
int nMulitiSelect(0);
cin >> nMulitiSelect;
if (1 == nMulitiSelect) //Please take note file path can't include chinese sign.
{
CString pFilePath("");
wcout << pFilePath.GetString() << endl;
CStringArray pFilePathArray;
OpenWindowsDlg(true, true, false, 0, &pFilePath, &pFilePathArray);
for (int i = 0; i < pFilePathArray.GetCount(); i++)
{
wcout << pFilePathArray.GetAt(i).GetString() << endl;
}
}
else
{
CString pFilePath("");
OpenWindowsDlg(false, true, false, 0, &pFilePath);
wcout << pFilePath.GetString() << endl;
}
system("Pause");
return 0;
}