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