作者:Kenny Kerr
翻譯:Dflying Chen
原文:http://weblogs.asp.net/kennykerr/archive/2006/11/10/Windows-Vista-for-Developers-_1320_-Part-6-_1320_-The-New-File-Dialogs.aspx
請同時參考《Windows Vista for Developers》系列。
正如Aero嚮導比傳統的嚮導更加友好,任務對話方塊比老式的訊息框更加友好一樣,Windows Vista中最新的檔案對話方塊也給使用者帶來了全新的體驗,代替了那有些年頭的GetOpenFileName 和GetSaveFileName 函數。最新的檔案對話方塊不但與Windows Vista的外觀保持一致,而且也提供了全新的COM介面,簡化了使用的方式並為今後的擴充留下了充分的空間。
在《Windows Vista for Developers》系列系列的第六篇中,我們就來看看這些最新的、通過IFileDialog 相關介面提供的檔案對話方塊API。本文將首先查看一下相關的各種介面,然後使用一個C++類模板簡化其使用方式。在進入代碼之前,我們還是先來看看這個新的檔案對話方塊能給使用者帶來什麼改變吧。
使用者體驗
下面這個就是使用GetOpenFileName 函數得到的傳統開啟檔案對話方塊:
大多數使用者都對該對話方塊很熟悉,但對於那些習慣了使用Windows Vista的使用者來說,這個對話方塊卻似乎有些讓人迷惑,顯得與Windows Vista的格格不入。檔案夾的導航方式與Vista的資源管理員不一樣,很多Vista中的新東西,例如那個內建的搜尋方塊也不見了。在Vista中,資源管理員的樣式如下所示:
下面一張就是Windows Vista中的新版本的開啟檔案對話方塊:
除了底部的那個面板之外,其他部分都與資源管理員視窗非常類似。這種一致性保證了使用者在使用Windows Vista時感到得心應手。接下來就讓我們看看如何才能在應用程式中使用這個最新的開啟檔案對話方塊。
傳統的方式
作為參考,我們首先看看大多數Windows開發人員(使用C++)是如何建立一個傳統的檔案對話方塊的。大多數開發人員都不會直接使用GetOpenFileName 或GetSaveFileName 函數,而是會使用MFC或WTL的CFileDialog 類。下面就是在WTL中的一個樣本:
class OldSampleDialog : public CFileDialog
{
public:
OldSampleDialog() :
CFileDialog(true) // open dialog
{
// TODO: Customize dialog with m_ofn
}
};
OldSampleDialog dialog;
INT_PTR result = dialog.DoModal();
MFC和WTL都不支援新版本的檔案對話方塊,至少在我書寫本文的時候還不能,但本文的目的之一就是用C++封裝出一個類似的、使用起來同樣方便的輔助類。首先我們先來看一下這些新的API。
新的API
正如我在前面提到過的那樣,最新的檔案對話方塊API是通過一系列的COM介面提供給開發人員的。下面就是其中一些最常見的:
- IFileOpenDialog:由FileOpenDialog COM類實現,提供開啟檔案對話方塊的相關方法。
- IFileSaveDialog:由FileSaveDialog COM類實現,提供儲存檔案對話方塊的相關方法。
- IFileDialog:IFileOpenDialog 和IFileSaveDialog 的基類,提供大多數與對話方塊互動並進行自訂的功能的實現。
- IModalWindow: IFileDialog 的基類。提供了Show方法。
- IFileDialogCustomize:由FileOpenDialog 和FileSaveDialog COM 類別實現,提供了對在對話方塊中添加新控制項的支援。
- IFileDialogEvents:由應用程式實現,實現偵聽對話方塊中發生的事件的功能。
- IFileDialogControlEvents:由應用程式實現,實現偵聽對話方塊中添加的控制項所發生的事件的功能。
我們還可能會使用到一些不是全新的介面,例如IShellItem。本文將在稍後作以討論。
我們可以使用CComPtr 類模板建立一個開啟檔案對話方塊,代碼如下:
CComPtr<IFileOpenDialog> dialog;
HRESULT result = dialog.CoCreateInstance(__uuidof(FileOpenDialog));
一般來講,我們會使用從IFileDialog繼承下來的方法來自訂該對話方塊,這部分內容將在稍後介紹。若想更進一步進行自訂,那麼可以使用IFileDialogCustomize 介面所提供的功能:
CComPtr<IFileDialogCustomize> customize;
HRESULT result = dialog.QueryInterface(&customize);
準備完成之後,即可將該對話方塊顯示出來了:
HRESULT result = dialog->Show(parentWindow);
Show 方法將阻止在這裡,直到對話方塊關閉為止。
檔案類型
我們應該知道,IFileDialog 不使用來自於OPENFILENAME 的REG_MULTI_SZ相關字串,而使用了更加簡單的COMDLG_FILTERSPEC 結構來指定該檔案對話方塊能夠接收(開啟/儲存)的檔案類型:
COMDLG_FILTERSPEC fileTypes[] =
{
{ L"Text Documents", L"*.txt" },
{ L"All Files", L"*.*" }
};
HRESULT result = dialog->SetFileTypes(_countof(fileTypes),
fileTypes);
這種模型簡化了動態建立檔案類型列表時的工作,也不必再引入那個令人頭痛的字串表了。下面這個樣本示範了如何使用字串表實現本地化支援:
CString textName;
VERIFY(textName.LoadString(IDS_FILTER_TEXT_NAME));
CString textPattern;
VERIFY(textPattern.LoadString(IDS_FILTER_TEXT_PATTERN));
CString allName;
VERIFY(allName.LoadString(IDS_FILTER_ALL_NAME));
CString allPattern;
VERIFY(allPattern.LoadString(IDS_FILTER_ALL_PATTERN));
COMDLG_FILTERSPEC fileTypes[] =
{
{ textName, textPattern },
{ allName, allPattern }
};
SetFileTypeIndex 方法允許我們設定對話方塊中被選中的檔案類型。需要注意的是這個索引是從1開始的,而不是C或C++程式員所熟悉的從0開始。
HRESULT result = dialog->SetFileTypeIndex(2);
無論對話方塊是否已經開啟,我們都可以調用SetFileTypeIndex 方法來改變當前被選中的檔案類型。而GetFileTypeIndex 則可以在對話方塊開啟時或關閉後調用。
UINT index = 0;
HRESULT result = dialog->GetFileTypeIndex(&index);
檔案對話方塊選項
檔案對話方塊提供了非常多的選項,我們可以藉助於這些選項控制對話方塊的樣式以及行為。這些選項均被聲明為標記,並封裝在一個DWORD中。當前應用的選項可通過GetOptions 方法擷取,修改後也可以使用SetOptions 方法重新設定。除非你非常清楚每一個選項的意義,否則最好首先調用GetOptions 方法,然後根據需要修改之後再使用SetOptions 方法重新設定回去。這樣即可避免丟失某些預先設定好的選項。
下列代碼示範了如何強制開啟預覽面板:
DWORD options = 0;
HRESULT result = dialog->GetOptions(&options);
if (SUCCEEDED(result))
{
options |= FOS_FORCEPREVIEWPANEON;
result = dialog->SetOptions(options);
}
當然,使用者也可以手工顯示/隱藏該預覽面板(通過工具列上的Organize 下拉式功能表):
SetOptions方法的文檔詳細介紹了我們能夠使用的所有選項。
標籤和文字框控制項
我們也可以改變對話方塊上的一些文本元素的預設值。
SetTitle 方法可以設定該對話方塊的標題。SetOkButtonLabel 方法用來設定對話方塊預設按鈕上的文字。SetFileNameLabel 方法用來設定檔案名稱文字框控制項旁邊的那個標籤上的文字。
我們也可以使用SetFileName 和GetFileName 方法設定/擷取檔案名稱文字框控制項中的文本。
Shell項目
很多介面中的用來控制檔案對話方塊的方法使用shell項目,而不是檔案系統路徑來引用檔案夾。之所以要這樣做,是因為這樣之後,對話方塊不僅僅能夠與檔案系統中真實存在的檔案夾打交道,還可以引用一些“虛擬”的檔案夾。例如,“Computer”檔案夾中就有控制台的條目。若想瞭解更多常見的shell項目(無論是否為虛擬項目),你可以參考我的《Known Folders Browser》這篇文章。
shell項目非常靈活,藉助於他們的協助,我們可以容易地與很多接受IShellItem 的shell API打交道。但如果你只知道檔案系統路徑的話,那麼就必須將其轉換為相應的shell項目。幸運的是Windows Vista引入了一個新的名為SHCreateItemFromParsingName 的函數協助你完成這件事:
CComPtr<IShellItem> shellItem;
HRESULT result = ::SHCreateItemFromParsingName(L"D:\\SampleFolder",
0,
IID_IShellItem,
reinterpret_cast<void**>(&shellItem));
類似地,若想得到某個shell項目的檔案系統路徑,那麼可以使用GetDisplayName 方法:
AutoTaskMemory<WCHAR> buffer;
HRESULT result = shellItem->GetDisplayName(SIGDN_FILESYSPATH,
&buffer.p);
這裡的AutoTaskMemory 是一個簡單的類模板。在其解構函式中調用了CoTaskMemFree 方法,以便釋放由GetDisplayName 函數分配的記憶體。需要注意的是,shell項目並不一定總是指向某個文獻系統路徑的。例如若你將“::{ED228FDF-9EA8-4870-83b1-96b02CFE0D52}”傳遞給SHCreateItemFromParsingName 函數,將會得到指向“Games”虛擬資料夾的shell項目。因為該虛擬資料夾在檔案系統上並不存在,所以調用其GetDisplayName 方法將會失敗。
簡單介紹shell項目之後,我們也應該回到正題上來了,接下來我們來看看檔案對話方塊是如何使用shell項目的。IFileDialog 介面使用shell項目來甄別檔案夾和選擇。例如,我們可以按照如下代碼設定檔案對話方塊的初始檔案夾:
CString path = // load path...
CComPtr<IShellItem> shellItem;
HRESULT result = ::SHCreateItemFromParsingName(path,
0,
IID_IShellItem,
reinterpret_cast<void**>(&shellItem));
if (SUCCEEDED(result))
{
result = m_dialog->SetFolder(shellItem);
}
在對話方塊開啟之後,我們仍可以調用SetFolder 方法,讓對話方塊導航至指定的檔案夾。而GetFolder 則可以用來得到該檔案對話方塊在初始時將要顯示的檔案夾,若對話方塊已經打來了的話,那麼得到當前正選中的檔案%