Dino Esposito 下載本文的代碼:CuttingEdge0303.exe (96KB)
本頁內(nèi)容
OpenFileDialog
位置欄的系統(tǒng)設(shè)置
RegOverridePredefKey
重寫位置欄項(xiàng)
自定義的位置欄
小結(jié)
在 Microsoft .NET Framework 中,用 Windows 窗體確實(shí)可以很容易地顯示“打開文件”對話框,但是結(jié)果窗口不如您通過 Win32 API 創(chuàng)建它時(shí)易于自定義。在 Windows 2000 中,Microsoft 添加了一個(gè)不錯(cuò)的功能 — 位置欄,它是一個(gè)出現(xiàn)在窗口左側(cè)的垂直工具欄,它允許您選擇經(jīng)常訪問的文件夾。如圖 1 所示,位置欄包含可以使用戶直接訪問五個(gè)文件夾(歷史、桌面、我的文檔、我的電腦和網(wǎng)上鄰居)的按鈕。
在針對 Win32 API 中的“打開文件”通用對話框進(jìn)行編碼時(shí),您可以設(shè)置用來隱藏位置欄的樣式。但是,與 Win32 通用對話框的其他功能相同的是,在移植到 .NET Framework 時(shí),該設(shè)置似乎已經(jīng)丟失。創(chuàng)建通用對話框從來沒有像在 Framework 中那樣容易,但是這是以降低靈活性為代價(jià)的。另外,在托管代碼中,無法在具有其他控件的情況下擴(kuò)展該對話框的布局。
在本專欄中,我將著重介紹“打開文件”通用對話框的位置欄,并討論如何自定義所顯示文件夾的列表,以及如何使其特定于應(yīng)用程序,甚至特定于調(diào)用。在討論過程中,我將回顧 .NET Framework 中用來處理注冊表項(xiàng)的一些注冊表配置單元和節(jié)點(diǎn)以及 API。最后,我將顯示一個(gè)類,該類用代表在工具欄中顯示的位置的集合來擴(kuò)展 OpenFileDialog 系統(tǒng)類。
OpenFileDialog
OpenFileDialog 類表示系統(tǒng)提供的通用對話框,該對話框允許用戶選擇和打開文件。該類從 CommonDialog 繼承,但是其他類不可以從它繼承。要顯示對話框并選取文件,請使用以下非常簡單的 C# 代碼:
OpenFileDialog.InitialDirectory = @"c:\";OpenFileDialog.Filter = "Bitmap|*.bmp|All|*.*";OpenFileDialog.ShowDialog();
請注意 Filter 屬性字符串需要的特殊格式。Filter 屬性標(biāo)識文件類型框中的項(xiàng)。屬性的內(nèi)容必須是字符串,而且該字符串必須由通過管道分隔的字符串對組成。在每個(gè)管道分隔的對中,第一個(gè)標(biāo)記表示要選擇的類型的描述性名稱,而第二個(gè)標(biāo)記代表文件的掩碼。例如,如果您希望篩選文件夾視圖以顯示所有位圖或所有文件,請使用以下字符串:
Bitmap|*.bmp|All|*.*
在 .NET Framework 中,必須將管道用作分隔符。而在 Win32 中,則使用 null(在 ASCII 中,使用 0)。但是,在 Win32 中,通用的編程做法是在字符串中使用管道,然后以編程方式將它們替換為 null。一個(gè)好消息就是,該服務(wù)現(xiàn)在自動由 .NET Framework 提供。
OpenFileDialog 類公開一組用來配置對話框的屬性。例如,您可以選擇初始目錄、初始篩選器索引、窗口標(biāo)題、是否可以選擇多個(gè)文件,以及在關(guān)閉之前是否應(yīng)當(dāng)還原應(yīng)用程序的當(dāng)前目錄。當(dāng)用戶單擊“打開”按鈕時(shí),該類還引發(fā)事件(名為 FileOk)。正如我以前所提到的,OpenFileDialog 是密封類,因此您不能從它派生其他類。但是,如果您希望自定義文件對話框的行為,您應(yīng)當(dāng)盡可能地創(chuàng)建從抽象類 FileDialog 派生的嶄新的類。在本例中,您可以訪問幾個(gè)功能強(qiáng)大但受保護(hù)的方法(如 HookProc 和 RunDialog)。HookProc 定義對話框掛鉤進(jìn)程,該進(jìn)程向通用對話框添加特定功能。RunDialog 具有如下簽名:
protected abstract bool RunDialog( IntPtr hwndOwner);
該方法接收擁有對話框的窗口的 HWND 句柄。此方法的典型實(shí)現(xiàn)只是將 hwndOwner參數(shù)存儲在 CommonDialog 的受保護(hù)成員 hwndOwner 中。RunDialog 方法非常重要,這是由于它允許您設(shè)置持久性對話框,并從不同的文件夾選擇多個(gè)文件。要將其他模式對話框轉(zhuǎn)換為無模式對話框,請?zhí)鎿Q所有者窗口,并使該對話框成為桌面窗口的子窗口。桌面窗口的句柄由 Win32 API 函數(shù) GetDesktopWindow 返回。順便提一句,使用一些 API 函數(shù)自定義“打開文件”對話框是絕對有必要的。
位置欄的系統(tǒng)設(shè)置
在 Win32 或 .NET Framework 中,位置欄的內(nèi)容不能完全由應(yīng)用程序配置。然而,在過去的幾年中,一些知識庫文章已經(jīng)提到過,您只需在特定的注冊表項(xiàng)中編寫一些條目即可修改位置列表。換句話說,通常由對話框顯示的位置列表只是默認(rèn)列表。內(nèi)部的 Win32 代碼嘗試從注冊表中讀取用戶的位置列表。如果它失?。矗绻粗付斜恚?,則使用默認(rèn)列表。因此,您可以使用哪個(gè)注冊表項(xiàng)?它位于 HKEY_CURRENT_USER 下,因此特定于登錄用戶。下面是它的路徑:
Software \Microsoft \Windows \CurrentVersion \Policies \ComDlg32 \PlacesBar
該注冊表項(xiàng)在默認(rèn)情況下不存在,必須顯式創(chuàng)建。嘗試使用注冊表編輯器或 regedt32 創(chuàng)建注冊表項(xiàng)。請記住,必須以管理員權(quán)限登錄才能修改注冊表。在創(chuàng)建注冊表項(xiàng)之后,嘗試啟動任何使用 Windows“打開文件”通用對話框的 Win32 或托管應(yīng)用程序。例如,嘗試用 Microsoft 畫圖打開;結(jié)果顯示在圖 2 中。怎么啦?
當(dāng) Win32 內(nèi)部代碼檢測到剛創(chuàng)建的注冊表子樹時(shí),它會丟棄默認(rèn)列表。由于您尚未列出任何位置,所以位置欄是空的。(請注意,只要更改了這些設(shè)置的用戶仍處于登錄狀態(tài),此更改就會影響在系統(tǒng)上運(yùn)行的所有應(yīng)用程序。)
如何指定自己喜歡的位置?應(yīng)當(dāng)在上面提到的注冊表項(xiàng)中創(chuàng)建條目。不能創(chuàng)建五個(gè)以上的條目,這是由于多出的條目將被忽略。每個(gè)條目的類型都應(yīng)當(dāng)是 REG_SZ(字符串值),且必須被命名為“PlaceX”,其中 X 是從零開始的索引??赏ㄟ^完全限定路徑來指定位置?;蛘?,如果您希望將目標(biāo)定為特殊文件夾(如我的文檔或我的電腦),則使用文件夾 ID。在本例中,創(chuàng)建的是 REG_DWORD 條目并輸入十六進(jìn)制的文件夾號。圖 3 中的兩個(gè)位置條目可在圖 4 中看到實(shí)際效果。
如果指定了五個(gè)以下的條目,則其余條目保留為空。沒有比五個(gè)位置更適合地址欄的了。有一點(diǎn)很重要,那就是需要將條目命名為 Place0、Place1 等。為了恢復(fù)為默認(rèn)值,只需刪除已添加的兩個(gè)注冊表項(xiàng) — 即 ComDlg32\PlacesBar。在刪除注冊表項(xiàng)時(shí)一定要格外小心。您應(yīng)當(dāng)確保只刪除這兩個(gè)注冊表項(xiàng),否則可能會損壞系統(tǒng)!
下面的 C# 代碼顯示了如何枚舉當(dāng)前用戶的注冊位置:
RegistryKey placesBarRoot;placesBarRoot = Registry.CurrentUser.OpenSubKey(key);string[] valuesOfKey = placesBarRoot.GetValueNames();for(int i=0; i<valuesOfKey.Length; i++) { Console.WriteLine(placesBarRoot.GetValue(valuesOfKey[i]);}placesBarRoot.Close();
首先在當(dāng)前用戶的配置單元上打開位置欄項(xiàng)。RegistryKey 類的 GetValueNames 方法用找到的所有條目的名稱填充一個(gè)字符串?dāng)?shù)組。在我給出的示例中,valuesOfKey 數(shù)組將包含類似于 Place0、Place1 等的字符串。每個(gè)注冊表項(xiàng)的值都是通過 GetValue 方法讀取的。
這樣,您無疑可以自定義出現(xiàn)在“打開文件”通用對話框中的位置,但是不要忘記這些更改會擴(kuò)展到所有正在運(yùn)行的應(yīng)用程序。注冊表設(shè)置基于每用戶工作,Microsoft 不提供 API 以編程方式基于每應(yīng)用程序或基于每調(diào)用來配置位置欄。是否可以在每次顯示文件對話框時(shí)欺騙系統(tǒng)并自定義位置欄?答案是肯定的,但是實(shí)現(xiàn)并不是一件小事,它需要使用鮮為人知的 Win32 API 函數(shù) — RegOverridePredefKey。
RegOverridePredefKey
RegOverridePredefKey 函數(shù)需要 Windows 2000 或更高版本;它不受運(yùn)行 Windows 9x 系統(tǒng)的支持。該函數(shù)主要面向軟件安裝程序,它允許將一個(gè)預(yù)定義的注冊表項(xiàng)映射到另一個(gè)注冊表項(xiàng)。該函數(shù)的最終目標(biāo)是允許安裝程序檢查可安裝組件嘗試對注冊表進(jìn)行的更改。實(shí)際上,在調(diào)用某些組件之前,安裝程序?qū)⒃趧e處映射關(guān)鍵的注冊表子樹,并使組件繼續(xù)。
當(dāng)組件完成之后,安裝程序檢查所做的更改,并確定它們是否安全。如果安全的話,安裝程序會將這些更改映射到由 DLL 指向的初始位置。否則,在寫入到目標(biāo)位置之前,這些更改要么被拒絕,要么被修改。該函數(shù)的原型如下所示:
LONG RegOverridePredefKey( HKEY hKey, HKEY hNewHKey );
第一個(gè)參數(shù)是要重新映射的注冊表項(xiàng),而第二個(gè)參數(shù)是重定向注冊表項(xiàng)所在的位置。重定向的注冊表項(xiàng)要么應(yīng)當(dāng)是預(yù)定義的項(xiàng),要么應(yīng)當(dāng)是當(dāng)前打開的注冊表項(xiàng)。在 RegOverridePredefKey 返回之后,對 hKey 的任何調(diào)用都在 hNewHKey 項(xiàng)中的子樹下解析。例如,請考慮以下調(diào)用:
HKEY hkMyCU; RegCreateKey(HKEY_CURRENT_USER, "Dino", &hkMyCU); RegOverridePredefKey(HKEY_CURRENT_USER, hkMyCU);
實(shí)際上,在調(diào)用 hKey 之后,用來從 Software\Microsoft 讀取的調(diào)用實(shí)際上將從 Dino\Software\Microsof 讀取。這兩項(xiàng)的配置單元必須相同。自動重定向僅適用于調(diào)用 RegOverridePredefKey 的過程。如果 hNewHKey 參數(shù)是 null,此函數(shù)將恢復(fù)該項(xiàng)的默認(rèn)映射。重定向特定于進(jìn)程,正是這一事實(shí)為您提供用來實(shí)現(xiàn)特定于應(yīng)用程序的位置欄的工具。
重寫位置欄項(xiàng)
創(chuàng)建或更新注冊表?xiàng)l目是自定義“打開文件”對話框中位置列表的唯一方法。但是,要使所做的更改特定于應(yīng)用程序,您需要修改每個(gè)調(diào)用的唯一系統(tǒng)項(xiàng)的內(nèi)容。如果您真的在應(yīng)用程序需要顯示對話框時(shí)讀取注冊表并寫入到其中,就會產(chǎn)生太多的通信量,而且,更重要的是,并發(fā)運(yùn)行的應(yīng)用程序?qū)⑾嗷ブ貙懺O(shè)置。通過使用 RegOverridePredefKey 項(xiàng),實(shí)際上將創(chuàng)建指定注冊表樹的特定于進(jìn)程的副本,并且可以在不損壞注冊表的情況下更新它。由于映射只位于進(jìn)程的上下文中,因此不會影響正在運(yùn)行的其他應(yīng)用程序。.NET Framework 不直接支持 RegOverridePredefKey 函數(shù),這意味著您需要通過 P/Invoke 互操作性平臺顯式導(dǎo)入該函數(shù)。
在 .NET Framework 中,并非所有的 Win32 注冊表函數(shù)都有與之匹配的托管類或靜態(tài)方法。作為 Win32 中注冊表核心的數(shù)據(jù)結(jié)構(gòu)(即 HKEY 句柄)已在 .NET Framework 中完全隱藏,并且已被名為 RegistryKey 的類替代。RegistryKey 類確實(shí)在內(nèi)部使用 HKEY 句柄,但是您無法從在 .NET Framework 上構(gòu)建的應(yīng)用程序內(nèi)部讀取該信息。實(shí)際上,hkey 數(shù)據(jù)成員被標(biāo)記為私有成員。這對您會有何影響?(誰知道 Redmontonian 是否將在下一版本的 .NET Framework 中公開 HKEY 成員?)
不能通過導(dǎo)入和修改 RegOverridePredefKey 函數(shù)來使用 RegistryKey 類的實(shí)例,從而管理注冊表項(xiàng)。您應(yīng)當(dāng)按如下方式導(dǎo)入該函數(shù):
[DllImport("advapi32.dll")]private static extern long RegOverridePredefKey( IntPtr hkey, IntPtr hnewKey);
問題在于您不能調(diào)用我剛顯示的包裝方法,并向它傳遞使用 .NET Framework 類打開的注冊表項(xiàng)。此時(shí),我發(fā)現(xiàn)兩種可能的方法:要么使用 Win32 函數(shù)(如 RegOpenKeyEx 和 RegOpenKey)打開要映射的注冊表項(xiàng)及其重定向器,要么將所有的初始化代碼放在新的 Win32 DLL 中,并從應(yīng)用程序內(nèi)部調(diào)用它的函數(shù)。在本專欄中,我將選擇第二種方法。圖 5 顯示了用來完成該任務(wù)的簡單 DLL 的 Win32 代碼。(我必須承認(rèn),對我來說,在經(jīng)過兩年多時(shí)間只針對 .NET Framework 進(jìn)行編碼之后,返回到 Win32 DLL 有點(diǎn)困難。)
該庫定義和導(dǎo)出兩個(gè)名為 InitializeRegistry 和 ResetRegistry 的函數(shù)。InitializeRegistry 是無參數(shù)的函數(shù),但是返回 Win32 HKEY 對象。ResetRegistry 不返回任何值,但是采用通過上一方法打開的同一個(gè) HKEY。InitializeRegistry 函數(shù)打開 HKEY_CURRENT_USER 配置單元,并將其映射到新創(chuàng)建的名為 Dino 的節(jié)點(diǎn)。(當(dāng)然,您可以將它隨意重命名為對您來說更有意義的名稱。)該函數(shù)創(chuàng)建注冊表項(xiàng)(如果它尚且不存在的話),然后打開它。在調(diào)用 InitializeRegistry 之后,如果嘗試在 HKCU 下讀取和寫入,都將導(dǎo)致在重新映射的注冊表項(xiàng)下讀取和寫入,這只針對當(dāng)前的進(jìn)程發(fā)生。ResetRegistry 恢復(fù)事情的自然順序,刪除映射并關(guān)閉重定向器注冊表項(xiàng)。當(dāng)然,為了避免代碼的其他部分出現(xiàn)嚴(yán)重問題,應(yīng)當(dāng)在非常短的時(shí)間間隔內(nèi)調(diào)用 InitializeRegistry/ResetRegistry。在加載窗體時(shí),永遠(yuǎn)不要調(diào)用 InitializeRegistry;在卸載時(shí),永遠(yuǎn)不要調(diào)用 ResetRegistry。
在編譯 Win32 DLL 之后,將文件放在與托管應(yīng)用程序可執(zhí)行文件相同的文件夾中。在編譯 DLL I 時(shí),由于習(xí)慣了 .NET Framework 編程的直接性,我總是忘記向 Visual Studio 6.0 項(xiàng)目中添加導(dǎo)出文件。我已經(jīng)假設(shè):如果聲明 APIENTRY,這些函數(shù)已經(jīng)夠了。因此,在 .NET Framework 上構(gòu)建的應(yīng)用程序報(bào)告過幾次錯(cuò)誤,在 Win32 DLL 中找不到這樣的函數(shù)。使用 .NET Framework 中的公共關(guān)鍵字是如此簡單和方便!下面的代碼片斷說明了如何在托管應(yīng)用程序中導(dǎo)入這兩個(gè)函數(shù)。
[DllImport("myregutil.dll")]private static extern IntPtr InitializeRegistry();[DllImport("myregutil.dll")]private static extern void ResetRegistry(IntPtr hKey);
自定義的位置欄
現(xiàn)在,讓我們看一看如何利用這個(gè)小型的 Win32 庫,為針對“打開文件”通用對話框進(jìn)行的每個(gè)調(diào)用創(chuàng)建自定義位置欄。在這里,我的目標(biāo)是使用特殊類來設(shè)置位置欄并顯示對話框。但是,如上所述,不能通過從 OpenFileDialog 類繼承來創(chuàng)建新的對話框類。當(dāng)不可能繼承時(shí),聚合始終是要考慮的替代選項(xiàng)。讓我們創(chuàng)建一個(gè) OpenDialogPlaces 類,以嵌入 OpenFileDialog 類的實(shí)例。下面的代碼片斷顯示了這個(gè)特定類將如何工作:
OpenDialogPlaces o = new OpenDialogPlaces(); o.Places.Add(@"c:\\"); o.Places.Add(@17); o.Init(); o.OpenDialog.ShowDialog(); o.Reset();
該類公開了一個(gè)名為 Places 的可根據(jù)需要填充的數(shù)組。在完成后,調(diào)用名為 Init 的特定于類的方法并更新注冊表。接著,顯示對話框并調(diào)用 Reset,以便恢復(fù)注冊表的初始狀態(tài)。請注意,這不是實(shí)現(xiàn)該功能唯一可能的方法;它只為您提供了如何實(shí)現(xiàn)它的思路。
OpenDialogPlaces 類從 Object 繼承,并在實(shí)例化時(shí)創(chuàng)建 OpenFileDialog 對象:
public OpenDialogPlaces() { m_places = new ArrayList(); m_OpenFileDialog = new OpenFileDialog(); }
其他私有成員包括用來重寫注冊表項(xiàng)的句柄,這些成員另存為 IntPtr 對象。公共成員是指表示嵌入的通用對話框?qū)嵗?Places 集合的 OpenDialog 對象。
Init 方法設(shè)置要發(fā)出調(diào)用的注冊表。在 Reset 被調(diào)用之前,該映射一直持續(xù),如下所示:
m_overriddenKey = InitializeRegistry();RegistryKey reg;reg = Registry.CurrentUser.CreateSubKey(Key_PlacesBar);for(int i=0; i<Places.Count; i++){ if(Places[i] != null) reg.SetValue("Place" + i.ToString(), Places[i]); }
對 InitializeRegistry 的調(diào)用為如下操作奠定了基礎(chǔ):為要使用的進(jìn)程創(chuàng)建虛設(shè)注冊表樹。上述代碼在重定向器項(xiàng)下創(chuàng)建樹,并定義已由用戶添加到 Places 集合中的位置條目。
針對 Registry 對象使用 CreateSubKey 方法,可以創(chuàng)建子樹。請注意,Win32 庫重新映射 CurrentUser 項(xiàng),因此,您必須在它的正下方創(chuàng)建子樹。SetValue 方法允許您創(chuàng)建注冊表?xiàng)l目,該方法是 RegSetValueEx Win32 API 函數(shù)的托管包裝。在默認(rèn)情況下,它創(chuàng)建 REG_SZ 條目。該方法的簽名如下所示:
public void SetValue(string name, object value)
值參數(shù)的實(shí)際類型確定所創(chuàng)建的注冊表項(xiàng)的類型。特別是,如果該參數(shù)的類型是 Int32,就會創(chuàng)建 REG_DWORD 條目。以上代碼在內(nèi)部執(zhí)行以下代碼片斷中顯示的類型檢查:
if (value as Int32 != null){...}
如果文件夾的名稱是絕對路徑或相對路徑,您將需要 REG_SZ 條目。如果您希望指向特殊文件夾,您需要使用文件夾特定的數(shù)字(有關(guān)數(shù)字列表,請參閱圖 6)。在本例中,您需要 REG_DWORD 條目。
小結(jié)
如果您用以下代碼中顯示的值填充 Places 數(shù)組,然后顯示文件對話框,將看到一個(gè)類似于圖 7 所示的窗口:
o.Places.Add(@"C:\"); o.Places.Add(17); o.Places.Add(5); o.Places.Add(@"C:\My Articles"); o.Places.Add(6);
如果您用自定義圖標(biāo)或注釋指示文件夾,這將由位置欄中的項(xiàng)目反映出來。如圖 8 所示,兩個(gè)同時(shí)運(yùn)行的應(yīng)用程序可以擁有不同的位置欄。
此處討論的訣竅并不特定于 .NET Framework。它與 Windows 2000 操作系統(tǒng)的某些特征有關(guān),因此,在 Win32 中同樣可以很好地工作。
將您對 Dino 的問題和評論發(fā)送到 cutting@microsoft.com。
Dino Esposito 是一位教師和顧問,他在意大利羅馬工作。他是 Building Web Solutions with ASP.NET and ADO.NET 和 Applied XML Programming for .NET(這兩本書均由 Microsoft Press 出版)的作者,他將大部分時(shí)間花在講授 ASP.NET 以及會議演講上。Dino 目前正為 Microsoft Press 編寫 Programming ASP.NET。請通過 dinoe@wintellect.com 與 Dino 聯(lián)系。