[轉]Windows Shell 編程 第五章 【來源:http://blog.csdn.net/wangqiulin123456/article/details/7987939】

第五章 瀏覽文件夾

         我在第二章中給出了文件夾的概覽和它在Windows Shell中的地位,在這一章中咱們打算更詳細地討論它們。咱們主要集中精力闡述涉及文件夾全部層面的Shell函數,以及保證全部操做順利進行的潛在機理。所以,咱們須要深刻研究兩個起着很是重要做用的概念:快捷方式和PIDLs。前者是下一章的題目,在這一章中咱們將研究PIDLs,其中包括:程序員

         SHBrowseForFolder()函數的用途算法

    關於PIDLs進一步的討論,以及怎樣使用PIDLsshell

    虛擬文件夾和位置編程

    怎樣得到文件夾的設置小程序

咱們將要論述的例子包含了一個加強版本的API函數SHBrowseForFolder(),一些使它更容易同PIDLs一道工做的輔助函數,以及一些怎樣枚舉某些指定位置(如,‘發送到’,‘Favorites’,以及‘個人資料’)內容的樣例程序。api

選擇文件夾

         讓咱們先從各類文件夾選擇方法開始咱們的討論。對於讓用戶可以從特定驅動器上選擇特殊目錄的應用程序,這是一個普通的需求。Windows3.x API沒有爲這提供任何內建的工具,因此必須創建本身的輔助函數,然而,有一項通用技術可使用,它由修改通用對話框模版組成,例如刪除象列表框那樣的包含文件名的控件。數組

         然而,推出這個方案到Win32有一個障礙:你必須拋棄新探測器風格的用戶界面,而仍然忠實地使用老界面:安全

 

                   

 

Win32平臺上,探測器風格的‘打開’對話框是一個單一實體,其中的任何控件都是不能擺脫掉的(好比:文件列表框)服務器

         要選擇採用老的Windows3.x界面,另外一個選擇是安排一個VC++顯示方式的對話框到新項目中來請求一個特殊的文件夾。網絡

                      

 

 

跳出Win32關於GetOpenFileName()函數的資料,可以發現更多的東西。

 

更現代的方法

Wondwos95 開始,Win32 SDK 就包含了瀏覽文件夾的系統解決方案:這個函數稱爲SHBrowseForFolder()。其主要的特徵是使用相似於咱們已知的和探測器鍾情的樹觀察:

                        

 

 

與前兩章中咱們測試過的函數同樣,SHBrowseForFolder()函數有一個簡單的原型,可是,它實際上包含了一個帶有大量設置和標誌的結構,能夠把這個函數看做是文件夾的中心函數,其目的之一就是使咱們可以在桌面命名空間中選擇可用的文件夾。

 

SHBrowseForFolder()函數的原型

         如今看一下SHBrowseForFolder()的原型,它聲明在shlobj.h中:

 

[cpp]  view plain copy print ?
 
  1. LPITEMIDLIST WINAPI SHBrowseForFolder(LPBROWSEINFO lpbi);  

 

參數只有一個BROWSEINFO結構的指針,它的聲明也在同一個文件中:

 

[cpp]  view plain copy print ?
 
  1. typedef struct _browseinfo  
  2. {  
  3. HWND hwndOwner;  
  4. LPCITEMIDLIST pidlRoot;  
  5. LPSTR pszDisplayName;  
  6. LPCSTR lpszTitle;  
  7. UINT ulFlags;  
  8. BFFCALLBACK lpfn;  
  9. LPARAM lParam;  
  10. int iImage;  
  11. } BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;  

 

 

成員的說明以下表:

名稱

描述

hwndOwner

擁有這個對話框的窗口Handle

pidlRoot

被表述層次對象的根節點標識。是一個PIDL

pszDisplayName

必須是一個已分配緩衝區的指針,它將包含選擇對象的顯示名。

lpszTitle

必須是一個緩衝區指針,包含一個做爲樹觀察標題的串。

ulFlags

指定外觀和窗口行爲(後面將介紹有效的值)

Lpfn

用於鉤住對話框的回調函數。

lParam

32位傳遞給回調函數的客戶數據。一般是一個指針或Handle

Iimage

包含選中文件夾或文件的圖標索引。是相對系統圖像列表的索引。

調用SHBrowseForFolder()最簡單的方法是:

 

[cpp]  view plain copy print ?
 
  1. BROWSEINFO bi;  
  2. ZeroMemory(&bi, sizeof(BROWSEINFO));  
  3. bi.hwndOwner = hDlg;  
  4. LPITEMIDLIST pidl = SHBrowseForFolder(&bi);  

 

這段代碼顯示一個前面看到過的對話框,而且恢復選中文件夾的PIDL。若是文件夾有一個對應的路徑,你能夠經過下述代碼得到:

 

[cpp]  view plain copy print ?
 
  1. TCHAR szPath[MAX_PATH] = {0};  
  2. SHGetPathFromIDList(pidl, szPath);  
  3. Msg(szPath);<span style="font-family: 'Times New Roman'; "> </span>  

 

有幾個有趣的結果與使用SHBrowseForFolder()函數有關。下面給出概述,在整個下一節咱們都將詳細地討論這些問題:

         這個函數透明地處理PIDLs和路徑名

         這個函數容許瀏覽特殊的系統文件夾

         這個函數返回大量的信息,是SHGetFileInfo()所不能的。

         對話框稍微能夠客戶化,這老是一個好消息。

SHBrowseForFolder()函數的用法

         SHBrowseForFolder()函數所能作的事情由BROWSEINFO結構的ulFlags成員所限制,其合法的值由下述標誌的組合構成:

 

標誌

描述

BIF_RETURNONLYFSDIRS

若是設置,僅在用戶選擇了文件系統的目錄後,OK按鈕才被容許。例如,你選擇‘網上鄰居’節點,若是這個標誌設置,OK按鈕是灰的。

BIF_DONTGOBELOWDOMAIN

不顯示網絡文件夾,僅有域名節點。

BIF_STATUSTEXT

對話框模版含有能夠顯示任何文字的標籤,特別是在子類化這個對話框窗口後(後面將詳細講解)

BIF_EDITBOX

這是Shell 4.71版之後的新特徵,它容許一個編輯框,在這裏能夠手動輸入文件夾。

BIF_VALIDATE

這是Shell 4.71版的另外一個新特徵,它是對BIF_EDITBOX標誌功能的補充。若是你設置了這個標誌,而且子類化了這個對話框,則用戶每次在編輯框鍵入和確認一個不正確的文件或文件夾名時,你都能收到通知(後面將詳細講解)。

BIF_BROWSEFORCOMPUTER

容許用戶僅選擇計算機名。瀏覽正常發生,可是OK按鈕老是灰的,除非選擇了計算機名。

BIF_BROWSEFORPRINTER

與上相同,可是是打印機。

BIF_BROWSEINCLUDEFILES

若是這個標誌設置,無論其它標誌如何,在樹觀察中都顯示文件名,而不只僅是文件夾名。這就提供了設置對話框顯示系統中全部打印機或可用字體的機會。

在調用SHBrowseForFolder()函數時,有兩種方法來客戶化最終對話框的外觀,經由回調函數子類化這個窗口更有力一些,咱們將在這一章的之後部分討論這個內容。得到有限程度客戶化的較簡單方法是修改樹觀察上面的文字。BROWSEINFO結構的lpszTitle成員負責這一點。它聲明爲一個指針,因此,你必須傳遞一個有效的內存緩衝區:

 

TCHAR szBuf[MAX_PATH] = {0};

lstrcpy(szBuf, __TEXT("Choose a folder:"));

bi.lpszTitle = static_cast<LPCSTR>(szBuf);

對pszDisplayName成員,也同樣,它是一個返回緩衝區。若是你對選中文件夾的顯示名感興趣,就須要傳遞一個有效的緩衝區,首先聲明或分配它,而後 把指針賦值到pszDisplayName。

TCHAR szDisp[MAX_PATH] = {0};

bi.pszDisplayName = static_cast<LPSTR>(szDisp);

函數認爲pszDisplayName至少有MAX_PATH字節尺寸。

         正如前幾章說明的,文件夾的顯示名是探測器用來顯示文件夾的名字。例如,(C)的顯示名是C/

函數返回了什麼

技術上講,函數返回的是PIDL,它標識一個選中的文件或文件夾。若是‘取消’了對話框,函數返回NULL,很是簡單。然而,這個函數還可以經過傳遞的BROWSEINFO結構返回其它有用的信息。這一點的特殊例子是包含選中對象的顯示名(上面已經提到了),和表明它的圖標。

獲取文件夾的圖標

         即便SHBrowseForFolder()看起來彷佛正在重複咱們已經從SHGetFileInfo()函數得到的功能。然而,就得到和顯示圖標仍然有至關的工做須要作。

    在函數返回時,BROWSEINFO結構的iImage成員含有一個數字,它是圖標在系統圖像列表中的位置索引。於是,若是想要繪製圖標—或更簡單,想要它的HICON Handle—你就必須首先得到這個圖像列表的Handle。

    在前一章中已經講到了怎樣取得圖標,可是,採用這裏的方法要容易一些。若是使用SHGFI_ICON和標誌調用SHGetFileInfo(),而且設置了SHGFI_SYSICONINDEX,函數則返回系統圖像列表的Handle。

HICON SHGetSystemIcon(int iIconIndex)

{

SHFILEINFO sfi;

ZeroMemory(&sfi, sizeof(SHFILEINFO));

// 不指定文件名,由於咱們只想要一個Handle...

HIMAGELIST himl = reinterpret_cast<HIMAGELIST>(SHGetFileInfo(

"*.*", 0, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_SYSICONINDEX));

HICON hIcon = ImageList_ExtractIcon(0, himl, iIconIndex);

return hIcon;

}

上面的代碼是一個輔助例程,給定一個索引,返回系統圖像列表中對應的圖標。要運行這段代碼須要包含shellapi.h,和經過調用InitCommonControls()或InitCommonControlsEx()初始化公共控件庫。就象附錄A中討論的那樣,第一個方法適用於老版本的Shell,第二才被推薦到Shell 4.71及其之後的版本。

使用回調函數

         有趣的是SHBrowseForFolder()函數要求一個回調函數。要子類化由這個函數創建的對話框,你須要指派一個有效的函數指針到BROWSEINFO的lpfn字段。這個指針必須指向有以下原型的函數:

int CALLBACK BrowseCallbackProc(HWND hwnd,

UINT uMsg,

LPARAM lParam,

LPARAM dwData);

其中hwnd是被鉤住窗口的Handle,uMsg接收到的消息。lParam是一個值,根據uMsg它有不一樣的意義,而最後這個dwData是用戶定義的數據—與你經過BROWSEINFO的lParam成員指定的數據相同。若是你須要回調函數在調用程序創建的數據上工做,而不是使用全程變量,應該使用一個32位值來填寫BROWSEINFO結構的lParam成員,而且保證它自動地經由dwData變量傳遞給回調函數。爲了適合多個數據的傳遞,可使用指針,更好地,分配一個Handle內存塊,鎖定它,封包全部東西,解鎖,而後把它存入lParam字段。下圖顯示了回調函數設置的情形:SHBrowseForFolder()調用了你所定義的函數,傳送一些數據和通知某些事件。

                        

 

 

 

能夠感知的事件

         由SHBrowseForFolder()創建的對話框能夠通知回調函數下列事件:

        對話框初始化完成

        選擇已經改變

        用戶在編輯框中鍵入了無效文件或文件夾。

經過發送下面消息完成這些功能:

BFFM_INITIALIZED

BFFM_SELCHANGED

BFFM_VALIDATEFAILED

這些消息由回調函數經過uMsg參數接收。每個消息都在lParam變量帶有一個LPARAM類型的值。如今就讓咱們逐個消息地觀看一下lParam是怎樣配置的:

消息

lParam 意義

BFFM_INITIALIZED

無用消息—它老是NULL,這個消息在對話框窗口過程完成WM_INITDIALOG後發送。

BFFM_SELCHANGED

指向新選中文件夾的標識符列表,注意,就象其餘Windows控件同樣,在選擇已經改變時通知改變事件。

BFFM_VALIDATEFAILED

指向編輯框的當前內容。就象它標誌的那樣,這個消息僅僅在Shell 4.71中支持。由返回0,回調函數能夠強迫瀏覽對話框關閉,返回1,對話框保持活動。

 

能夠發送的消息

         有幾個回調函數能夠發送給對話框窗口的消息,使它能執行必定的活動。它們是:

 

消息

描述

BFFM_ENABLEOK

根據lParam值,容許或禁止OK按鈕。若是非零,按鈕容許,此時能夠確認當前選中的文件夾。wParam 沒有用。

BFFM_SETSELECTION

選擇特殊文件或文件夾。lParam中存儲一個指向PIDL或路徑名的指針,wParam表示怎樣解釋這個指針。FALSE說明是一個PIDLTRUE說明路徑名。

BFFM_SETSTATUSTEXT

設置你提供的文字到對話框的狀態區域。實際文字由lParam指向,WParam無用。

這些消息正常地使用SendMessage()函數發送,經過組合它們,你能夠實際地加強SHBrowseForFolder()的行爲。

客戶化用戶界面

         經過使用回調函數,你能夠介入和改變對話框的用戶界面,於是,使它能夠更好地適應你的需求。例如,你不但願?(幫助)按鈕在標題條上出現,或使某些控件具備更顯著的3D外觀等。下面內容將說明這些功能是怎樣實現的

刪除關聯幫助按鈕

         從標題條中刪除關聯幫助按鈕是一個關於窗口顯示風格的簡單問題:你只須要關閉處使Windows繪製和處理它的指示位便可。在擴展風格的任何窗口設置了WS_EX_CONTEXTHELP位時,這個按鈕就顯示。

    擴展風格首先在Windows3.x中提出,而後在Windows95的SDK版本中獲得增強。在正常風格和擴展風格之間的惟一不一樣在於它們佔用的存儲區域,而不是概念上的差異。

         你須要使用不一樣於訪問窗口風格的代碼來訪問‘擴展’風格,爲了關閉使幫助按鈕顯示的位,在回調函數響應BFFM_INITIALIZED消息時中所必須這樣作:

DWORD dwStyle = GetWindowLong(hwnd, GWL_EXSTYLE);

SetWindowLong(hwnd, GWL_EXSTYLE, dwStyle & ~WS_EX_CONTEXTHELP);

首先,取得當前擴展風格(用GWL_EXSTYLE 代替 GWL_STYLE),而後關閉指定位,最後回存這個風格值。

給狀態文字加3D邊框

         作這個比前一個稍微複雜一點,而且要求多行代碼。然而你應該清楚,如今所作的並不能保證你的代碼在全部存在的和將來的Windows版本中都能正常工做。它僅僅能工做在你已經成功地測試了這個功能的地方。

         如今,咱們想要繪製一個帶有3D邊框的狀態標籤,就象狀態條那樣。BIF_STATUSTEXT標誌可能有點誤導—它不是象所指望的那樣在窗口的底部加一個狀態條,而是在樹觀察上方和標題之間加一個靜態標籤。這個標籤窗口有一個控件ID,咱們能夠經過Spy++得知:

          

 

 

當你知道了這個控件的ID以後,一旦回調函數進入到對話框代碼之中,得到任何子窗口的Handle就象調用下述代碼那樣容易:

HWND hwndChild = GetDlgItem(hDlg, controlID);

在上圖中你能夠看到,咱們感興趣的標籤有ID0x3743,因此:

HWND hwndLabel = GetDlgItem(hwnd, 0x3743);

dwStyle = GetWindowLong(hwndLabel, GWL_EXSTYLE);

SetWindowLong(hwndLabel, GWL_EXSTYLE, dwStyle | WS_EX_STATICEDGE);

SetWindowPos(hwndLabel, NULL, 0, 0, 0, 0,

SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME);

上述代碼加了一個細邊框到窗口中,外觀以下圖所示:

                           

 

 

 

注意,爲了看到這個變化,你須要強迫窗口重畫它的非客戶區域,這就是SetWindowPos()函數出現的緣由。再有就是,全部這些操做均在回調函數響應BFFM_INITIALIZED消息時完成。

         前面我已經警告過,這段代碼潛在的風險,如今,在全部Win32 平臺上都能很好地工做,可是有一天微軟決定改變這個ID時,將會怎樣?一個好的方法多是:

 

[cpp]  view plain copy print ?
 
  1. HWND hwndLabel = GetDlgItem(hwnd, 0x3743);  
  2. // 檢查是否爲一可用的窗口  
  3. if(IsWindow(hwndLabel))  
  4. {  
  5. // 如今檢查體是否爲一個'static'窗口類  
  6. TCHAR szClass[MAX_PATH] = {0];  
  7. GetClassName(hwndLabel, szClass, MAX_PATH);  
  8. if(lstrcmpi(szClass, __TEXT("static")))  
  9. return;  
  10. }  
  11. else  
  12. return;  



 

咱們對GetDlgItem()函數返回的Handle執行了兩個檢查,頭一個是使用IsWindow()來檢查是否爲有效的窗口,第二個是驗證這個窗口是否是一個標籤—一個‘static’類窗口。若是其中有一個檢查失敗,則退出,以免非法操做。

改變對話框標題

         比加3D邊框更有用的是改變對話框窗口的標題—使用新字符串調用SetWindowText(),即,在響應BFFM_INITIALIZED消息時,執行下面代碼:

SetWindowText(hwnd, szNewCaption);

移動對話框窗口

         另外一個在響應BFFM_INITIALIZED消息的初始化中能夠作的是定位窗口到指定位置。典型地,能夠移動對話框到屏幕中心:

 

[cpp]  view plain copy print ?
 
  1. RECT rc;  
  2. GetClientRect(hwnd, &rc);  
  3. SetWindowPos(hwnd, NULL,  
  4. (GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2,  
  5. (GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2,  
  6. 0, 0, SWP_NOZORDER | SWP_NOSIZE);  



 

變更狀態標籤

         狀態標籤的典型用途是顯示當前選中的文件或文件夾名,就象在前面圖中顯示的那樣。其實現方法就是響應BFFM_SELCHANGED消息:

 

[cpp]  view plain copy print ?
 
  1. TCHAR szText[MAX_PATH] = {0};  
  2. SHGetPathFromIDList(reinterpret_cast<LPITEMIDLIST>(lParam), szText);  
  3. SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, reinterpret_cast<LPARAM>(szText));  

 

在接收到這個消息的時候,lParam變量指向一個新選中文件夾或文件的PIDL,說明文件或文件夾是存在的,所以能夠調用SHGetPathFromIDList()函數得到可顯示的路徑名,注意,不是全部文件夾映射到物理目錄—例如‘個人計算機’,是一個沒有實際目錄的文件夾。若是使用‘個人計算機’的PIDL調用SHGetPathFromIDList()函數,可能會得到一個NULL字符串。

    從SHGetPathFromIDList()中恢復的字符串可使用BFFM_SETSTATUSTEXT消息發送到狀態窗口。

容許手動編輯

         自綁定到IE4.0Shell 4.71版開始,這個對話框的用戶界面就加入了一個編輯框,並且不須要藉助於回調方式就能夠操做。你只要在調用SHBrowseForFolder()函數時簡單地設置BIF_EDITBOX標誌便可。結果顯示以下:

                         

 

 

這個編輯框使你可以用輸入文件夾的名字來選擇它們。當你點擊‘OK’時,函數將確認你的輸入有效。若是編輯框中包含了一個文件夾的全路徑名,或當前選擇的文件夾名,則它的內容是正確的,如上圖所示。若是BIF_VALIDATE標誌設置,而且SHBrowseForFolder()函數發現編輯框的內容是不正確的,則它用BFFM_VALIDATEFAILED消息喚醒回調函數,在編輯框中的字符串經由lParam變量傳遞給回調函數。經由BROWSEINFO結構的lParam成員傳遞的任何用戶數據,做爲回調函時的第四個參數傳遞。所以,若是須要經過輸入選擇文件夾,絕對必須輸入全路徑名。

         下面列出的代碼例子說明咱們目前所看到的所有狀況,後面章節中將給出整個應用的例程。

 

[cpp]  view plain copy print ?
 
  1. int CALLBACK BrowseCallbackProc(  
  2. HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM dwData)  
  3. {  
  4. switch(uMsg)  
  5. {  
  6. case BFFM_INITIALIZED:  
  7. {  
  8. // 刪除標題中的?  
  9. DWORD dwStyle = GetWindowLong(hwnd, GWL_EXSTYLE);  
  10. SetWindowLong(hwnd, GWL_EXSTYLE, dwStyle & ~WS_EX_CONTEXTHELP);  
  11. // 給狀態文字加3D邊框  
  12. HWND hwndLabel = GetDlgItem(hwnd, 0x3743);  
  13. // 檢查是否爲有效的窗口  
  14. if(IsWindow(hwndLabel))  
  15. {  
  16. // 檢查是否爲'static'類窗口  
  17. TCHAR szClass[MAX_PATH] = {0};  
  18. GetClassName(hwndLabel, szClass, MAX_PATH);  
  19. if(lstrcmpi(szClass, __TEXT("static")))  
  20. break;  
  21. }  
  22. else  
  23. break;  
  24. dwStyle = GetWindowLong(hwndLabel, GWL_EXSTYLE);  
  25. SetWindowLong(hwndLabel, GWL_EXSTYLE, dwStyle | WS_EX_STATICEDGE);  
  26. SetWindowPos(hwndLabel, NULL, 0, 0, 0, 0,  
  27. SWP_NOSIZE | SWP_NOMOVE | SWP_DRAWFRAME);  
  28. }  
  29. break;  
  30. case BFFM_SELCHANGED:  
  31. {  
  32. TCHAR szText[MAX_PATH] = {0};  
  33. SHGetPathFromIDList(reinterpret_cast<LPITEMIDLIST>(lParam),  
  34. szText);  
  35. SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0,  
  36. reinterpret_cast<LPARAM>(szText));  
  37. }  
  38. break;  
  39. case BFFM_VALIDATEFAILED:  
  40. Msg("/"%s/" is a wrong path name.",  
  41. reinterpret_cast<LPTSTR>(lParam));  
  42. return 1;  
  43. }  
  44. return 0;  
  45. }  



 

指定初始文件夾

         SHBrowseForFolder()函數設計時的一個缺陷是沒有一個方便的方法來指定開始瀏覽的初始目錄。你能夠指定顯示層的根,就是這樣,若是想要使用目錄而不是文件夾,也不簡單。爲了在代碼中設置初始選中的文件夾,咱們必須藉助於回調函數,特別是,必須探索BFFM_SETSELECTION消息,和請求函數移動焦點到一個特殊的文件夾。實現的最好方式是在響應BFFM_INITIALIZED通知消息的地方,下面就是實現的代碼:

 

[cpp]  view plain copy print ?
 
  1. int CALLBACK BrowseCallbackProc(  
  2. HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM dwData)  
  3. {  
  4. switch(uMsg)  
  5. {  
  6. case BFFM_INITIALIZED:  
  7. {  
  8. ...  
  9. SendMessage(hwnd, BFFM_SETSELECTION, TRUE, dwData);  
  10. }  
  11. break;  
  12. ...  
  13. }  
  14. return 0;  
  15. }  



 

BFFM_SETSELECTION消息須要知道lParam變量是一個PIDL仍是一個路徑名,在上面代碼中由於設置(第三個參數)爲TRUE,因此dwData指向一個路徑名,若dwData指向PIDL,則第三個參數應該設置爲FALSE。

指定根節點

         前面已經暗示,SHBrowseForFolder()函數容許指定桌面層上的哪個節點做爲根節點。換句話說,你能夠選擇想要瀏覽的探測器觀察子樹。能這樣作的參數就是BROWSEINFO結構的pidlRoot成員。若是這個成員設置爲NULL,樹觀察以桌面做爲根。下面圖中顯示瀏覽對話框以‘打印機’做爲根節點,而且設置了BIF_BROWSEINCLUDEFILES標誌:

                         

 

 

附帶地,這個例子還說明,BIF_BROWSEINCLUDEFILES的做用。代碼以下:

 

[cpp]  view plain copy print ?
 
  1. LPITEMIDLIST pidl = NULL;  
  2. BROWSEINFO bi;  
  3. ZeroMemory(&bi, sizeof(BROWSEINFO));  
  4. bi.lpszTitle = __TEXT("Choose a printer:");  
  5. SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pidl);  
  6. bi.pidlRoot = pidl;  
  7. bi.ulFlags = BIF_BROWSEINCLUDEFILES;  
  8. SHBrowseForFolder(&bi);  



 

若是覈對一下BROWSEINFO結構的聲明,你將看到,pidlRoot成員應該有LPCITEMIDLIST類型—即,PIDL。在上面代碼段中,經過在函數SHGetSpecialFolderLocation()第二個參數中傳遞CSIDL_PRINTERS,咱們已經取得了這個值,這一點將在後面繼續討論。如今,歸納地講,你能夠指定顯示樹的根節點,可是須要提供一個PIDL。

使用目錄做爲根

         若是咱們的目標正好就是瀏覽某個系統文件夾,如‘打印機’或‘字體’或‘Favorites’,沒有問題—使用上面的代碼段,只需把CSIDL_PRINTERS換成想要的文件夾ID便可。若是想要把普通的目錄做爲樹觀察的根,事情就有點嚴重了。

    對於特殊文件夾ID,翻看SHGetSpecialFolderLocation()函數的資料或研究shlobj.h源碼就能夠找到某些資料沒有說明的IDs。

轉換路徑名到PIDL

         沒有別的辦法,你只有轉換路徑名到PIDL。如今,你可能但願有一個ShellAPI函數作這個轉換,不幸的是沒有這樣的函數。然而,有一種方法可以轉換路徑名到PIDL,這須要兩個步驟:

                   得到指向IShellFolder接口的指針

        調用它的ParseDisplayName()的方法

ParseDisplayName()方法確實能實現你所要求的轉換:它接受路徑名,而後把它轉換成PIDL,問題是咱們怎樣才能得到指向IShellFolder接口的指針。在你寫一個命名空間擴展的時候,IShellFolder接口是一個須要實現的接口,而且,探測器也使用這個接口一塊兒工做,請求繪製和枚舉其內容。一個指向IShellFolder接口的指針由SHGetDesktopFolder()函數返回—精確地講,它返回一個桌面文件夾的IShellFolder接口。就咱們考慮的狀況而言,咱們只是須要一個提供實際實現ParseDisplayName()功能的對象指針,於是SHGetDesktopFolder()函數返回的是可用的。下面是一個新Shell函數的代碼,它接受路徑名和返回對應的PIDL,以微軟命名習慣,咱們稱之爲SHPathToPidl():

 

[cpp]  view plain copy print ?
 
  1. HRESULT SHPathToPidl(LPCTSTR szPath, LPITEMIDLIST* ppidl)  
  2. {  
  3. LPSHELLFOLDER pShellFolder = NULL;  
  4. OLECHAR wszPath[MAX_PATH] = {0};  
  5. ULONG nCharsParsed = 0;  
  6. // 獲取IShellFolder接口指針  
  7. HRESULT hr = SHGetDesktopFolder(&pShellFolder);  
  8. if(FAILED(hr))  
  9. return hr;  
  10. // 轉換路徑名到Unicode  
  11. MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, wszPath, MAX_PATH);  
  12. // 調用ParseDisplayName()函數  
  13. hr = pShellFolder->ParseDisplayName(  
  14. NULL, NULL, wszPath, &nCharsParsed, ppidl, NULL);  
  15. // 清理  
  16. pShellFolder->Release();  
  17. return hr;  
  18. }  



 

ParseDisplayName()函數的原形以下:

 

[cpp]  view plain copy print ?
 
  1. HRESULT ParseDisplayName(HWND hwndOwner,  
  2. LPBC pbcReserved,  
  3. LPOLESTR lpszDisplayName,  
  4. ULONG* pchEaten,  
  5. LPITEMIDLIST* ppidl,  
  6. ULONG* pdwAttributes);  



 

第一個變量是用做函數可能須要顯示消息框的父窗口的Handle。第二個,pbcReserved沒有使用,必須設置爲NULL。頭一個有意義的變量是lpszDisplayName,表示一個要求轉換的Unicode格式的名字串,pchEaten是一個包含實際傳遞字符數的緩衝,而pdwAttributes(若是不空NULL),則包含由lpszDisplayName所指定文件夾的屬性,這些屬性都是SHGAO_前綴常量屬性。若是不注意,可能會傳遞NULL屬性。最後是ppidl變量,新生成的PIDL的返回緩衝。一旦對指定的路徑成功地生成了PIDL,你就可以限制用戶的瀏覽到指定的子樹,而不能繼續向上,例如:

                   

 

 

這個圖顯示C:/Program Files做爲根文件夾的情形。

總結例程

         到目前爲止,咱們孤立地討論了SHBrowseForFolder()函數,而且老是給出解決特殊問題的代碼段,如今,咱們構造一個完整的應用來總結上面全部咱們看到的特性。

      

   

圖中的對話框是測試SHBrowseForFolder()函數特徵所使用過的,稱之爲SHBrowse。經過選擇路徑名(文件夾編輯框)或PIDL(PIDL列表框)指定根文件夾—使用PIDL複選框肯定是哪種路徑。能夠在標題編輯框中設置對話框標題,以及幾個與SHBrowseForFolder()函數標誌大體匹配的複選框,結果顯示在下面的區域,顯示名,路徑名和文件夾圖標。增長的第一段代碼在OnInitDialog()函數中,以便使用特殊文件夾的名字設置PIDL下拉列表框。

 

[cpp]  view plain copy print ?
 
  1. void OnInitDialog(HWND hDlg)  
  2. {  
  3. // 設置圖標(T/F 大/小圖標)  
  4. SendMessage(hDlg, WM_SETICON, FALSE, reinterpret_cast<LPARAM>(g_hIconSmall));  
  5. SendMessage(hDlg, WM_SETICON, TRUE, reinterpret_cast<LPARAM>(g_hIconLarge));  
  6. // 填寫下拉列表框  
  7. HWND hwndCbo = GetDlgItem(hDlg, IDC_SPECIAL);  
  8. int i = ComboBox_AddString(hwndCbo, "Control Panel");  
  9. ComboBox_SetItemData(hwndCbo, i, CSIDL_CONTROLS);  
  10. i = ComboBox_AddString(hwndCbo, "Favorites");  
  11. ComboBox_SetItemData(hwndCbo, i, CSIDL_FAVORITES);  
  12. i = ComboBox_AddString(hwndCbo, "Printers");  
  13. ComboBox_SetItemData(hwndCbo, i, CSIDL_PRINTERS);  
  14. i = ComboBox_AddString(hwndCbo, "Fonts");  
  15. ComboBox_SetItemData(hwndCbo, i, CSIDL_FONTS);  
  16. i = ComboBox_AddString(hwndCbo, "SendTo");  
  17. ComboBox_SetItemData(hwndCbo, i, CSIDL_SENDTO);  
  18. ComboBox_SetCurSel(hwndCbo, 0);  
  19. }  



 

此時你能夠從這個列表中選擇‘發送到’(SendTo)文件夾,這依賴於你的其它選擇設置。如圖:

                           

 

 

固然,這個對話框在你本身的計算機上可能稍有不一樣,這是由於‘SendTo’目錄可能有不一樣的快捷方式。在此選擇‘OutLook Express’,結果以下:

                    

 

 

整個項目是可用的,代碼包含了BrowseCallbackProc(),SHGetSystemIcon()和SHPathToPidl()函數。編譯以前必定要記住#include shlobj.h 和resource.h。這些函數在點擊OK按鈕時被執行。

 

[cpp]  view plain copy print ?
 
  1. void OnOK(HWND hDlg)  
  2. {  
  3. BROWSEINFO bi;  
  4. TCHAR szTitle[MAX_PATH] = {0};  
  5. TCHAR szPath[MAX_PATH] = {0};  
  6. TCHAR szDisplay[MAX_PATH] = {0};  
  7. LPITEMIDLIST pidl = NULL;  
  8. LPMALLOC pMalloc = NULL;  
  9. // 準備  
  10. ZeroMemory(&bi, sizeof(BROWSEINFO));  
  11. bi.hwndOwner = hDlg;  
  12. // 標題和顯示名  
  13. GetDlgItemText(hDlg, IDC_TITLE, szTitle, MAX_PATH);  
  14. bi.lpszTitle = szTitle;  
  15. bi.pszDisplayName = szDisplay;  
  16. //初始目錄  
  17. if(IsDlgButtonChecked(hDlg, IDC_USEPIDL))  
  18. {  
  19. HWND hwndCbo = GetDlgItem(hDlg, IDC_SPECIAL);  
  20. int i = ComboBox_GetCurSel(hwndCbo);  
  21. int nFolder = ComboBox_GetItemData(hwndCbo, i);  
  22. SHGetSpecialFolderLocation(NULL, nFolder, &pidl);  
  23. bi.pidlRoot = pidl;  
  24. }else{  
  25. // 轉換路徑名到 PIDL  
  26. GetDlgItemText(hDlg, IDC_FOLDER, szPath, MAX_PATH);  
  27. if(lstrlen(szPath) == 0)  
  28. GetCurrentDirectory(MAX_PATH, szPath);  
  29. SHPathToPidl(szPath, &pidl);  
  30. bi.pidlRoot = pidl;  
  31. }  
  32. // 採集標誌  
  33. UINT uiFlags = 0;  
  34. if(IsDlgButtonChecked(hDlg, IDC_NOBELOW))  
  35. uiFlags |= BIF_DONTGOBELOWDOMAIN;  
  36. if(IsDlgButtonChecked(hDlg, IDC_ONLYDIRS))  
  37. uiFlags |= BIF_RETURNONLYFSDIRS;  
  38. if(IsDlgButtonChecked(hDlg, IDC_INCLUDEFILES))  
  39. uiFlags |= BIF_BROWSEINCLUDEFILES;  
  40. if(IsDlgButtonChecked(hDlg, IDC_EDITBOX))  
  41. uiFlags |= BIF_EDITBOX | BIF_VALIDATE;  
  42. if(IsDlgButtonChecked(hDlg, IDC_STATUS))  
  43. uiFlags |= BIF_STATUSTEXT;  
  44. if(IsDlgButtonChecked(hDlg, IDC_COMPUTER))  
  45. uiFlags |= BIF_BROWSEFORCOMPUTER;  
  46. bi.ulFlags = uiFlags;  
  47. // 設置回調  
  48. bi.lpfn = BrowseCallbackProc;  
  49. bi.lParam = 0;  
  50. // 調用函數  
  51. LPITEMIDLIST pidlFolder = SHBrowseForFolder(&bi);  
  52. if(pidlFolder == NULL)  
  53. return;  
  54. // 顯示結果...  
  55. // 顯示顯示名  
  56. SetDlgItemText(hDlg, IDC_DISPLAYNAME, bi.pszDisplayName);  
  57. // 顯示路徑名  
  58. SHGetPathFromIDList(pidlFolder, szPath);  
  59. SetDlgItemText(hDlg, IDC_PATHNAME, szPath);  
  60. // 顯示文件夾圖標  
  61. HICON hIcon = SHGetSystemIcon(bi.iImage);  
  62. SendDlgItemMessage(  
  63. hDlg, IDI_ICON, STM_SETICON, reinterpret_cast<WPARAM>(hIcon), 0);  
  64. // 釋放  
  65. SHGetMalloc(&pMalloc);  
  66. pMalloc->Free(pidl);  
  67. pMalloc->Free(pidlFolder);  
  68. pMalloc->Release();  
  69. }  



 

上面函數的工做方式對你來說是很顯然的—除了,最後面的一段。爲了進一步解釋它,咱們須要更深地瞭解關於PIDL的知識。

有點變態的PIDL

         在第二章中咱們檢測了PIDLs,在這裏,咱們獲得了它的特殊應用:使用PIDL瀏覽文件夾的內容,不管這個內容是什麼。每個Windows Shell 的元素都有它本身的PIDL而且包含在某種文件夾中,於是,對於每個元素,都有一段代碼來處理文件夾和依據文件夾自有的規則和需求提供PIDL。也就是說,咱們從不能設定PIDL結構或它所組合成的數據,而是必須使用通用接口來處理它。例如,若是但願探索SHITEMID結構鏈,你就應該在每一步檢查下一個塊的長度。就像你已經看到過的那樣,一個ITEMIDLIST—或PIDL—是由一個或多個連續分配的SHITEMID組成的,這個鏈在cb字段爲0的元素上終止。下面是從MSDN上摘錄的函數,它能夠說明怎樣遍歷這個列表的項。這與普通的列表操做沒有太大的差異。

 

[cpp]  view plain copy print ?
 
  1. LPITEMIDLIST GetNextItemID(LPITEMIDLIST pidl)  
  2. {  
  3. // 取得制定項的尺寸  
  4. int cb = pidl->mkid.cb;  
  5. // 尺寸爲零則列表終止  
  6. if(cb == 0)  
  7. return NULL;  
  8. // 加cb到pidl (生成字節增量)  
  9. pidl = (LPITEMIDLIST)(((LPBYTE)pidl) + cb);  
  10. // 若是NULL終止則返回NULL,不然返回一個pidl  
  11. return (pidl->mkid.cb == 0) ? NULL : pidl;  
  12. }  

 

你不能夠設定PIDL的格式,對於一個文件夾,一種方法可能工做的很好,對於另外一個可能會失敗。例如,爲了保證兩個項是相同的,你必須經過IShellFolder::CompareIDs()方法請求文件夾本身來比較它們。

釋放PIDL

         在進一步討論以前,有必要解釋一下上面例子中的最後一段代碼。在文件夾創建PIDLs時,一般必須由其它模塊銷燬,這就是咱們在OnOK()函數最後所作的。標識符列表的內存從Shell應用的分配器上取得,向第二章中所見,咱們能夠調用SHGetMalloc()函數得到指向分配器的指針。通常,調用順序以下:

 

[cpp]  view plain copy print ?
 
  1. LPMALLOC pMalloc;  
  2. SHGetMalloc(&pMalloc); // 取得IMalloc 接口  
  3. pMalloc->Free(pidl); // 釋放標識符列表  
  4. pMalloc->Release(); // 釋放IMalloc 接口  

 

 

怎樣使用PIDL

         回到咱們的題目當中,使PIDLs有某些實際的用途,這裏有兩個主要的目標,頭一個,咱們想要可以枚舉任何文件夾的內容,第二個是但願重複探測器在Shell4.71和更高版本中所支持的特徵。爲了展現這個想法,下面是探測器在地址欄輸入‘打印機’後,鍵入‘回車’截圖:

                

 

 

探測器容許使用‘打印機’做爲一般意義的文件夾名。換言之,它模糊了物理文件夾和虛擬文件夾的差別,精確地講,‘打印機’是一個內容爲有效打印機設備的虛擬文件夾的顯示名。在這個例子中咱們創建一個示範程序稱之爲Pidl,其用戶界面以下:

       

 

 

‘搜索路徑’按鈕接受編輯框的內容並努力查找具備這個名字的文件夾。編輯框中的串就是一個文件夾的顯示名(記住,路徑名也是顯示名)。若是成功,應用將在列表觀察中顯示全部在這個文件夾中找到的文件對象。另外一方面,‘顯示PIDL內容’按鈕將在列表觀察中枚舉特殊文件夾中找到的全部文件對象,特殊文件夾在下拉列表中選擇。

經過顯示名搜索

         讓咱們從點擊‘搜索路徑’按鈕所執行的代碼開始,固然這兩個新按鈕都須要APP_DlgProc()過程來處理,因此在這裏添加代碼以下:

 

[cpp]  view plain copy print ?
 
  1. case WM_COMMAND:  
  2. switch(wParam)  
  3. {  
  4. case IDC_SEARCHPATH:  
  5. DoSearchPath(hDlg);  
  6. return FALSE;  
  7. case IDC_PIDLCONTENT:  
  8. DoEnumeratePidl(hDlg);  
  9. return FALSE;  
  10. case IDCANCEL:  
  11. EndDialog(hDlg, FALSE);  
  12. return FALSE;  
  13. }  
  14. break;  

 

這裏所涉及到的第一個函數是DoSearchPath(),它從‘文件夾名’編輯框中讀出你鍵入的名字,把它做爲搜索路徑名,若是它確實是一個路徑名,全部操做都正常進行,可是若是它是一個文件夾的顯示名,將會有什麼事情發生呢?咱們但願函數可以處理如C:/和(C:)等的串,這個實現將可以正確處理全部路徑名和與桌面關聯的子文件夾的顯示名,或‘個人計算機’等虛擬文件夾。

注意,正常地,驅動器的顯示名由括弧中驅動器字符加標號給出,例如:Ms-Dos_6(C:)。若是沒有標號,則認爲有一個前導空格在(C:)中

 

[cpp]  view plain copy print ?
 
  1. DoSearchPath()一開始就枚舉桌面文件夾的內容:  
  2. void DoSearchPath(HWND hDlg)  
  3. {  
  4. LPITEMIDLIST pidl = NULL;  
  5. LPSHELLFOLDER pFolder = NULL;  
  6. LPSHELLFOLDER pSubFolder = NULL;  
  7. // 取得內存分配器  
  8. LPMALLOC pMalloc = NULL;                                      
  9. SHGetMalloc(&pMalloc);  
  10. // 取得搜索名  
  11. TCHAR szName[MAX_PATH] = {0};  
  12. GetDlgItemText(hDlg, IDC_FOLDER, szName, MAX_PATH);  
  13. // 取得卓面的IShellFolder接口  
  14. SHGetDesktopFolder(&pFolder);  
  15. // 試圖在桌面上找到一個匹配  
  16. int iNumOfItems = SHEnumFolderContent(pFolder, NULL, 0, NULL);  
  17. int rc = SHEnumFolderContent(  
  18. pFolder, SearchText, reinterpret_cast<DWORD>(szName), &pidl);  

 

 

SHEnumFolderContent()是用戶定義的函數,他接受一個文件夾的PIDL和一個回調函數做爲輸入,而後枚舉這個文件夾的全部項,並傳遞這些項到這個函數做進一步的處理。後面咱們將進一步討論它。爲了理解它在這裏的用途,你只須要知道,若是沒有指定回調函數,它返回所找到的項目數:

int iNumOfItems = SHEnumFolderContent(pFolder, NULL, 0, NULL);

不然,它返回實際處理的項目數。這兩個值必然是不一樣的,由於回調函數能夠在自己選中的點上中止枚舉。例如SearchText()函數,當它找到了它正在尋找的名字時,SHEnumFolderContent()中止了。

    SHEnumFolderContent()函數開始搜索,檢查咱們在編輯框中鍵入的名字是否對應桌面下一個文件夾的顯示名。這就是上面代碼終止後,rc 和 iNumOfItems不等的狀況。若是它們相等,咱們就在‘個人計算機’節點上開始一個新的搜索:

 

[cpp]  view plain copy print ?
 
  1. // 若是沒找到,在‘個人計算機’上再試  
  2. if(rc == iNumOfItems)  
  3. {  
  4. // 綁定到‘個人計算機’  
  5. LPITEMIDLIST pidlMyComp;  
  6. SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidlMyComp);  
  7. pFolder->BindToObject(pidlMyComp, NULL, IID_IShellFolder,  
  8. reinterpret_cast<LPVOID*>(&pSubFolder));  
  9. //釋放桌面文件夾指針  
  10. pFolder->Release();  
  11. pMalloc->Free(pidlMyComp);  
  12. pFolder = pSubFolder;  
  13. //掃描‘個人計算機’  
  14. iNumOfItems = SHEnumFolderContent(pFolder, NULL, 0, NULL);  
  15. rc = SHEnumFolderContent(  
  16. pFolder, SearchText, reinterpret_cast<DWORD>(szName), &pidl);  

 

 

在重複調用SHEnumFolderContent()函數工做於‘個人計算機’文件夾上以前,咱們須要爲之得到IShellFolder接口指針,在這一點上咱們有的只是桌面的IShellFolder接口,然而,咱們能夠經過接口的BindToObject()方法得到想要的接口。這個操做使你能綁定到子文件夾的IShellFolder接口,所以,你能夠一樣使用這個方法做用於PIDL。

 

[cpp]  view plain copy print ?
 
  1. HRESULT IShellFolder::BindToObject(  
  2. LPCITEMIDLIST pidl, // 咱們想要的文件夾的PIDL  
  3. LPBC pbcReserved, // 保留,必須爲空NULL  
  4. REFIID riid, // 必須是IID_IShellFolder  
  5. LPVOID* ppvOut // 接收IShellFolder 的指針  
  6. );  

 

 

若是既沒在桌面上也沒在‘個人計算機’上找到指定的顯示名,則咱們所取得的是一個快捷方式,並且咱們確實不能肯定它的位置。儘管如此,也不要認爲這是一個系統限制—在文件夾上使用遞歸搜索來肯定名字的位置是頗有可能的。這個方法略述以下:

                   枚舉桌面文件夾的內容,就象上面所作的。

                   對每個找到的文件夾(不只是‘個人計算機’)重複這個搜索過程

然而,徹底遞歸的搜索可能致使試圖經過名字查找一個不惟一的文件夾—這是頗有可能的。固然可能有兩個文件夾具備相同的顯示名MyDir,一個在c:/,另外一個在d:/,上面的算法將老是中止在頭一個文件夾出現的地方。

         一個較好的方法是接受和分析全質量文件夾名,好比:

My Computer/ (c:)/Windows

Control Panel/Add New Hardware

這樣作僅僅須要不多的額外代碼來分析文件夾名,而且從搜索桌面上的第一個項開始,前一步完成到達文件夾的下一個項,等等。上面所看到的代碼能夠稍微加推廣,和封裝在一個循環中。

         回想一下,這確實與在文件系統中搜索沒有什麼不一樣,正好就是使用FindFirstFile()和 FindNextFile()來枚舉目錄的內容,只是使用了由文件夾對象的COM接口暴露的方法而已。

         在完成代碼以前,注意,鍵入的顯示名是一個完整的路徑名的狀況,如c:/。在輸出一個消息框以前處理這種狀況是有價值的—正象咱們須要轉換這個名字爲PIDL格式同樣,看一下什麼狀況發生了,若是沒有錯誤,則一個路徑名被接受了。

 

[cpp]  view plain copy print ?
 
  1. if(rc == iNumOfItems)  
  2. {  
  3. // 作最後的努力,它是一個路徑名  
  4. HRESULT hr = SHPathToPidlEx(szName, &pidl, pFolder);  
  5. if(FAILED(hr))  
  6. {  
  7. Msg("/"%s/" not found under Desktop or My Computer.", szName);  
  8. pMalloc->Free(pidl);  
  9. pFolder->Release();  
  10. // 調用輔助函數刷新UI  
  11. ClearUI(hDlg);  
  12. return;  
  13. }  
  14. }  
  15. }  

 

最後,若是函數在這一點以前沒有返回,咱們知道,已經有了一個可用於輸出文件夾圖標的PIDL,也就是說,在編輯框中有一個輸入串做爲上面源碼中的szName被引用。咱們就用那個名字標識文件夾對象並得到它的PIDL。如今,要枚舉這個文件夾的內容,咱們須要得到它的IShellFolder接口和把它傳遞給SHEnumFolderContent()函數。

    於是,‘搜索路徑’按鈕處理程序的結尾代碼有以下形式:

 

[cpp]  view plain copy print ?
 
  1. // 若是到達這裏,則:  
  2. // pidl 指向咱們須要綁定的文件夾以枚舉它的內容  
  3. // pFolder 指向pidl父文件夾的IShellFolder  
  4. // 綁定到咱們正在搜索的子文件夾  
  5. // pFolder 能夠指向桌面的或‘個人計算機’的IShellFolder  
  6. pFolder->BindToObject(pidl, NULL, IID_IShellFolder,  
  7. reinterpret_cast<LPVOID*>(&pSubFolder));  
  8. // 刷新UI (清空列表觀察和圖像列表等)  
  9. ClearUI(hDlg);  
  10. // 枚舉文件夾內容到列表觀察  
  11. HWND hwndListView = GetDlgItem(hDlg, IDC_LISTVIEW);  
  12. SHEnumFolderContent(pSubFolder, ShowFolderContent,  
  13. reinterpret_cast<DWORD>(hwndListView), NULL);  
  14. // 清理  
  15. pFolder->Release();  
  16. pSubFolder->Release();  
  17. pMalloc->Free(pidl);  
  18. pMalloc->Release();  
  19. return;  
  20. }  

 

轉換路徑名到PIDLs()

         查看上面的代碼,你可能已經注意到,我使用了SHPathToPidlEx()函數來轉換路徑名到PIDL。在這一章的開始,咱們開發了有一樣目的的SHPathToPidl()輔助函數—它使用IShellFolder接口的ParseDisplayName()方法。SHPathToPidl()函數的代碼濃縮到這一點上,它取得相對於桌面的PIDL—即,層次的根是

SHGetDesktopFolder(&pFolder);

pFolder->ParseDisplayName(NULL, NULL, wszPath, &n, ppidl, NULL);

不幸的是這個PIDL是相對於提供IShellFolder接口文件夾的,是桌面。新狀況是咱們須要相對於操做文件夾之父文件夾的PIDL。理由是,當咱們使用BindToObject()方法來得到一個子文件夾的IShellFolder時,要求傳遞一個PIDL,它是與咱們調用BindToObject()位於相同位置文件夾的PIDL。

    此後,咱們還須要在得到IShellFolder接口指針和ParseDisplayName()函數之間添加幾步,這幾步是要保證用於ParseDisplayName()調用的IShellFolder接口確實是咱們想要的文件夾的接口。

代碼以下:

 

[cpp]  view plain copy print ?
 
  1. HRESULT SHPathToPidlEx(  
  2. LPCTSTR szPath, LPITEMIDLIST* ppidl, LPSHELLFOLDER pFolder)  
  3. {  
  4. OLECHAR wszPath[MAX_PATH] = {0};  
  5. ULONG nCharsParsed = 0;  
  6. LPSHELLFOLDER pShellFolder = NULL;  
  7. BOOL bFreeOnExit = FALSE;  
  8. MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szPath, -1, wszPath, MAX_PATH);  
  9. // 默認使用桌面的IShellFolder  
  10. if(pFolder == NULL)  
  11. {  
  12. SHGetDesktopFolder(&pShellFolder);  
  13. bFreeOnExit = TRUE;  
  14. }  
  15. else  
  16. pShellFolder = pFolder;  
  17. HRESULT hr = pShellFolder->ParseDisplayName(  
  18. NULL, NULL, wszPath, &nCharsParsed, ppidl, NULL);  
  19. if(bFreeOnExit)  
  20. pShellFolder->Release();  
  21. return hr;  
  22. }  

 

這個函數比SHPathToPidl()更通常,並且它也要求傳遞PIDL相對的文件夾。若是傳遞一個NULL,而不是一個IShellFolder指針,則桌面的IShellFolder接口被使用,而後被釋放。在這個例子中調用轉換函數的代碼是:

HRESULT hr = SHPathToPidlEx(szName, &pidl, pFolder);

試着傳遞一個NULL而不是肯定的路徑名給pFolder,進行搜索,將會看到:不管如何,你將老是枚舉‘桌面’文件夾的內容。

清除用戶界面

         做爲SHEnumFolderContent()細節的一部分,在這個代碼中咱們所看到的最簡單的輔助函數是ClearUI():

 

[cpp]  view plain copy print ?
 
  1. void ClearUI(HWND hDlg)  
  2. {  
  3. HWND hwndListView = GetDlgItem(hDlg, IDC_LISTVIEW);  
  4. ListView_DeleteAllItems(hwndListView);  
  5. ImageList_RemoveAll(g_himl);  
  6. SetDlgItemText(hDlg, IDC_FOUND, __TEXT("0 item(s) found."));  
  7. }  

 

 

這正是重置應用的對話框,從觀察列表中刪除全部項,以及清空由SHEnumFolderContent()函數創建的圖像列表,最後一項任務由HIMAGELIST類型的全稱變量g_himl完成,它在WinMain()中被初始化爲0。

構建枚舉器函數

         關於枚舉給定文件夾內容這方面仍然有大量的問題須要考慮,好比,它是一個物理目錄仍是一個如‘打印機’的虛擬文件夾等。咱們所看到的源碼給出一個重要的函數SHEnumFolderContent(),這是一個負責查詢文件夾和逐個枚舉其內容的函數。

    有些文件夾其內容是文件的集合,還有些文件夾其可見內容能夠是單個文件的記錄或某種硬件設備。通常而言,僅僅是文件夾才確切地知道它的內容是什麼。對探測器或程序而言,沒有任何保險的方法不須要查詢文件夾就能枚舉它所包含的項。這並不奇怪,由於這種通信是基於COM接口的。

         在程序中,SHEnumFolderContent()詢問一個文件夾的內容,並傳輸它找到的每個項的名字到另外的函數作進一步的處理。你已經看到了這兩個函數:SearchText()和ShowFolderContent()。然而要理解它們的適看成用,首先要研究項目枚舉是怎樣發生的。

讀文件夾的內容

         關聯於‘搜索路徑’(和顯示PIDLs內容)按鈕的代碼,其目的在於讀出文件夾的內容。爲了容許枚舉其中的項,文件夾實現了IEnumIDList接口,這個接口暴露了四個函數:Next()Skip()Reset()和 Clone(),使之能夠在給定的集合中先後移動。它們的原型爲:

 

[cpp]  view plain copy print ?
 
  1. HRESULT IEnumIDList::Next(ULONG celt,  
  2. LPITEMIDLIST* rgelt,  
  3. ULONG* pceltFetched);  

 

頭一個變量是請求的項目數,第二個是一個PIDLs數組指針,第三個則是返回的實際拷貝的項目數。IEnumIDList接口自己負責分配保存PIDL數據的內存。

         要想了解指定文件夾的內容,須要設計一段代碼,要記住是經過獲取指向IEnumIDList接口的指針和IShellFolder接口所暴露的方法EnumObjects()實際完成這個操做的。這個方法的原型以下:

 

[cpp]  view plain copy print ?
 
  1. HRESULT IShellFolder::EnumObjects(HWND hwndOwner, //一個窗口Handle  
  2. DWORD grfFlags, //一個標誌集  
  3. LPENUMIDLIST* ppenumIDList //接收IEnumIDList的指針  
  4. );  

 

這個方法的第二個參數容許你規定枚舉項目的類型。它取以下定義的枚舉類型組合值:

 

[cpp]  view plain copy print ?
 
  1. typedef enum tagSHCONTF  
  2. {  
  3. SHCONTF_FOLDERS = 32,  
  4. SHCONTF_NONFOLDERS = 64,  
  5. SHCONTF_INCLUDEHIDDEN = 128,  
  6. } SHCONTF;  

 

就如助記名所述的同樣:你能夠決定枚舉文件夾、非文件夾對象,甚至是隱藏對象。

         之後在寫一個命名空間擴展得時候,詳細討論這些接口是很是必要的。如今,咱們建議你花點時間看一下VC++的幫助文件,以澄清這些方法名和原型。

 

[cpp]  view plain copy print ?
 
  1. LPENUMIDLIST pEnumIDList = NULL;  
  2. LPITEMIDLIST pItem = NULL;  
  3. ULONG ulFetched = 0;  
  4. pFolder->EnumObjects(  
  5. NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnumIDList);  
  6. while(pEnumIDList->Next(1, &pItem, &ulFetched) == NOERROR)  
  7. {  
  8. ...  
  9. }  

 

上面這段代碼描述了觸發枚舉文件夾項函數的過程。每次循環條件成立,pItem指向一個PIDL的單項。得到這個數據後有兩件事情須要處理:它的顯示名和可能的圖標。

取得項的顯示名

         即便有了PIDL,取得項目的顯示名也不是一件容易的事。儘管有IShellFolder::GetDisplayNameOf()函數,仍有一些工做要作,問題在於這個方法不提供正常的ANSI或Unicode格式。相反,它返回的是一個STRRET結構的指針。STRRET結構定義以下:

 

[cpp]  view plain copy print ?
 
  1. typedef struct _STRRET  
  2. {  
  3. UINT uType;  
  4. union{  
  5. LPWSTR pOleStr;  
  6. LPSTR pStr; // Unused  
  7. UINT uOffset;  
  8. char cStr[MAX_PATH];  
  9. } DUMMYUNIONNAME;  
  10. } STRRET, *LPSTRRET;  
  11. 正像你所看到的,這個結構用一個標誌表示格式,這個標誌說明了其後字符串的類型。串能夠是Unicode串(pOleStr),ANSI串(cStr),或甚至是一個串的地址偏移(uOffset)。也就是說無論初始串是什麼格式的,你都須要本身寫展開例程處理返回串。這裏給出一個樣例:  
  12. void StrretToString(LPITEMIDLIST pidl, LPSTRRET pStr, LPSTR pszBuf)  
  13. {  
  14. lstrcpy(pszBuf, "");  
  15. switch(pStr->uType)  
  16. {  
  17. case STRRET_WSTR: // Unicode 串  
  18. WideCharToMultiByte(  
  19. CP_ACP, 0, pStr->pOleStr, -1, pszBuf, MAX_PATH, NULL, NULL);  
  20. break;  
  21. case STRRET_OFFSET: // 地址偏移  
  22. lstrcpy(pszBuf, reinterpret_cast<LPSTR>(pidl) + pStr->uOffset);  
  23. break;  
  24. case STRRET_CSTR: // ANSI 串  
  25. lstrcpy(pszBuf, pStr->cStr);  
  26. break;  
  27. }  
  28. }  
  29. StrretToString()函數接收一個PIDL和一個STRRET結構,經由第三個變量返回一個LPSTR。附帶地上面代碼還顯示了uType的合法值。回到咱們主要討論的話題,GetDisplayNameOf()函數的原型是:  
  30. HRESULT IShellFolder::GetDisplayNameOf(LPCITEMIDLIST pidl,  
  31. DWORD uFlags,  
  32. LPSTRRET lpName);  
  33. 這裏uFlags標誌來自SHGNO枚舉類型:  
  34. typedef enum tagSHGDN  
  35. {  
  36. SHGDN_NORMAL = 0,  
  37. SHGDN_INFOLDER = 1,  
  38. SHGDN_INCLUDE_NONFILESYS = 0x2000,  
  39. SHGDN_FORADDRESSBAR = 0x4000,  
  40. SHGDN_FORPARSING = 0x8000,  
  41. } SHGNO;  

 

 

資料中對這些標誌的描述是足夠清晰的。所以,你能夠構建你所指望的具備最終行爲的函數。然而,不管我怎樣設置標誌,全部樣例程序都是以一樣的方式工做。坦白地說,我也不知道哪兒除了問題。因此我建議老是使用0做爲這個參數的值。

 

[cpp]  view plain copy print ?
 
  1. STRRET sName;  
  2. CHAR szBuf[MAX_PATH] = {0};  
  3. pFolder->GetDisplayNameOf(pItem, 0, &sName);  
  4. StrretToString(pItem, &sName, szBuf);  

 

這個代碼段以可閱讀格式顯示項目名。再次提醒注意,對於文件型文件夾以及FontsFavoritesPrintersControl Panel等特殊文件夾這是正確的。也就是說,咱們可以列出全部‘控制面板’中的小程序。

         自己,咱們沒有用到STRRET結構,這要感謝StrretToString()函數提供的幫助,它固然也包含在Shell庫函數中。

讀取項目的圖標

    已開始接觸Shell編程,覺得很是艱鉅,然而,當你從初始的頭三四個月中挺過來以後,你就開始有機會接觸高級項目問題的答案了。要說明這一點,你認爲怎樣才能取得一個項的圖標?回答是,你必須詢問提供它的文件夾。IShellFolder::GetUIObjectOf()方法返回全部你可能須要處理用戶界面和文件對象的接口。

 

[cpp]  view plain copy print ?
 
  1. HRESULT IShellFolder::GetUIObjectOf(  
  2. HWND hwndOwner, // 窗口Handle  
  3. UINT cidl, // 下一個參數中的元素數  
  4. LPCITEMIDLIST* apidl, // 指向一個PIDLs數組的指針  
  5. REFIID riid, // 要求的接口ID  
  6. UINT* prgfInOut, // 保留(必須爲NULL)  
  7. LPVOID* ppvOut // 接收接口的指針  
  8. );  

 

對這個聲明咱們感興趣的是能夠請求必定數量的不一樣接口指針,它們都能影響UI顯示。例如,請求IContextMenu,以得到元素的關聯菜單HMENU的Handle。在咱們的任務中,請求的是IExtractIcon接口,以便查找圖標(在第十六章中將看到更多關於GetUIObjectOf()的解釋)。

pFolder->GetUIObjectOf(NULL, 1, const_cast<LPCITEMIDLIST*>(&pItem),

IID_IExtractIcon, NULL, reinterpret_cast<LPVOID*>(&pExtractIcon));

IExtractIcon接口有兩個新方法:GetIconLocation()和Extract()。頭一個使你能知道圖標的索引和位置,第二個則返回一個HICON 型的Handle。在客戶端調用GetIconLocation()函數時,它返回包含圖標的文件名,以及在這個文件的資源中的一個從零開始的圖標索引。

 

[cpp]  view plain copy print ?
 
  1. HRESULT IExtractIcon::GetIconLocation(UINT uFlags,  
  2. LPSTR szIconFile,  
  3. INT cchMax,  
  4. LPINT piIndex,  
  5. UINT* pwFlags);  

 

Extract()函數依次從指定文件中抽取給定的圖標,和返回它的HICON。這個方法幾乎與API函數ExtractIconEx()是同樣的。

 

[cpp]  view plain copy print ?
 
  1. HRESULT IExtractIcon::Extract(LPCSTR pszFile,  
  2. UINT nIconIndex,  
  3. HICON* phiconLarge,  
  4. HICON* phiconSmall,  
  5. UINT nIconSize);  

 

對這些函數而言資料顯得有點冗長,例如,即便你不關心pwFlags的內容,你也須要知道它不能爲NULL。相似地,即便你僅僅須要大圖標,你仍然必須爲小圖標傳遞一個有效的非零HICON。下面測試一個例子,說明怎樣調用它:

 

[cpp]  view plain copy print ?
 
  1. pExtractIcon->GetIconLocation(0, szIconFile, MAX_PATH, &iIconIndex, &u);  
  2. pExtractIcon->Extract(szIconFile, iIconIndex, &hIcon, &hIconSm, MAKELONG(32, 16));  
  3. pExtractIcon->Release();  

 

 

在開發這個實例代碼時,我得到了另外一個有趣的結果,坦白地講,這超出了個人想象。在有些狀況下Extract()函數返回的Handle是NULL,即便在圖標的位置和索引都是正確的狀況下,也是如此。奇怪,使用相同的參數調用ExtractIconEx()函數工做無缺。固然,工做環境是直接的:

if(hIcon == NULL)

ExtractIconEx(szIconFile, iIconIndex, &hIcon, NULL, 1);

在這一點上,最後要作的工做就是須要創建一個新的Shell函數,它接收IShellFolder指針,在它的項上循環,爲每一個項喚醒回調函數。就像不少其餘稱爲‘枚舉’的函數同樣,咱們的SHEnumFolderContent()函數將提供一個用戶定義的緩衝(dwData)發送程序級的變量到回調函數。進一步,若是回調函數返回FALSE,則函數將中止工做。這裏是它的原型:

 

[cpp]  view plain copy print ?
 
  1. int SHEnumFolderContent(LPSHELLFOLDER pFolder,  
  2. FOLDERCONTENTPROC pfn, DWORD dwData, LPITEMIDLIST* ppidl);  

 

 

其中FOLDERCONTENTPROC是一個用戶定義的函數指針,其聲明是:

typedef BOOL (CALLBACK *FOLDERCONTENTPROC)(LPCSTR, HICON, DWORD);

第一個變量是元素顯示名,然後是一個圖標Handle。而後是用戶定義的緩衝。像已經提到的那樣,函數返回FALSE來中斷枚舉,TRUE則繼續。

         SHEnumFolderContent()的最後一個參數是PIDL。這並不嚴格地必要,只是有時須要(如在咱們的例子中),理解這個最後傳遞的參數PIDL是有幫助的。若是這個變量是NULL,則它被忽略。下面是SHEnumFolderContent()函數的源代碼:

 

[cpp]  view plain copy print ?
 
  1. int SHEnumFolderContent(LPSHELLFOLDER pFolder,  
  2. FOLDERCONTENTPROC pfn, DWORD dwData, LPITEMIDLIST* ppidl)  
  3. {  
  4. int iNumOfItems = 0;  
  5. // 枚舉內容  
  6. LPENUMIDLIST pEnumIDList = NULL;  
  7. pFolder->EnumObjects(  
  8. NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnumIDList);  
  9. ULONG ulFetched = 0;  
  10. LPITEMIDLIST pItem = NULL;  
  11. while(NOERROR == pEnumIDList->Next(1, &pItem, &ulFetched))  
  12. {  
  13. STRRET sName;  
  14. TCHAR szBuf[MAX_PATH] = {0};  
  15. pFolder->GetDisplayNameOf(pItem, 0, &sName);  
  16. StrretToString(pItem, &sName, szBuf);  
  17. // 喚醒回調函數  
  18. if(pfn)  
  19. {  
  20. // 獲取圖標  
  21. UINT u = 0;  
  22. int iIconIndex = 0;  
  23. HICON hIcon = NULL;  
  24. HICON hIconSm = NULL;  
  25. TCHAR szIconFile[MAX_PATH] = {0};  
  26. LPEXTRACTICON pExtractIcon = NULL;  
  27. pFolder->GetUIObjectOf(NULL, 1, const_cast<LPCITEMIDLIST*>(&pItem),  
  28. IID_IExtractIcon, NULL,  
  29. reinterpret_cast<LPVOID*>(&pExtractIcon));  
  30. pExtractIcon->GetIconLocation(0, szIconFile, MAX_PATH,  
  31. &iIconIndex, &u);  
  32. pExtractIcon->Extract(szIconFile, iIconIndex, &hIcon,  
  33. &hIconSm, MAKELONG(32, 16));  
  34. pExtractIcon->Release();  
  35. if(hIcon == NULL)  
  36. ExtractIconEx(szIconFile, iIconIndex, &hIcon, NULL, 1);  
  37. if(!pfn(szBuf, hIcon, dwData))  
  38. {  
  39. // 返回當前的PIDL  
  40. if(ppidl != NULL)  
  41. *ppidl = pItem;  
  42. break;  
  43. }  
  44. }  
  45. ++iNumOfItems;  
  46. }  
  47. return iNumOfItems;  
  48. }  

 

 

回調函數

    典型地,回調函數用於實現某些項的採集任務。如此,SHEnumFolderContent()調用這樣的函數採集各個文件夾項。SearchText()函數僅僅報告是否傳遞過來的兩個串是相等的。

 

[cpp]  view plain copy print ?
 
  1. BOOL CALLBACK SearchText(LPCSTR pszItem, HICON hIcon, DWORD dwData)  
  2. {  
  3. return static_cast<BOOL>(lstrcmpi(pszItem, reinterpret_cast<LPCSTR>(dwData)));  
  4. }  

 

 

ShowFolderContent()函數用於構建一個傳遞過來圖標的圖像列表,插入圖標到提供的列表觀察中:

 

[cpp]  view plain copy print ?
 
  1. BOOL CALLBACK ShowFolderContent(LPCSTR pszItem, HICON hIcon, DWORD dwData)  
  2. {  
  3. //創建圖像列表  
  4. int iIconWidth = GetSystemMetrics(SM_CXICON);  
  5. int iIconHeight = GetSystemMetrics(SM_CYICON);  
  6. if(g_himl == NULL)  
  7. g_himl = ImageList_Create(iIconWidth, iIconHeight, ILC_MASK, 1, 0);  
  8. int iIconPos = ImageList_AddIcon(g_himl, hIcon);  
  9. HWND hwndListView = reinterpret_cast<HWND>(dwData);  
  10. ListView_SetImageList(hwndListView, g_himl, LVSIL_NORMAL);  
  11. LV_ITEM lvi;  
  12. ZeroMemory(&lvi, sizeof(LV_ITEM));  
  13. lvi.mask = LVIF_TEXT | LVIF_IMAGE;  
  14. lvi.pszText = const_cast<LPSTR>(pszItem);  
  15. lvi.cchTextMax = lstrlen(pszItem);  
  16. lvi.iImage = iIconPos;  
  17. ListView_InsertItem(hwndListView, &lvi);  
  18. //更新計數  
  19. TCHAR s[MAX_PATH] = {0};  
  20. wsprintf(s, "%d item(s) found.", ListView_GetItemCount(hwndListView));  
  21. SetDlgItemText(GetParent(hwndListView), IDC_FOUND, s);  
  22. return TRUE;  
  23. }  

 

 

示例程序

必定要保證,主程序代碼中包含#includes  shlobj.h 和resource.h,下面圖中顯示使用這一階段開發的實例程序能夠作的操做。鍵入‘打印機’,你能充填這個列表觀察,就像標準的文件夾窗口同樣:

                   

 

 

再有,指定一個路徑名,能夠看到文件和文件夾,正象在探測器中所看到的那樣:

                      

 

 

         回想一下,若是想要獲得任何驅動器根目錄的內容時,你必須包括一個最後的反斜槓‘/’,例如C:/工做正常,而C: 則產生這個結果:

                           

 

 

經過PIDL搜索

         適當地使用咱們全部的輔助函數,寫一個‘顯示PIDLs內容’按鈕的處理器不是太困難的事。與這個按鈕相關的combo框由名字和一些特殊文件夾的ID初始化,這個過程執行與SHBrowse例子中相同的代碼。此處由函數在點擊‘顯示PIDLs內容’按鈕時執行,並充填列表觀察:

 

[cpp]  view plain copy print ?
 
  1. void DoEnumeratePidl(HWND hDlg)  
  2. {  
  3. LPITEMIDLIST pidl = NULL;  
  4. // 取得特殊文件夾和它的PIDL  
  5. HWND hwndCbo = GetDlgItem(hDlg, IDC_SPECIAL);  
  6. int i = ComboBox_GetCurSel(hwndCbo);  
  7. int nFolder = ComboBox_GetItemData(hwndCbo, i);  
  8. SHGetSpecialFolderLocation(NULL, nFolder, &pidl);  
  9. //取得IShellFolder接口  
  10. LPSHELLFOLDER pFolder = NULL;  
  11. SHGetDesktopFolder(&pFolder);  
  12. // 綁定到子文件夾  
  13. LPSHELLFOLDER pSubFolder = NULL;  
  14. pFolder->BindToObject(pidl, NULL, IID_IShellFolder,  
  15. reinterpret_cast<LPVOID*>(&pSubFolder));  
  16. pFolder->Release();  
  17. pFolder = pSubFolder;  
  18. //清除程序的UI  
  19. ClearUI(hDlg);  
  20. //枚舉內容  
  21. HWND hwndListView = GetDlgItem(hDlg, IDC_LISTVIEW);  
  22. SHEnumFolderContent(pFolder, ShowFolderContent,  
  23. reinterpret_cast<DWORD>(hwndListView), NULL);  
  24. // 清理  
  25. LPMALLOC pMalloc = NULL;  
  26. SHGetMalloc(&pMalloc);  
  27. pMalloc->Free(pidl);  
  28. pMalloc->Release();  
  29. pFolder->Release();  
  30. }  

 

 

這個函數從得到特殊文件夾的ID開始,這個ID來自combo框的選擇,然後調用SHGetSpecialFolderLocation()函數得到文件夾的PIDL,再從這個PIDL得到IShellFolder接口,而後傳遞給SHEnumFolderContent()函數。圖中顯示了這個應用怎樣枚舉‘控制面板’的小程序:

                     

 

 

特殊文件夾

         咱們首先在第二章中看到了特殊文件夾和它們的基本概念,其中有三種基本類型。幾乎全部的特殊文件夾都有對應的目錄,可是這些與普通的文件型文件夾和客戶文件夾是徹底不一樣的。第三種類型是由沒有目錄的文件夾(虛擬文件夾)組成。

虛擬文件夾感受上象一個文件夾,可是它們的位置和內容沒有文件和目錄概念的映射。‘控制面板’,‘打印機’,‘網上鄰居’,‘個人計算機’都是虛擬文件夾的例子。例如‘控制面板’能夠包含全部安裝了的小程序。

         除了外觀,沒有稱爲‘控制面板’的物理目錄能夠包含與之相關的任何東西,好比‘添加新硬件’或‘Modems’。這個文件夾所列出的全部圖標都來自系統目錄下的.cpl文件。他們由命名空間擴展彙集和表示爲虛擬文件夾。

系統對特殊文件夾的支持

         Windows API定義了必定數量的特殊文件夾,而且有一堆函數做用其上。這些例程經過如ID同樣的數字標識每個特殊文件夾,可是對PIDLsCLSIDs則沒有什麼可作的。

         ID定義在shlobj.h中,而且都有至關奇怪的符號名:都以CSIDL_開始。下表列出了一些可用的特殊文件夾:

 

文件夾ID

虛擬的

描述

CSIDL_DESKTOP

Yes

桌面

CSIDL_DRIVES

Yes

個人計算機

CSIDL_BITBUCKET

Yes

回收站

CSIDL_CONTROLS

Yes

控制面板

CSIDL_NETWORK

Yes

網上鄰居

CSIDL_INTERNET

Yes

Shell上的IE探測器節點(版本4.71以上)

CSIDL_PRINTERS

Yes

打印機

CSIDL_DESKTOPDIRECTORY

 

含有所有桌面快捷方式的目錄

CSIDL_FAVORITES

 

Favorite文件夾的快捷方式

CSIDL_FONTS

 

安裝的字體

CSIDL_NETHOOD

 

網絡域的引用

CSIDL_PRINTHOOD

 

打印機的引用

CSIDL_PERSONAL

 

私有文件的快捷方式

CSIDL_PROGRAMS

 

‘程序’菜單的快捷方式

CSIDL_RECENT

 

最近使用文檔的快捷方式

CSIDL_SENDTO

 

‘發送到’菜單項的快捷方式

CSIDL_STARTMENU

 

‘開始’菜單中用戶定義的項

CSIDL_STARTUP

 

啓動時運行的程序的快捷方式

CSIDL_COOKIES

 

Cookies

CSIDL_TEMPLATES

 

文檔模版的快捷方式

CSIDL_HISTORY

 

訪問過的Web頁面的快捷方式

CSIDL_INTERNET_CACHE

 

IE的臨時Internet文件

CSIDL_APPDATA

 

一個應用專有數據的文件夾

CSIDL_ALTSTARTUP

 

非本地‘啓動’組

資料中還提到了另一些標號爲CSIDL_COMMON_XXX的文件夾,它們是:

CSIDL_COMMON_STARTUP            CSIDL_COMMON_STARTMENU

CSIDL_COMMON_PROGRAMS           CSIDL_COMMON_FAVORITES

CSIDL_COMMON_DESKTOPDIRECTORY   CSIDL_COMMON_ALTSTARTUP

除了它們指向一個任何用戶均可看到的物理文件夾之外,這些文件夾與不包含COMMON的文件夾相同。雖然這一點在資料中沒有顯式提到,這些文件夾彷佛感受僅在WindowsNT下出現。

獲取文件夾路徑

         非虛擬文件夾在機器的某個地方有一個路徑,你能夠經過調用SHGetSpecialFolderPath() API函數得到一個特殊文件夾的路徑。特殊文件夾與它的路徑之間的鏈接存儲在註冊表中,註冊鍵入下:

HKEY_CURRENT_USER

/Software

/Microsoft

/Windows

/CurrentVersion

/Explorer

/Shell Folders

在HKEY_LOCAL_MACHINE下相同的鍵存儲了全部可用的COMMON文件夾,可是,在Windows95和Windoes98下並非全部COMMON文件夾都有路徑。事實上,僅僅CSIDL_COMMON_DESKTOPDIRECTORY和CSIDL_COMMON_STARTUP有路徑存儲。假設C:/Windows是系統目錄,所列出的路徑在C:/Windows/All Users folder下。然而,在Windows95/Windows98下SHGetSpecialFolderPath()函數不能爲其返回任何值。反之,在WindowsNT下使用一樣的函數,則返回正確的路徑。

函數

         咱們能夠經過觀察曾經使用過的函數SHGetSpecialFolderLocation()來走進函數SHGetSpecialFolderPath()函數。這個函數恢復指定的特殊文件夾的PIDL,它有下面的原型:

 

[cpp]  view plain copy print ?
 
  1. HRESULT SHGetSpecialFolderLocation(HWND hwndOwner,  
  2. int nFolder,  
  3. LPITEMIDLIST* ppidl);  

 

 

hwndOwner是任何彈出顯示窗口的父窗口,nFolder是特殊文件夾的標識符,能夠是上表列出的常量之一,而ppidl則是指向包含這個文件夾的PIDL緩衝的指針。SHGetSpecialFolderPath()函數,試圖恢復給定文件夾的路徑,與上函數很是類似:

 

[cpp]  view plain copy print ?
 
  1. HRESULT SHGetSpecialFolderPath(HWND hwndOwner,  
  2. LPTSTR lpszPath,  
  3. int nFolder,  
  4. BOOL fCreate);  

 

 

lpszPath將包含路徑名,而fCreate是一個邏輯值,表示若是文件夾不存在,是否須要創建。固然,在這種狀況下,你不能指定一個虛擬文件夾的ID。注意,與SHGetSpecialFolderLocation()函數不一樣,SHGetSpecialFolderPath()僅僅支持Shell 4.71以上版本。

文件夾設置

         IE4.0和活動桌面極大地增長了系統文件夾的設置量。‘文件夾選項’對話框有完善的複選框來肯定文件夾的外觀,使其具備咱們但願的形式:

                        

 

 

這個對話框是Windows系統上人人都使用的對話框,它使你能設置查看系統文件和隱藏文件。有些設置(不是所有)能夠經過編程讀出,天然這些也僅能在Shell 4.71以上版本上有效。

         VC++的資料中能夠找到每個設置的詳細說明,在這裏所能找到的僅僅是一個例子程序。

SHGetSettings()函數

         實際上,使用SHGetSettings()函數是至關簡單的,它僅須要兩個變量:

void SHGetSettings(LPSHELLFLAGSTATE lpsfs, DWORD dwMask);

SHELLFLAGSTATE是一個很是簡潔的結構:

 

[cpp]  view plain copy print ?
 
  1. typedef struct  
  2. {  
  3. BOOL fShowAllObjects : 1;  
  4. BOOL fShowExtensions : 1;  
  5. BOOL fNoConfirmRecycle : 1;  
  6. BOOL fShowSysFiles : 1;  
  7. BOOL fShowCompColor : 1;  
  8. BOOL fDoubleClickInWebView : 1;  
  9. BOOL fDesktopHTML : 1;  
  10. BOOL fWin95Classic : 1;  
  11. BOOL fDontPrettyPath : 1;  
  12. BOOL fShowAttribCol : 1;  
  13. BOOL fMapNetDrvBtn : 1;  
  14. BOOL fShowInfoTip : 1;  
  15. BOOL fHideIcons : 1;  
  16. UINT fRestFlags : 3;  
  17. } SHELLFLAGSTATE, *LPSHELLFLAGSTATE;  

 

 

dwMask參數是二進制屏蔽位—對於結構中每個感興趣的字段和須要函數恢復的字段,都必須設置適當的屏蔽位。可能的值是:

 

字段

屏蔽位

在文件夾選擇對話框中的設置

fShowAllObjects

SSF_SHOWALLOBJECTS

顯示全部文件

fShowExtensions

SSF_SHOWEXTENSIONS

隱藏已知文件類型的文件擴展

fNoConfirmRecycle

SSF_NOCONFIRMRECYCLE

None

fShowSysFiles

SSF_SHOWSYSFILES

不顯示隱藏文件

fShowCompColor

SSF_SHOWCOMPCOLOR

None

fDoubleClickInWebView

SSF_DOUBLECLICKINWEBVIEW

在‘通常|客戶設置’對話框上‘雙擊打開項’選擇

fWin95Classic

SSF_WIN95CLASSIC

在‘通常’頁上‘典型風格’選擇

fDontPrettyPath

SSF_DONTPRETTYPATH

所有容許大寫名字

fMapNetDrvBtn

SSF_MAPNETDRVBUTTON

在工具條中顯示‘網絡驅動器映射’按鈕

fShowAttribCol

SSF_SHOWATTRIBCOL

在‘細節觀察’中顯示文件屬性

fShowInfoTip

SSF_SHOWINFOTIP

對文件夾和桌面項顯示彈出的描述

fDesktopHTML

SSF_DESKTOPHTML

在活動桌面關聯菜單上設置‘Web頁面觀察’

fHideIcons

SSF_HIDEICONS

當桌面做爲Web頁面時,隱藏圖標

不少資料都說明fHideIcons是沒用的,事實上它僅僅說明當桌面設置成Web模式時,是否桌面上的圖標應該被顯示。如今讓咱們查看一些應用,以便探討能夠從這些標誌中得到的信息。

觀察文件擴展

         頭一個用途是程序員是否想要在用戶的應用界面中顯示文件擴展。若是應用程序須要在任何狀況下顯示文件名,你就應該依據這個標誌的狀態使用戶可以選擇肯定是否顯示擴展名。

使桌面更具活力

         fHideIcons標誌使你在觀察模式設置爲‘Web頁面’時知道桌面上的圖標是否能夠看到。而fDesktopHTML標誌從另外一方面告訴你是否桌面使用了一個HTML頁做爲它的背景。若是桌面是在Web模式中,而且圖標不可見,則你就不能在桌面上創建新的快捷方式。

         若是咱們僅僅能設置這些位而不能得到它們的狀態的話,組合使用fDesktopHTML和fHideIcons是很是有用的。考慮下面的狀況:有不少方法清除桌面以禁止公共計算機用戶而不是你瀏覽和運行應用程序。而使用fDesktopHTML和fHideIcons的組合給出了一種新方法。首先,它容許你設置標誌顯示HTML頁面做爲桌面的背景,其次隱藏全部桌面上的圖標。使用這種方法,你能夠把Windows桌面(和機器)變成一個專門運行單一HTML應用的服務器。誠然,任務條還在,可是,你能夠經過取得其HWND,而後使用SW_HIDE標誌調用ShowWindow()函數來隱藏它:

 

[cpp]  view plain copy print ?
 
  1. // 任務條是一個窗口類'Shell_TrayWnd'  
  2. HWND hwnd = FindWindow("Shell_TrayWnd", NULL);  
  3. if(IsWindow(hwnd))  
  4. ShowWindow(hwnd, SW_HIDE);  

 

 

點擊列表觀察

         Shell 4.71以上版的衆多文件夾的設置中,有可能設置文件夾的行爲使其在被選中時變成下劃線形式,和只要一次點擊即可打開它們。你能夠經過‘文件夾選項’對話框的‘通常’頁設置這些選擇。有趣的是,這些風格對於通用控件庫版本4.70的列表觀察也有效。因此你能夠根據fDoubleClickInWebView標誌來修改列表觀察的活動形式和鼠標跟蹤能力。如此,須要考慮下列關係:

 

[cpp]  view plain copy print ?
 
  1. LVS_EX_ONECLICKACTIVATE (4.70)  
  2. LVS_EX_TWOCLICKACTIVATE (4.70)  
  3. LVS_EX_UNDERLINECOLD (4.71)  
  4. LVS_EX_UNDERLINEHOT (4.71)  

 

 

這個列表中的版本號指的是通用控件庫的版本號,不是Shell的版本號。版本4.70的comctl32.dll與IE4.0一同發佈(不管活動桌面是否安裝)。4.71與IE4.01。

         設置擴展風格的列表觀察,須要使用ListView_SetExtendedListViewStyle()函數,它是一個圍繞LVM_SETEXTENDEDLISTVIEWSTYLE消息創建的宏。上面列表中的頭兩種風格的意義是直接的,其它的涉及到熱點項(一個術語,用於描述鼠標正在通過的項)。LVS_EX_UNDERLINECOLD引發非熱點項下劃線,而LVS_EX_UNDERLINEHOT則僅僅熱點項下劃線。

刪除操做的確認

         fNoConfirmRecycle標誌指示在刪除文件以前是否顯示確認對話框。能夠想象,這僅適用於刪除到‘回收站’和Shell操做的場合。然而,即便你沒有使用Shell函數,如SHFileOperation(),來刪除文件,若是用戶但願有這樣的詢問時,是否不能很好地給出確認提示呢?閱讀一下fNoConfirmRecycle能夠向這個方向前進一大步,進而使之成爲可能。

示例程序

    程序界面,做爲這一章最後一個例子,以下圖所示,可能你已經猜到了什麼是咱們要用於創建的程序框架。

                                   

               

這個例子的代碼是十分容易的:只須要爲‘取得設置’按鈕加一個處理器便可,它將引發當前Shell的選項設置被讀出。下面的源碼產生了在圖中所看到的結果。總之,必定要記住在源文件頂部包含#include shlobj.h 和 resource.h。

 

[cpp]  view plain copy print ?
 
  1. void OnSettings(HWND hDlg)  
  2. {  
  3. SetDlgItemText(hDlg, IDC_SETTINGS, "");  
  4. SHELLFLAGSTATE sfs;  
  5. SHGetSettings(&sfs, SSF_DESKTOPHTML | SSF_SHOWALLOBJECTS |  
  6. SSF_MAPNETDRVBUTTON | SSF_SHOWATTRIBCOL | SSF_SHOWEXTENSIONS);  
  7. TCHAR szBuf[MAX_PATH] = {0};  
  8. if(sfs.fDesktopHTML)  
  9. lstrcat(szBuf, __TEXT("Active Desktop - View as Web page is active/r/n"));  
  10. if(sfs.fMapNetDrvBtn)  
  11. lstrcat(szBuf, __TEXT("Network buttons on the toolbar/r/n"));  
  12. if(sfs.fShowAllObjects)  
  13. lstrcat(szBuf, __TEXT("Shows all files/r/n"));  
  14. if(sfs.fShowAttribCol)  
  15. lstrcat(szBuf, __TEXT("Shows attributes in Detail view/r/n"));  
  16. if(sfs.fShowExtensions)  
  17. lstrcat(szBuf, __TEXT("Shows extensions for known file types/r/n"));     
  18. SetDlgItemText(hDlg, IDC_SETTINGS, szBuf);  
  19. }  

 

 

參數設置

         閱讀了這些參數的設置,對處理各類狀況確實有很多幫助,可是更有興趣的應該是編程地設置這些特徵。不幸地是,到目前爲止只有SHSetSettings()例程出現,如今,咱們要說明的是有大量的能夠達到這個目標的方法可用而不須要微軟的幫助。

參數存儲在哪裏

         正如你已經大體猜到的那樣,全部可使用SHGetSettings()讀到的參數都存儲在註冊表的某個地方。也即,能夠由一個相對安全的方法來編程地設置這些參數。

    在向下進行以前,咱們要強調一個重點。在官方資料缺少的狀況下,微軟在將來的操做系統版本中是能夠自由改變註冊表鍵的用途的,這就間接地影響到你的代碼。在編寫代碼時,咱們使用的技術在版本4.71下是好用的。

         打開註冊表,查看下面的鍵:

HKEY_CURRENT_USER

/Software

/Microsoft

/Windows

/CurrentVersion

/Explorer

/Advanced

 

           

 

彷佛咱們已經找到了要找的東西。修改這些註冊表項是足夠簡單的,對嗎?很不幸,不是這樣的—你很快就能注意到,這個值列表缺乏了條目數。尤爲是‘Web觀察’設置。

         回想一下測試反向工程註冊表設置的黃金定律:老是比較HKEY_CURRENT_USER和 HKEY_LOCAL_MACHINE下相同鍵的內容。這裏就是咱們要找的:

            

            

正如所見,有一個完整的層次結構複製了與‘文件夾選項’對話框相同的樹結構。主節點是一個「group」類型的節點,有本身的Bitmap,和顯示名。結構的葉含有一個屬性集,其中突出的有兩個值:RegPath和 HKeyRoot:

            

       

這就是說,在子樹中每個條目都指向註冊表中的另外一個鍵,實際值就存儲在那兒,其路徑是HKeyRoot/RegPath/ValueName。葉特徵肯定了顯示文字,選項類型(複選框或收音按鈕),選擇值(選中或沒選中),默認值,甚至文件名和有幫助的主題ID等。

         給出了這些以後,處理客戶的SHSetSettings()函數就只是簡單地讀寫註冊表數據操做了。

 

附加客戶選項到標準對話框

         由於在‘文件夾選項’對話框的層次結構和註冊表子樹佈局之間有良好的對應,咱們能當即猜想到添加一個新鍵到註冊表應該在標準對話框中產生一個新的客戶選項。爲了證實這一點,僅須要作一件事情:加一個新鍵到註冊表子樹。

         我在Folder下定義了一個新鍵,稱爲MySetting。而後定義全部在其它葉上看到的值:

        

 

 

保存這個改變到註冊表以後,期待地打開‘文件夾選項’對話框,沒有任何新東西出現。事實上,對此有一個顯而易見的緣由:對話框僅在可以讀出所存儲的值時才添加新項。正如早先提到過的那樣,這個值存儲在註冊表的另外一個位置—是由HKeyRoot,RegPath 和ValueName指向的。剩下的就是在下面的鍵上創建一個稱爲MySetting的新值:

HKEY_CURRENT_USER

/Software

/Microsoft

/Windows

/CurrentVersion

/Explorer

/Advanced

還應該設置所指望選項的默認值。在保存改變和響應‘文件夾選項’對話框後,新的設置就出現了,以下圖所示:

                                                

                     

何時客戶選項是有用的

         在‘文件夾選項’對話框中添加新的客戶選項不是爲了向人炫耀—也不是一項容許用戶客戶化你的程序的便利方法。咱們並不建議你使用這個對話框來設置一個應用的全部設置。可是,考慮使用選項來解決用戶界面和文件夾的問題是有價值的。能更好地探索這些特徵的模塊應該在命名空間擴展應用中。

使用註冊表路徑的選擇徹底在於你本身,可是,應該認識到這一點,把你本身的設置存儲到遠離標準設置的地方,好的選擇是使用應用特殊的註冊鍵。

小結

         文件夾是一個普遍的題目,這一章一直在努力給出詳細解釋。你已經看到怎樣瀏覽特殊文件安夾和怎樣與它們一道工做。枚舉它們的內容和設置它們的參數。特別,在這一章中還揭示了:

         怎樣更好地使用SHBrowseForFolder()

    怎樣枚舉任何文件夾的內容

    處理特殊系統文件夾的函數

    那些文件夾設置是可讀的,怎樣編程設置它們

爲此,咱們構建了潛在有用的函數來擴展API所提供的工具。例如SHEnumFolderContent()和SHPathToPidlEx()輔助例程。此外,咱們還揭示了Shell怎樣存儲文件夾的設置,和給出了添加新選項到標準的‘文件夾選項’對話框的方法。

相關文章
相關標籤/搜索