從 Windows Vista 開始,共用項目對話框在開啟或儲存檔案時取代了舊有的共用檔案對話框。 共用物品對話框有兩種變體: 開啟 對話框與 儲存 對話框。 這兩個對話框功能大致相同,但各自有其獨特的方法。
Important
IFileDialog 是現代檔案對話對話 API(Windows Vista 及更新版本)。 舊版 的 GetOpenFileName 和 GetSaveFileName 函式是舊有的,不應用於新應用程式。 舊有 API 不支援 Shell 命名空間、現代對話框自訂或檔案中繼資料功能。
使用 IFileDialog時:
- 你 必須 先呼叫 CoInitializeEx ,才能呼叫 CoCreateInstance 來建立對話框。 將
COINIT_APARTMENTTHREADED用於 UI 執行緒。 - 將
CLSID_FileOpenDialog或CLSID_FileSaveDialog與CLSCTX_INPROC_SERVER搭配使用。 -
現代替代方案:若為 UWP/WinUI 應用程式,請使用 Windows.Storage.Pickers。 對於 .NET 桌面應用程式,WPF 和 WinForms 提供在內部使用
IFileDialog的OpenFileDialog/SaveFileDialog包裝器。
雖然這個新版本被稱為「共用項目對話框」,但在大多數文件中仍稱為「共用檔案對話框」。 除非你特別處理較舊版本的 Windows,否則你應該假設任何提到的「共用檔案對話框」都指的是這個「共用項目對話框」。
以下主題在此討論:
IFileDialog、IFileOpenDialog 與 IFileSaveDialog
Windows Vista 提供了開啟與儲存對話框的實作:CLSID_FileOpenDialog 和 CLSID_FileSaveDialog。 這些對話框如圖所示。
IFileOpenDialog 與 IFileSaveDialog 繼承自 IFileDialog ,並共享許多功能。 此外, Open 對話框支援 IFileOpenDialog, 儲存對話框 則支援 IFileSaveDialog。
Windows Vista 中的通用項目對話框實作相較於早期版本的實作,提供了多項優勢:
- 支援透過 IShellItem 直接使用 Shell 命名空間,而非使用檔案系統路徑。
- 可簡化對話框自訂,例如在 確定 鍵上設定標籤,無需掛鉤程序。
- 透過新增一組資料驅動的控制項,支援更廣泛的對話框自訂,這些控制項可在不使用 Win32 對話框範本的情況下運作。 這種自訂方案讓呼叫過程擺脫了 UI 佈局的束縛。 由於對話設計的任何變更仍會使用此資料模型,因此對話實作不綁定於當前對話框的特定版本。
- 支援呼叫者通知對話框內的事件,例如選取變更或檔案類型變更。 同時也讓呼叫程序能掛鉤對話中的特定事件,例如解析。
- 新增對話功能,例如在 地點 欄新增來電者指定地點。
- 在儲存對話框中,開發者可以利用 Windows Vista Shell 新增的元資料功能。
此外,開發者可選擇實作以下介面:
- IFileDialogEvents 以接收對話框內事件的通知。
- IFileDialog自訂 以新增對話框中的控制項。
- IFileDialogControlEvents 將通知新增控制項中的事件。
開啟或儲存對話框會回傳一個 IShellItem 或 IShellItemArray 物件給呼叫程序。 呼叫者接著可以使用個別 的 IShellItem 物件來取得檔案系統路徑,或在該物件上開啟串流以讀寫資訊。
新對話框方法可用的旗標與選項與 OPENFILENAME 結構中舊有的 OFN 旗標非常相似,這些旗標用於 GetOpenFileName 和 GetSaveFileName。 其中許多名稱完全相同,只是以 FOS 前綴開頭。 完整清單可在 IFileDialog:GetOptions 與 IFileDialog::SetOptions 主題中找到。 開啟 與 儲存 對話框預設使用最常見的標誌。 對 Open 對話方塊而言,這是 (FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR),而對 Save 對話方塊而言,這是 (FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR)。
IFileDialog 及其後繼介面繼承並擴充了 IModalWindow。
Show 唯一的參數是父視窗的 handle。 如果 Show 成功返回,則有有效結果。 如果回傳 HRESULT_FROM_WIN32(ERROR_CANCELLED),代表使用者取消了對話。 它也可能合法回傳其他錯誤代碼,例如 E_OUTOFMEMORY。
範例使用方式
以下章節展示各種對話任務的範例程式碼。
大部分範例程式碼可在 Windows SDK 通用檔案對話框範例中找到。
基本使用方式
以下範例說明如何啟動 Open 對話。 在此範例中,僅限於 Microsoft Word 文件。
Note
本主題中有多個範例使用 CDialogEventHandler_CreateInstance 輔助函式來建立 IFileDialogEvents 實作的實例。 若要在自己的程式碼中使用此函式,請從CDialogEventHandler_CreateInstance中複製該函式的原始碼,該範例本主題中的所有範例皆取自該範例。
HRESULT BasicFileOpen()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
DWORD dwCookie;
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set the options on the dialog.
DWORD dwFlags;
// Before setting, always get the options first in order
// not to override existing options.
hr = pfd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
// In this case, get shell items only for file system items.
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
if (SUCCEEDED(hr))
{
// Set the file types to display only.
// Notice that this is a 1-based array.
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
if (SUCCEEDED(hr))
{
// Set the selected file type index to Word Docs for this example.
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);
if (SUCCEEDED(hr))
{
// Set the default extension to be ".doc" file.
hr = pfd->SetDefaultExtension(L"doc;docx");
if (SUCCEEDED(hr))
{
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Obtain the result once the user clicks
// the 'Open' button.
// The result is an IShellItem object.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// We are just going to print out the
// name of the file for sample sake.
PWSTR pszFilePath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH,
&pszFilePath);
if (SUCCEEDED(hr))
{
TaskDialog(NULL,
NULL,
L"CommonFileDialogApp",
pszFilePath,
NULL,
TDCBF_OK_BUTTON,
TD_INFORMATION_ICON,
NULL);
CoTaskMemFree(pszFilePath);
}
psiResult->Release();
}
}
}
}
}
}
}
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
}
return hr;
}
將結果限制為檔案系統項目
以下範例取自上述,示範如何將結果限制為檔案系統項目。 請注意, IFileDialog::SetOptions 會將新旗標加入透過 IFileDialog::GetOptions 取得的值。 這是建議的方法。
// Set the options on the dialog.
DWORD dwFlags;
// Before setting, always get the options first in order
// not to override existing options.
hr = pfd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
// In this case, get shell items only for file system items.
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
為對話框指定檔案類型
若要設定對話框可處理的特定檔案類型,請使用 IFileDialog::SetFileTypes 方法。 該方法接受一組 COMDLG_FILTERSPEC 結構,每個結構代表一種檔案類型。
對話方塊中的預設副檔名機制與 GetOpenFileName 和 GetSaveFileName 相比並未改變。 使用者在檔案名稱編輯框中輸入文字後附加的副檔名會在對話框開啟時初始化。 它應該會和預設的檔案類型(對話框開啟時選擇的那種)相符。 如果預設檔案類型是「*.*」(所有檔案),則檔案可使用你選擇的副檔名。 若使用者選擇不同的檔案類型,副檔名會自動更新為與該檔案類型相關聯的第一個副檔名。 若使用者選擇「*.*」(所有檔案),副檔名會回復原始值。
以下範例說明了上述做法。
// Set the file types to display only.
// Notice that this is a 1-based array.
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
if (SUCCEEDED(hr))
{
// Set the selected file type index to Word Docs for this example.
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);
if (SUCCEEDED(hr))
{
// Set the default extension to be ".doc" file.
hr = pfd->SetDefaultExtension(L"doc;docx");
控制預設資料夾
Shell 命名空間中幾乎任何資料夾都可以作為對話框的預設資料夾(使用者選擇開啟或儲存檔案時呈現的資料夾)。 若要這麼做,請先呼叫 IFileDialog::SetDefaultFolder,再呼叫 Show。
預設資料夾是使用者第一次從應用程式開啟對話框時,對話框會從中開始的資料夾。 之後,對話框會從使用者最後開啟的資料夾或他們最後用來儲存物品的資料夾開啟。 更多細節請參見 狀態持久性 。
你可以透過呼叫 IFileDialog::SetFolder,強制對話框開啟時永遠顯示相同的資料夾,不論之前的使用者操作如何。 不過,我們不建議這麼做。 如果你在顯示對話框前呼叫 SetFolder ,使用者最近儲存或開啟的位置不會顯示。 除非有非常具體的原因,否則這不是良好或預期的使用者體驗,應該避免。 在幾乎所有情況下, IFileDialog::SetDefaultFolder 是更好的方法。
第一次在 儲存 對話方塊中儲存文件時,你應比照 開啟 對話方塊中決定初始資料夾時所遵循的相同準則。 如果使用者正在編輯先前存在的文件,請打開存放該文件的資料夾中的對話框,並在編輯框中填入該文件名稱。 在呼叫 Show 前,先用目前的項目呼叫 IFileSaveDialog::SetSaveAsItem。
將項目新增至位置列
以下範例示範了在 位置 列中新增項目的過程:
HRESULT AddItemsToCommonPlaces()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Always use known folders instead of hard-coding physical file paths.
// In this case we are using Public Music KnownFolder.
IKnownFolderManager *pkfm = NULL;
hr = CoCreateInstance(CLSID_KnownFolderManager,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pkfm));
if (SUCCEEDED(hr))
{
// Get the known folder.
IKnownFolder *pKnownFolder = NULL;
hr = pkfm->GetFolder(FOLDERID_PublicMusic, &pKnownFolder);
if (SUCCEEDED(hr))
{
// File Dialog APIs need an IShellItem that represents the location.
IShellItem *psi = NULL;
hr = pKnownFolder->GetShellItem(0, IID_PPV_ARGS(&psi));
if (SUCCEEDED(hr))
{
// Add the place to the bottom of default list in Common File Dialog.
hr = pfd->AddPlace(psi, FDAP_BOTTOM);
if (SUCCEEDED(hr))
{
// Show the File Dialog.
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
}
}
psi->Release();
}
pKnownFolder->Release();
}
pkfm->Release();
}
pfd->Release();
}
return hr;
}
狀態持續性
在 Windows Vista 之前,狀態(例如最後造訪的資料夾)是依每個程序儲存的。 然而,這些資訊無論行動如何都會被使用。 例如,影片剪輯軟體會在 「渲染為 」對話框中呈現與 「匯入媒體 」對話框相同的資料夾。 在 Windows Vista 中,你可以透過 GUID 更具體地設定。 若要為對話框指派 GUID ,請呼叫 iFileDialog::SetClientGuid。
多重選擇功能
多重選擇功能可在 開啟 對話框中使用 GetResults 方法,如圖所示。
HRESULT MultiselectInvoke()
{
IFileOpenDialog *pfd;
// CoCreate the dialog object.
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
DWORD dwOptions;
// Specify multiselect.
hr = pfd->GetOptions(&dwOptions);
if (SUCCEEDED(hr))
{
hr = pfd->SetOptions(dwOptions | FOS_ALLOWMULTISELECT);
}
if (SUCCEEDED(hr))
{
// Show the Open dialog.
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Obtain the result of the user interaction.
IShellItemArray *psiaResults;
hr = pfd->GetResults(&psiaResults);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
psiaResults->Release();
}
}
}
pfd->Release();
}
return hr;
}
聆聽對話中的事件
呼叫程序可使用 IFileDialog::Advise 與 IFileDialog::Unadvise 方法,如下所示,向對話方塊註冊 IFileDialogEvents 介面。
這取自 基本用法 範例。
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
DWORD dwCookie;
hr = pfd->Advise(pfde, &dwCookie);
大部分的對話處理會放在這裡。
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
}
return hr;
}
呼叫程序可利用事件通知使用者變更資料夾、檔案類型或選擇。 當呼叫程序在對話中新增控制項(參見「 自訂對話框」),並必須根據這些事件改變控制項的狀態時,這些事件特別有用。 在所有情況下,呼叫程序都能提供自訂程式碼,以處理例如分享違規、覆寫檔案,或在對話結束前判斷檔案是否有效。 本節中描述了部分案例。
OnFileOK
此方法在使用者選擇項目後、對話結束前呼叫。 應用程式接著可以呼叫 IFileDialog::GetResult 或 IFileOpenDialog::GetResults ,就像對話結束後一樣。 如果所選項目可接受,則可以傳回 S_OK。 否則,會回傳 S_FALSE,並顯示說明所選項目為何無效的 UI。 如果回傳S_FALSE,對話框不會關閉。
呼叫程序可以使用對話框本身的視窗代柄作為使用者介面的父節點。 這個句柄可以透過先呼叫 IOleWindow::QueryInterface 取得,然後再用這個句柄呼叫 IOleWindow::GetWindow ,如本範例所示。
HRESULT CDialogEventHandler::OnFileOk(IFileDialog *pfd)
{
IShellItem *psiResult;
HRESULT hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
SFGAOF attributes;
hr = psiResult->GetAttributes(SFGAO_COMPRESSED, &attributes);
if (SUCCEEDED(hr))
{
if (attributes & SFGAO_COMPRESSED)
{
// Accept the file.
hr = S_OK;
}
else
{
// Refuse the file.
hr = S_FALSE;
_DisplayMessage(pfd, L"Not a compressed file.");
}
}
psiResult->Release();
}
return hr;
};
HRESULT CDialogEventHandler::_DisplayMessage(IFileDialog *pfd, PCWSTR pszMessage)
{
IOleWindow *pWindow;
HRESULT hr = pfd->QueryInterface(IID_PPV_ARGS(&pWindow));
if (SUCCEEDED(hr))
{
HWND hwndDialog;
hr = pWindow->GetWindow(&hwndDialog);
if (SUCCEEDED(hr))
{
MessageBox(hwndDialog, pszMessage, L"An error has occurred", MB_OK);
}
pWindow->Release();
}
return hr;
}
OnShareViolation 與 OnOverwrite
如果使用者選擇在 儲存 對話框中覆寫檔案,或是正在使用或替換的檔案正在使用且無法寫入(屬於分享違規),應用程式可以提供自訂功能來覆寫對話框的預設行為。 預設情況下,當覆寫檔案時,對話框會顯示一個提示,讓使用者驗證此操作。 對於違規分享,預設對話框會顯示錯誤訊息,不會關閉,使用者需做出其他選擇。 呼叫程序可以覆寫這些預設值,並在需要時顯示自己的使用者介面(UI)。 對話框可以被指示拒絕該檔案並保持開啟,或接受檔案並成功關閉。
自訂對話
可以在對話框中新增多種控制項,而不必提供 Win32 對話框範本。 這些控制包括按鈕、組合框、編輯框、勾選按鈕、單選按鈕列表、群組、分隔符及靜態文字控制。 呼叫對話物件上的 QueryInterface (IFileDialog、 IFileOpenDialog 或 IFileSaveDialog)以取得 IFileDialogCustomize 指標。 用那個介面來新增控制功能。 每個控制項都有對應的來電者提供的 ID,以及一個可由呼叫程序設定的 可見 且 啟用 的狀態。 有些控制項,例如按鈕,也會附有文字。
多個控制項可新增至一個「視覺群組」,並在對話方塊的版面配置中作為單一單位移動。 群組可以有與其關聯的標籤。
控制項只能在對話顯示前加入。 然而,一旦對話框顯示,控制項可以依使用者操作被隱藏或顯示。 以下範例說明如何在對話框中新增單選按鈕清單。
// Controls
#define CONTROL_GROUP 2000
#define CONTROL_RADIOBUTTONLIST 2
#define CONTROL_RADIOBUTTON1 1
#define CONTROL_RADIOBUTTON2 2 // It is OK for this to have the same ID
// as CONTROL_RADIOBUTTONLIST, because it
// is a child control under CONTROL_RADIOBUTTONLIST
// This code snippet demonstrates how to add custom controls in the Common File Dialog.
HRESULT AddCustomControls()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
DWORD dwCookie = 0;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set up a Customization.
IFileDialogCustomize *pfdc = NULL;
hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
if (SUCCEEDED(hr))
{
// Create a Visual Group.
hr = pfdc->StartVisualGroup(CONTROL_GROUP, L"Sample Group");
if (SUCCEEDED(hr))
{
// Add a radio-button list.
hr = pfdc->AddRadioButtonList(CONTROL_RADIOBUTTONLIST);
if (SUCCEEDED(hr))
{
// Set the state of the added radio-button list.
hr = pfdc->SetControlState(CONTROL_RADIOBUTTONLIST,
CDCS_VISIBLE | CDCS_ENABLED);
if (SUCCEEDED(hr))
{
// Add individual buttons to the radio-button list.
hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON1,
L"Change Title to ABC");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON2,
L"Change Title to XYZ");
if (SUCCEEDED(hr))
{
// Set the default selection to option 1.
hr = pfdc->SetSelectedControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON1);
}
}
}
}
// End the visual group.
pfdc->EndVisualGroup();
}
pfdc->Release();
}
if (FAILED(hr))
{
// Unadvise here in case we encounter failures
// before we get a chance to show the dialog.
pfd->Unadvise(dwCookie);
}
}
pfde->Release();
}
if (SUCCEEDED(hr))
{
// Now show the dialog.
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
}
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfd->Release();
}
return hr;
}
在確定按鈕上新增選項
同樣地,選項也可以加入「 開啟 」或 「儲存 」按鈕,這些按鈕是對應對話類型設定的 確定 鍵。 選項可透過按鈕附帶的下拉選單框進入。 清單中的第一個項目會成為按鈕的文字。 以下範例展示了如何提供有兩種可能性的 開啟 按鈕:「開啟」與「唯讀開啟」。
// OpenChoices options
#define OPENCHOICES 0
#define OPEN 0
#define OPEN_AS_READONLY 1
HRESULT AddOpenChoices()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
DWORD dwCookie = 0;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set up a Customization.
IFileDialogCustomize *pfdc = NULL;
hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
if (SUCCEEDED(hr))
{
hr = pfdc->EnableOpenDropDown(OPENCHOICES);
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(OPENCHOICES, OPEN, L"&Open");
}
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(OPENCHOICES,
OPEN_AS_READONLY,
L"Open as &read-only");
}
if (SUCCEEDED(hr))
{
pfd->Show(NULL);
}
}
pfdc->Release();
}
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
return hr;
}
使用者的選擇可以在對話從 Show 方法回傳後驗證,就像 ComboBox 一樣,或是透過 IFileDialogEvents::OnFileOK 作為處理的一部分來驗證。
新增控制項中對事件的回應
呼叫程序所提供的事件處理程式除了 IFileDialogEvents 之外,還可以實作 IFileDialogControlEvents。 IFileDialogControlEvents 使呼叫程序能對以下事件做出反應:
- 按鈕按下
- CheckButton 狀態變更
- 從選單、組合盒或 RadioButton 列表中選擇的物品
- 控制項啟動中。 當選單即將顯示下拉選單時,會傳送此訊息,以防呼叫程序想要更改清單中的項目。
完整範例
以下是來自 Windows 軟體開發套件(SDK)的完整可下載 C++ 範例,示範如何使用及與 Common Item 對話框互動。
相關主題