即 Windows 下的命名空間擴展
命名空間擴展
一種允許將外部自定義的信息集成到windows資源管理器,以用戶自定義顯示方式來處理數據信息,資源管理器提供必要的控制、交互接口、GUI實現。
實現命名空間擴展
- 一個文件件管理器對象,用於請求其其需要的信息
- 一個顯示文件夾內容的視圖窗口
- 一個枚舉文件夾內的項目的枚舉對象
- 一個表征文件夾對象的標識ID
- 一系列可定制用戶GUI的訪問函數
一般流程
命名空間擴展和shell擴展幾乎一樣,都要被注冊安裝,被檢測、被調用,都屬於進程里COM服務實現,實現一系列的接口來定制Shell;不同點:前者主要是針對Explorer的添加一個虛擬文件夾,后者則是限制於文件類型。
- 資源管理器掃描注冊表來安裝組件並建立和他們的連接通信,無論是你自己實現或是系統自帶的;
- 資源管理器探測到當前存在的命名空間擴展,它便加載該COM服務對象(調用IShellFolder的一些接口實現);
- IShellFolder作為一個文件夾管理器,並提供給資源管理器需要的任何東西,也即是充當資源管理器和擴展的其他部分之間的代理對象;
- 當Explorer需要顯示一個視圖內容時,Explorer將請求IShellFolder一個視圖對象,同樣的,當顯示樹形視圖的節點時,其將請求枚舉文件夾對象和子文件夾的屬性內容;這些幾乎均通過IShellFolder接口來完成;
- 當命名空間擴展被加載后,資源管理器也會給其一個機會來更新用戶界面。所有的可能擴展對象感興趣的事件都將會被通告調用到相應的函數實現。也就是說,實現一個命名空間擴展也就是要准備響應Explorer的各種請求實現,而響應實現則通過具體的COM接口來實現(一些必要的接口集合實現)。
實現命名空間擴展必要的接口
- IShellFolder
- IPersistFolder
- IEnumIDList
- IShellView
前兩者可認為為文件管理器Folder Manager,IEnumIDList為枚舉器,IShellView則為視圖窗口。
另外可選的接口:IContextMenu和IExtractIcon用來請求自定義的上下文菜單和單個Item項的圖標。
對於一些接口實現,若自己不想實現則可交給Explorer知道的,可通過返回E_NOTIMPL的錯誤碼值。
PIDL:一個文件夾下的一個項目的標識符,其將貫穿於整個Shell的命名空間;其表示一個文件夾類型,對於自定義的文件夾,則也需要提供自定義的PIDL。
具體流程
獲取一個命名空間擴展顯示到explorer的流程
- Explorer檢測到命名空間擴展通過注冊表結點獲取它的CLSID;
- Explorer創建該命名擴展對象實例,並請求IShellFolder接口;
- Explorer請求該對象(實現了IShellFolder)返回一個IShellView接口對象的指針在一個視圖對象上;
- 一個IShellBrowser指針傳給該視圖對象,並允許該IShellBrowser對象來控制Explorer的菜單和工具欄;
- 另外該視圖對象也會收到一個IShellFolder的對象指針;
- Explorer請求IShellFolder對象返回一個可以枚舉文件夾內容的對象;
- Explorer遍歷該文件夾包含的元素列表。對於每個元素獲取它的PIDL並根據它的角色和特點來繪制它。
點擊Tree View的某個文件夾時的操作流程
- Explorer請求IShellFolder返回一個可枚舉文件夾的對象(一個枚舉器);
- Explorer僅顯示有“folder”屬性的元素,對於還有“has subfolders”屬性的,會繪制一個“+”或者其他可展開的節點;
- Explorer請求IShellFolder提供顯示Tree View中每個節點的圖標;事實上,Explorer將接收一個IExtractIcon接口的對象指針;
- Explorer請求IShellFolder提供下一個被顯示的各Item項;
- Explorer請求IShellFolder提供對每個Item項的上下文菜單。
Folder Manager
- IshellFolder繼承於IPersistFolder, IPersistFolder提供Explorer來初始化一個文件夾對象,告訴explorer在命名空間中的位置
- IShellFolder實現的一些接口提供給Explorer來請求一個視圖View、一個枚舉對象,一個子文件夾對象
- IShellFolder對象還需要提供其包含的每個單獨項的屬性信息,如比較兩個Item項,返回它們的顯示名稱,這些Item項均通過PIDLs來定義
主要的接口說明
IPersistFolder
GetClassID:獲取當前文件夾對象的CLSID,該CLSID為GUID的常量字符串,為命名空間擴展的CLSID值。
Initialize:允許文件夾初始化自身,參數pidl為文件夾所在命名空間的位置,若是與當前文件夾相關,則可先保存后面用;否則返回S_OK即可。
注意:這些接口不能被你調用,其主要是被綁定到你的文件夾后,由系統來調用的。
IShellFolder
BindToObject:shell請求模塊打開一個子文件夾時的調用,該方法提供一個PIDL以及你需要創建一個基於該PIDL的新的文件夾對象返回。
BindToStorage:Shell不再調用該接口,可直接返回E_NOTIMPL。
CompareIDs:主要提供兩個Item比較和排序用的,參數為PIDL。
CreateViewObject:創建並返回一個IShellView對象,用以右側面板顯示視圖內容的。
EnumObjects:創建並返回一個IEnumIDList對象,用以枚舉文件夾下對應的各項的。
GetAttributesOf:返回一個特定項的屬性族,無論其是否被重命名或拷貝、或持有的圖標、或是一個文件夾或有子文件,
這些屬性都是由SFGAO_xxx開頭的一些助記符。
GetDisplayNameOf:返回文件夾中的一個用於渲染一個項的名稱,如在地址欄、用於解析等(見SHGNO枚舉含義)。
GetUIObjectOf:借助於該方法,Explorer請求一個特殊的接口來處理UI。
ParseDisplayName:返回一個給定PIDL的顯示名稱,顯示名稱並不必要,當設置SHGDN_FORPARSING標識時,由GetDisplayNameOf返回。
SetNameOf:給一個給定對象分配一個新的顯示名稱,這個名稱主要用於地址欄,文件夾,或解析目的。
很多情況下,顯示名稱與實際的文件名稱是一致的,不管是否文件夾包含文件
說明:CreateViewObject:獲取IShellView;GetUIObjectOf:獲取IContextMenu和IExtractIcon;EnumObjects:獲取IEnumIDList。
IEnumIDList
為了允許外部模塊遍歷自定義文件夾的內容,命名空間擴展需要實現IEnumIDList接口,該對象接口提供給其他模塊可枚舉任何文件夾的各項內容;此外該接口比較通用,一個模塊和它通信不需要知道任何它的內容或者文件夾對象自身的組織方式
Next:返回集合中特定數量的項目;每個項目通過PIDL被定義。
Skip:跳過特定數量的項目。
Reset:移動當前指針到list列表的頭部。
Clone:拷貝當前對象副本。
其中最為重要的便是Next接口;用於枚舉對象獲取指定數量的PIDL值,一般建議用一個指針保存最新將要返回的值的位置。
一般數據存儲結構(以LPENUMLIST形成的鏈表結構)。
-
LPITEMIDLIST(PIDL) = ITEMIDLIST + USER_STRUCT_DATA + ITEMIDLIST(占位的,作為單個PIDL指針內容結束的哨兵);USER_STRUCT_DATA 可為任何形式的內容,一般我們稱之為PIDLDATA(或任何名稱均可);
-
第一個ITEMIDLIST(也即是SHITEMID)中cb為ITEMIDLIST + USER_STRUCT_DATA的大小,而abID地址位置值即為實際的存儲數據(類似於讓USER_STRUCT_DATA前移一個BYTE)或者USER_STRUCT_DATA位置放實際的值也可以(有個冗余的1BYTE字節) LPENUMLIST = PIDL + next;
注意:
- PIDL分配的資源以及本身均應從IMalloc接口來申請,使得Explorer可釋放它;PIDL只是一塊內存不是對象。
- PIDL可以存儲到磁盤,此時PIDL內部內容則不應該含有指針或引用之類的。
- 如果你要PIDL持久化,則建議增加版本或簽名之類的字段,這樣可以向后兼容。
IShellView
右側視圖中放置的一個IShellView視圖對象,其與Explorer通信,以處理消息,菜單、工具欄等
AddPropertySheetPages:允許你添加文件夾選項的自定義頁對話框。
CreateViewWindow:創建並返回一個嵌入到右側面板的窗口,其應該為一個無邊框的窗口。
DestoryViewWindow:銷毀前一個窗口。
EnableModeless:一般不用,直接返回E_NOTIMPL。
EnableModelesssSV:同上。
GetCurrentInfo:通過一個FOLDERSETTINGS結構,返回當前文件夾的配置。
GetItemObject:返回一個上下文菜單或剪切板的一個指針接口,一個項目的集合,一般被共用的對話框調用。
Refresh:使得當前文件夾內容被重繪。
SaveViewState:保存當前視圖的狀態(可結合IShellBrowser的GetViewStateStream接口獲取到ISteam來操作數據保存持久化);此外,IStream需要操作的Read和Write的對象由傳給GetViewStateStream的參數STGM_READ和STGM_WRITE分別來表示讀寫數據流。
SelectItem:修改當前的一個或多個項目的選擇狀態。
TranslateAccelerator:當擴展獲取到焦點時,翻譯快捷鍵;返回S_OK可阻止explorer再次翻譯。
UIActivate:當激活狀態被改變時候被調用,如當文件夾被激活或失去激活,一般修改某些狀態均可在此函數中完成。
GetWindow:返回當前視圖窗口的句柄。
ContextSensitiveHelp:文件夾應進入或退出語境上下文幫助模式,並處理所有的不同的消息,一般也不常用。
IShellBrowser
實現Explorer與命名空間擴展的通信交互,如菜單、工具欄、視圖狀態等
菜單
處理IShellView中的UIActivate(),當文件視圖從未激活到激活時,需先移除早期的菜單即通過IShellBrowser的RemoveMenusSB以及DestoryMenu銷毀資源,再由CreateMenu創建並調用IShellBrowser的InsertMenusSB接口,讓shell通過菜單組描述來共享該菜單;
此后便可通過InsertMenuItem或DeleteMenu等處理自己的菜單內容;處理后調用IShellBrowser的SetMenuSB接口將該菜單對象設置到當前命名空間擴展,(另外需要保存一下當前的激活與否的狀態,對於狀態沒有變化時,則無需處理前面的一系列操作),菜單ID對應的消息也會被發給右側的視圖對象,視圖對象自行處理即可。
工具欄
IShellBrowser的SendControlMsg,第一個參數為FCW_TOOLBAR即為操作工具欄;若注冊圖標按鈕,則第二個參數為TB_ADDBITMAP(對應第四個參數為一個TBADDBITMAP對象地址)或其他如TB_ADDSTRING(第四個參數為字符串地址);
IShellBrowser的SetToolbarItems設置按鈕,參數為TBBUTTON結構對象,以及FCT_MERGE參數。另外還有SendControlMsg工具欄FCW_TOOLBAR下的其他消息參數如TB_GETBUTTON,TB_DELETEBUTTON或TB_INSERTBUTTON等。
狀態欄
IShellBrowser的SendControlMsg,第一個參數為FCW_STATUS即為狀態欄,靈活性比較大;若是簡單的字符串,則可用SetStatusTextSB即可。
左側樹控件
IShellBrowser的SendControlMsg,第一個參數為FCW_TREE,即為給左側樹控件面板發送消息。
IContextMenu
自定義上下文菜單,右鍵菜單
InvokeCommand:
QueryContextMenu:
GetCommandString:
IExtractIcon
自定義圖標,下面兩個均為返回圖標,前者為返回圖標路徑和索引后面再提取,后者直接提取返回HICON;兩者是相互排斥獨立的,其中一個調用成功另一個便不會被調用
GetIconLocation:
Extract:
這個主要用來設置地址欄和左側樹控件面板的圖標,至於視圖的如列表控件則需要自行實現。除了IExtractIcon,IShellIcon也可以提供圖標而且比較快速;
另外,前者是需要的時候還要單獨創建一個圖標實例;而后者只需要提前准備好創建一次即可。故而Explorer搜索樹或地址欄圖標時,優先從IShellIcon中獲取;若失敗,再從IExtractIcon的GetUIObjectOf獲取。
IDropTarget
支持拖拽
DragEnter:
DragOver:
DragLeave:
Drop:
IDataObject
支持拖拽以及拷貝至剪切板或打包數據,並提供多種數據格式、可移動
其他部分擴展接口
IShellView2:
IPersistFolder2:
命名空間擴展節點(Junction Points)
注冊注冊表節點,因其為COM進程里服務,故需要被注冊到HKEY_CLASSES_ROOT\CLSID
訪問命名空間擴展的節點,操作方式可有:
- 關聯命名空間到一個文件類型
- 使用一個非常特別內容的目錄
- 使用一個特別名稱的目錄
- 將其關聯到一個已存在的命名空間上
安裝命名空間擴展
- 注冊命名空間擴展為COM服務對象,並指定其線程模型為apartement,顯示圖標、擴展名稱;
- 注冊approved擴展,以支持WindowsNT系統下可以工作;
- 定義你的擴展節點;
一般注冊節點包含有:
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}設置默認值為擴展名稱
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\InProcServer32下設置默認值dll以及ThreadingModel=Apartment字符串
- HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\DefaultIcon下默認值設置為"xxx.dll,0",(0為圖標索引,可根據需要選擇圖標)
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellExtensions\Apprved{XXX-XXX....}下設置NT下支持的擴展名稱,一般可和上名稱一樣
另外如果需要增加到桌面、我的電腦圖標,則可添加以下注冊表:
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace{XXX-XXX...};
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace{XXX-XXX...};
除了以上的,還可以增加HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}\ShellFolder的Attributes十六進制值,該值一般為SFGAO_FOLDER|SFGAO_HASSUBFOLDER等值的組合值,根據你的需要設置該Attributes值。
HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}可增加一個InfoTip值,使得當鼠標放在圖標上時,可以顯示tootip提示信息;雖然此信息也可以通過IQueryInfo的接口來實現。
想要給桌面的那個圖標增加一個移除命名,可以增加一個Removal Message的字符串,並且要在之前的那個Attributes上組合一個SFGAO_CANDELETE屬性即可(移除了桌面的圖標后,也會自動異常對應注冊表值)。
還可以添加其他更多的屬性在上面,如允許重命名和屬性,前者需要一個SFGAO_CANRENAME,后者需要SFGAO_HASPROPSHEET並實現ISehllPropShetExt接口,另外還要在HKEY_CLASSES_ROOT\CLSID{XXX-XXX....}下增加一個Shellex\PropertySheetHandlers鍵值,值即為該接口對應的GUID。
要讓命名空間擴展支持系統公共接口或對話框等之類也存在時候,需要組合SFGAO_BROWSABLE,SFGAO_FILESYSTEM的Attributes值並且可能需要SHBrowserForFolder函數的實現支持。
其他:
注冊命名空間擴展時,可能需要硬編碼DllRegisterServer函數;當被regsvr32.exe調用啟動該擴展時,將立即生效刷洗資源管理器,你的擴展就可以生效了。