[C++]利用IFileDialog打开(保存)文件对话框并获取文件路径

在Win10下使用GetOpenFileName() 以及GetSaveFileName() 打开对话框以后,常常在打开窗口时遇到系统莫名崩溃的问题,上MSDN了解到是这两个API已经过时了,推荐使用IFileDialog 这个接口。于是乎更改了我们的API,果然系统崩溃的问题不再出现。怀疑是之前的API系统handle没有很好的释放,而在IFileDialog 这个接口中,所有的handle和指针都得到了有效的释放。话不多说,来看看如何使用IFileDialog 这个接口。
使用这个API要包含头文件

#include <Shlobj.h>

首先创建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 <afx.h>
#include <Shlobj.h>
#include <string>
#include <windows.h>
#include <iostream>
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;
}

猜你喜欢

转载自blog.csdn.net/Murphy_CoolCoder/article/details/89380888
今日推荐