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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章