Tips in Writing Namespace Extensions (I) - Implements Subfolder






4.85/5 (32 votes)
Oct 8, 2005
13 min read

757237

3554
This article describes how to develop namespace extensions with subfolders.
Contents
- Introduction
- Manage NSE's data
- Implement how interfaces interact with Explorer
- How to debug
- Conclusion
Introduction
Namespace Extension (abbr. NSE) is actually a COM server which implements some required shell interfaces. Explorer uses those COM interfaces to let the user access and manipulate internal namespace data and display it in a graphical form. How to develop an NSE is a big topic, in this article, I will focus on how to implement subfolders in NSE.
After struggling with NSE for about half a year, I finally found my way out. The available articles on NSE are so poor that I had to read the source code line by line to find solutions. I learned ATL myself by reading the MSDN's sample project RegView (it was written in pure C++). Thanks to Bjarke Viksoe, who contributed the source code of AdfView which teaches a lot of important skills to deal with NSE.
Here, I want to share some tips in developing NSE. I will focus on how to solve problems that you may encounter and things you will be interested to know while writing your own NSE. Because writing an article to cover all these aspects is almost impossible (God knows how long such an article will be), I plan for a series of articles which will cover implementation of subfolder, delete and create folder, drag and drop.
This article assumes you know C++, ATL, COM and are familiar with basic knowledge of NSE. Before reading this article, read "The Complete Idiot's Guide to Writing Namespace Extensions - Part I" by Michael Dunn, as the terms used in it are used in this article as well.
In this article, the emphasis is on implementing subfolder. The NSE implemented in the provided sample project does the simple work: read the data which describes the structure of the NSE's root folder (with subfolders) from a configuration file and make the root folder act in Explorer like a real folder that has subfolders. Of course, you know this folder is not a part of the file system, it's a virtual folder.
The sample project is created using ATL COM AppWizard.
Manage NSE's data
Configuration file
Following is the content of our sample NSE's configuration file, the file's format refers to initialization file's format:
[ROOT]
dir=src;doc
[src]
dir=VB;VC
file=note1.txt;note2.txt
[src\VB]
file=vb.vbp
[src\VC]
file=test.cpp
Notes:
- Sections in the file represent the folders in our NSE, section [ROOT] represents the root folder of our NSE.
- Value in the key "dir" of each section represents the subfolders included in this folder, separated by the char ';'.
- Value in the key "file" of each section represents the files included in this folder, separated by the char ';'.
All the folders and files listed out in this file will be created into the corresponding objects in our sample NSE as the above image shows.
CComModule derived class
If you are using the ATL COM AppWizard to create your NSE, the wizard will automatically generate _Module
, a global instance of CComModule
. If you have some global resources shared by the whole module, put them in the CComModule
derived class as its data member. Like this:
Data members that manage global resources
CShellMalloc m_Allocator; //class instance to manage // Shell's IMalloc interface // pointer CShellImageLists m_ImageLists; //class instance to manage NSE // used image/icon resources LPITEMIDLIST m_pidlNSFROOT; //the PIDL uniquely specifies our // NSE's root folder all the // across the entire namespace TCHAR m_szInstallPath[MAX_PATH]; //the absolute path of NSE Dll, // use to locate the cfg file
If you have defined your CComModule
derived class, to make it work, you just need to replace the _Module
declaration type from CComModule
to the CComModule
derived class that you have implemented.
PIDL management class
Each namespace object in our NSE is uniquely identified by its PIDL- a pointer to its item ID (which is actually a SHITEMID
structure) list. If the PIDL describes the object relative to the desktop folder (and hence uniquely describes the object within the entire Shell namespace), for example the PIDL stored in _Module.m_pidlNSFROOT
, we call it a fully qualified PIDL or a full PIDL. Otherwise, it is known as a relative PIDL or a partial PIDL.
The PIDLs covered in this article are mostly relative PIDLs, divided into two sub-types:
- Complex PIDL (multi-level PIDL) - PIDL can have multiple
SHITEMID
structures and identify objects one or more levels below the parent folder. The complex PIDLs mentioned in this article all refer to the PIDL relative to our NSE's root folder. - Simple PIDL (single-level PIDL) - PIDL relative to its parent folder's PIDL, which contain only a
SHITEMID
structure (the object's item ID) and a terminating NULLSHITEMID
.
Our PIDL data
The data that will be stored in PIDL is totally determined by the NSE's implementer. For our NSE's purpose, to simply creating a folder which holds objects including both files and subfolders, a flag to distinguish folder or file and a relative name to his parent folder is enough. The following PIDLDATA
structure is defined to hold the item ID's value - which will be stored in the Item ID's SHITEMID.abID
field.
typedef enum tagITEM_TYPE { NWS_FOLDER = 0x00000001, NWS_FILE = 0x00000002, }ITEM_TYPE; typedef struct tagPIDLDATA { ITEM_TYPE type; //used to mark folder or file TCHAR szName[1]; //store the object's name // (relative to its parent folder) }PIDLDATA, FAR *LPPIDLDATA;
Because implementing our NSE involves in a lot of PIDL operations such as: create, delete, concatenate, copy and also needs some methods to fetch info from PIDL, a PIDL management class to do all these things is needed.
class CNWSPidlMgr { public: LPITEMIDLIST Create(ITEM_TYPE iItemType,LPTSTR szName); LPITEMIDLIST GetNextItem(LPCITEMIDLIST pidl); LPITEMIDLIST Concatenate(LPCITEMIDLIST, LPCITEMIDLIST); //get current pidl item's name HRESULT GetName(LPCITEMIDLIST,LPTSTR); //concat the name of the pidl(from the //pointer to the list end) together HRESULT GetFullName(LPCITEMIDLIST pidl, LPTSTR szFullName,DWORD *pdwLen); ITEM_TYPE GetItemType(LPCITEMIDLIST pidl); //Decide whether current folder has subfolder BOOL HasSubFolder(LPTSTR pszPath); .... //Other public methods private: LPPIDLDATA GetDataPointer(LPCITEMIDLIST); };
I will cover some important methods to support subfolders, for others please refer to my sample project code and its comments.
Create a new PIDL
As previously described, each Item ID's value in namespace consists of two parts: item type and name. Here we have created a simple PIDL.
LPITEMIDLIST CNWSPidlMgr::Create(ITEM_TYPE iItemType, LPTSTR pszName) { USHORT TotalSize = sizeof(ITEMIDLIST) + sizeof(ITEM_TYPE) + (_tcslen(pszName)+1)*sizeof(TCHAR); // Also allocate memory for the final null SHITEMID. LPITEMIDLIST pidlNew = (LPITEMIDLIST) _Module.m_Allocator.Alloc( TotalSize + sizeof(ITEMIDLIST)); if (pidlNew) { ::ZeroMemory(pidlNew,TotalSize + sizeof(ITEMIDLIST)); LPITEMIDLIST pidlTemp = pidlNew; // Prepares the PIDL to be filled with actual data pidlTemp->mkid.cb = (USHORT)TotalSize; LPPIDLDATA pData; pData = GetDataPointer(pidlTemp); //viz. pidlTemp->mkid.abID // Fill the PIDL pData->type = iItemType; ::CopyMemory(pData->szName, pszName, (_tcslen(pszName)+1) * sizeof(TCHAR)); // Set an empty PIDL at the end and // set the NULL terminator to 0 pidlTemp = GetNextItem(pidlTemp); pidlTemp->mkid.cb = 0; pidlTemp->mkid.abID[0] = 0; } return pidlNew; }
Get PIDL's type
This function is very useful for our NSE to realize subfolders. In the other part of this article - Retrieve the object's attribute in the folder, this method is called to determine the type PIDL represents, folder or file, before returning to Explorer.
ITEM_TYPE CNWSPidlMgr::GetItemType(LPCITEMIDLIST pidl) { LPITEMIDLIST pidlTemp = GetLastItem(pidl); //it is the last item //determine the pidl //represent what type LPPIDLDATA pData = GetDataPointer(pidlTemp); return pData->type; }
Has subfolders?
If the current folder has subfolder, NSE should tell the Explorer (IShellFolder::GetAttributesOf
returns SFGAO_HASSUBFOLDER
to Explorer), then the Explorer will display a '+' sign beside the folder in the tree view. To implements this, we simply need to refer to the configuration file to find out whether the folder's corresponding section, has the "dir" key or not?
BOOL CNWSPidlMgr::HasSubFolder(LPCITEMIDLIST pidl) { TCHAR szPath[MAX_PATH]=_TEXT(""); TCHAR tmpStr[MAX_PATH]=_TEXT(""); TCHAR szCfgFile[MAX_PATH]=_TEXT(""); DWORD dwLen=MAX_PATH; GetFullName(pidl,szPath,&dwLen); if( dwLen>0) { dwLen = MAX_PATH; _tcscpy(szCfgFile,g_szInstallPath); _tcscat(szCfgFile,_T("\\NSExtWithSubFld.cfg")); GetPrivateProfileString( szPath, _T("dir"),_T("NotFound"),tmpStr, dwLen, szCfgFile); if( (_tcscmp(tmpStr,_T("NotFound"))==0 ) || (_tcslen(tmpStr)==0 ) ) return FALSE; else return TRUE; } }
Concatenate PIDLs
If NSE has multi-level folder, sometimes you need to combine an object's simple PIDL with either its parent folder's complex PIDL to get a qualified PIDL relative to the NSE's root or its parent folder's full PIDL to get a full PIDL relative to the desktop folder.
LPITEMIDLIST CNWSPidlMgr::Concatenate( LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { LPITEMIDLIST pidlNew; UINT cb1 = 0, cb2 = 0; //are both of these NULL? if(!pidl1 && !pidl2) return NULL; //if pidl1 is NULL, just return a copy of pidl2 if(!pidl1) { pidlNew = Copy(pidl2); return pidlNew; } //if pidl2 is NULL, just return a copy of pidl1 if(!pidl2) { pidlNew = Copy(pidl1); return pidlNew; } cb1 = GetByteSize(pidl1) - sizeof(ITEMIDLIST); cb2 = GetByteSize(pidl2); //create the new PIDL pidlNew = (LPITEMIDLIST)_Module.m_Allocator.Alloc(cb1 + cb2); if(pidlNew) { ::ZeroMemory(pidlNew,cb1+cb2); //copy the first PIDL ::CopyMemory(pidlNew, pidl1, cb1); //copy the second PIDL ::CopyMemory(((LPBYTE)pidlNew) + cb1, pidl2, cb2); } return pidlNew; }
Interfaces interact with Explorer
IEnumIDList
To implement subfoldering NSE, you must provide an enumerator for the subfolders, which are responsible for enumerating the contents for all NSE's folders, from top (NSE's root folder) to bottom (the NSE's deepest subfolders). When a folder's IShellFolder::EnumObjects
method is called, it creates an enumeration object and passes a pointer to the object's IEnumIDList
interface back to the caller. Caller can use this interface to enumerate the contents within the folder object (the subfolders and files). Please refer to Get the folder's enumerate interface.
In NSE, common uses of IEnumIDList
are:
- Used by
IShellView
to enumerate and display contents within a folder in Shell view. - Used by Explorer to display the subfolders of a folder in the Tree view.
In our NSE, to create an enumerator for a folder to enumerate its contents, we refer to the configuration file and find the corresponding section for this folder, and create PIDL (which will be the item of enum list) for each subfolder and file object according to its "dir" and "file" key values and add them to an enum
list. We also provided some standard and self-defined methods to handle this PIDL enum
list.
Sometimes IEnumIDList
is used to list only the folder objects. For example, when the user clicks the '+' sign beside your NSE’s folder in the TreeView pane to expand subfolders in this folder, the Explorer will call IShellFolder::EnumObjects
method and set the grfFlags
parameter to SHCONTF_FOLDERS
to create enumerator object and get the IEnumIDList
interface pointer to list only the subfolders. So when we create our enumerator object, we use the value of grfFlags
as an initialization condition.
Self-defined structure and data members
typedef struct tagENUMLIST { struct tagENUMLIST *pNext; LPITEMIDLIST pidl; }ENUMLIST, FAR *LPENUMLIST; LPENUMLIST m_pFirst; //pointer to the head // of the enum list LPENUMLIST m_pLast; //pointer to the tail // of the enum list LPENUMLIST m_pCurrent; //pointer to the current // pidl in the enum list CNWSPidlMgr m_PidlMgr; //class instance use to // handle pidl operations
Member functions
//member function of IEnumIDList interface, //standard method STDMETHOD (Next) (DWORD, LPITEMIDLIST*, LPDWORD); STDMETHOD (Skip) (DWORD); STDMETHOD (Reset) (void); STDMETHOD (Clone) (LPENUMIDLIST*); //Self-defined functions BOOL DeleteList(void); //called when the // instance be deleted BOOL AddToEnumList(LPITEMIDLIST pidl); //add a pidl // to the enum list //called when init the enumlist HRESULT _Init(LPCITEMIDLIST pidlRoot,DWORD dwFlags); HRESULT _AddPidls(ITEM_TYPE iItemType, LPTSTR pszPath);
Create the enum list
In initialize the enum
list, our implement of IEnumIDList
must support both the SHCONTF_FOLDERS
and SHCONTF_NONFOLDERS
flags.
HRESULT CPidlEnum::_Init(LPCITEMIDLIST pidlRoot,DWORD dwFlags) { TCHAR tmpPath[MAX_PATH]=_TEXT(""); DWORD dwLen=MAX_PATH; if((NULL==pidlRoot)||(0 == pidlRoot->mkid.cb))//Current folder //is NSE's root _tcscpy(tmpPath,_T("ROOT")); else //Current folder is NSE's subfolder { //Fetch pidlRoot correspond path name //relative to NSE's root m_PidlMgr.GetFullName(pidlRoot,tmpPath,&dwLen); } if(dwFlags & SHCONTF_FOLDERS)//add subfolders objects _AddPidls(NWS_FOLDER,tmpPath); if(dwFlags & SHCONTF_NONFOLDERS)//add files objects _AddPidls(NWS_FILE,tmpPath); Reset(); return S_OK; } HRESULT CPidlEnum::_AddPidls(ITEM_TYPE iItemType, LPTSTR pszPath) { TCHAR tmpStr[MAX_PATH]=_TEXT(""); TCHAR tmpName[MAX_PATH]=_TEXT(""); TCHAR szCfgFile[MAX_PATH]=_TEXT(""); DWORD dwLen=MAX_PATH; LPITEMIDLIST pidl=NULL; _tcscpy(szCfgFile,g_szInstallPath); _tcscat(szCfgFile,T("\\NSExtWithSubFld.cfg")); if( iItemType == NWS_FOLDER ) GetPrivateProfileString( pszPath,_T("dir"), _T(""),tmpStr,dwLen, szCfgFile); else if( iItemType == NWS_FILE ) GetPrivateProfileString( pszPath,_T("file"), _T(""),tmpStr,dwLen, szCfgFile); if( _tcslen(tmpStr)==0 ) return S_OK; TCHAR *pChr,*pszHandle; pszHandle=tmpStr; pChr=pszHandle; while( ( pChr = _tcschr(pszHandle,_T(';') ) )!=NULL) { _tcsnset(tmpName,0,MAX_PATH); _tcsncpy(tmpName,pszHandle,pChr-pszHandle); //create relative pidl pidl=m_PidlMgr.Create(iItemType,tmpName); if(pidl) { if(!AddToEnumList(pidl)) return E_FAIL; } else return E_FAIL; if(pszHandle[0] == _T('\0')) break; pszHandle = pChr+1; } _tcsnset(tmpName,0,MAX_PATH); _tcscpy(tmpName,pszHandle); if(_tcslen(tmpName)==0) return E_FAIL; //create pidl for the last item pidl=m_PidlMgr.Create(iItemType,tmpName); if(pidl) { if(!AddToEnumList(pidl)) return E_FAIL; } else return E_FAIL; return S_OK; } BOOL CPidlEnum::AddToEnumList(LPITEMIDLIST pidl) { LPENUMLIST pNew = (LPENUMLIST)_Module.m_Allocator.Alloc(sizeof(ENUMLIST)); if(pNew) { //set the next pointer pNew->pNext = NULL; pNew->pidl = pidl; //is this the first item in the list? if(!m_pFirst) { m_pFirst = pNew; m_pCurrent = m_pFirst; } if(m_pLast) m_pLast->pNext = pNew; //add the new item //to the end of the list //update the last item pointer m_pLast = pNew; return TRUE; } return FALSE; }
Deal with NSE's folder
As you known, NSE is actually a COM server which implements some required Shell interface. The Explorer works like a COM client to cooperate with NSE.
Following are the initial steps performed by the Explorer when you click our NSE root folder's (has subfolders) icon in Tree view:
- First, calling
CoCreateInstance
to create an instance of our NSE. - Second, query a pointer to the folder object which implements
IPersistFolder
andIShellFolder
. - Then, the Explorer retrieves a pointer to the folder object’s
IPersistFolder
interface, and calls itsInitialize
function (simultaneously, pass in the full PIDL of our NSE's root folder). - After the folder object is initialized successfully, the Explorer will query for the
IShellFolder
interface, and call its functions to display the content of the root folder in both Tree view and Shell view:- The Explorer will call
IShellFolder::EnumObjects
(grfFlags
will includesSHCONTF_FOLDERS
) to get a pointer toIEnumIDList
interface , and callIEnumIDList::Next
continuously to retrieve a list of the subfolders of the root folder which will be shown in Tree view. - Then the Explorer will call
IShellFolder::CreateViewObject
to create the folder view object and return a pointer to itsIShellView
interface. Finally, the Explorer will callIShellView::CreateViewWindow
to create a view window in the right pane of Windows Explorer (Shell View), which will include a list view and filled with the contents of objects in the root folder.
- The Explorer will call
As you click the subfoldered entries under our NSE’s root folder, a new instance of the folder object for each subfolder will be created in our DLL, by calling our IShellFolder::BindToObject
function.
IPersistFolder
Folder initialization
The full PIDL of our NSE's root folder which is mentioned above must be saved if we want to provide more complex UI functions such as delete or create a new folder in our NSE and want our NSE to act correctly both in Tree view and Shell View. When we implement our NSE, we use _Module.m_pidlNSFROOT
to save it.
Because the Explorer uses IPersistFolder::Initialize
function to inform shell folder object to do initialization, if an error value is returned the Explorer will figure that something has gone wrong with NSE initialization and immediately stops loading the extension. So, even if this function does nothing, it should not return E_NOTIMPL
, instead, we return S_OK
(or NOERROR).
IShellFolder
This interface is used to manage folders in our NSE. It is a required interface when implementing a NSE. OLE interfaces that can be used to carry out actions on the specified file objects or folders (such as drag and drop) implemented by our NSE will be retrieved through this interface by calling the function IShellFolder::GetUIObjectOf
.
Self-defined data members
CNWSPidlMgr m_PidlMgr; //class instance use // to handle pidl operations LPITEMIDLIST m_pidlPath; //store the complex PIDL // of current folder object
Subfolder related member functions
The subfolder related methods in this interface include:
BindToObject()
- Called when a subfolder in our NSE is being browsed. Its job is to create a newIShellFolder
object for the subfolder, initialize it, and return that new object to the shell.EnumObjects()
- Creates a COM object (enumerator) which implementsIEnumIDList
, and returns the pointer of the interface to the caller. Caller can use this interface to enumerate the contents of the current folder in our NSE.GetAttributesOf()
- Retrieves attributes (such as is folder or not) of one or more objects (files or subfolders) in the current folder. Callers like Explorer use this function's return value to decide how to display an object in current folder in the Tree view.
Bind to the subfolder
STDMETHODIMP CMyVirtualFolder::BindToObject(LPCITEMIDLIST pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppRetVal) { *ppRetVal = NULL; HRESULT hr = S_OK; if( riid != IID_IShellFolder) return E_NOINTERFACE; CComObject<CMyVirtualFolder> *pVFObj=0; hr = CComObject<CMyVirtualFolder>::CreateInstance(&pVFObj); if(FAILED(hr)) return hr; pVFObj->AddRef(); //cause relative pidl doesn't include path info, //so use Concatenate to make a absolute pidl LPITEMIDLIST pidlNew = m_PidlMgr.Concatenate(m_pidlPath,pidl); pVFObj->m_pidlPath = m_PidlMgr.Copy(pidlNew); m_PidlMgr.Delete(pidlNew); hr = pVFObj->QueryInterface(riid, ppRetVal); ATLASSERT(pVFObj); if(pVFObj == NULL) { MessageBox(NULL, _T("CMyVirtualFolder::BindToObject() pVFObj=NULL"), _T("NSF"),MB_OK); return E_FAIL; } pVFObj->Release(); return hr; }
Get the folder's enumerate interface
If you have no idea about IEnumIDList
, please go back to the section IEnumIDList in this article.
STDMETHODIMP CMyVirtualFolder::EnumObjects(HWND hWnd, DWORD grfFlags, LPENUMIDLIST* ppEnumIDList) { HRESULT Hr; CComObject<CPidlEnum> *pEnum; Hr = CComObject<CPidlEnum>::CreateInstance(&pEnum); if (SUCCEEDED(Hr)) { // AddRef() the object while we're using it. pEnum->AddRef(); // Init the enumerator. create all the items in // this folder and add it to the enumerator's list. Hr = pEnum->_Init(m_pidlPath, grfFlags); //grfFlags will passed in //as the initialization //condition // Return an IEnumIDList interface to the caller. if (SUCCEEDED(Hr)) Hr = pEnum->QueryInterface(IID_IEnumIDList, (void**)ppEnumIDList); pEnum->Release(); } return Hr; }
Retrieve the object's attribute in the folder
Folder objects should return attributes including SFGAO_FOLDER
when the system calls the IShellFolder::GetAttributesOf
method to retrieve their attributes according to their simple e e e PIDL. If the folder has subfolders, the return value includes SFGAO_HASSUBFOLDER
.
The Explorer will call this function to decide how to show objects of the current folder in Tree view (If it is a folder object, show it in Tree view. If the folder object has subfolders, add a '+' sign beside its folder icon in Tree view).
STDMETHODIMP CMyVirtualFolder::GetAttributesOf(UINT uCount, LPCITEMIDLIST pidls[], LPDWORD pdwAttribs) { HRESULT Hr; *pdwAttribs = (DWORD)-1; if(( uCount==0 )||(pidls[0]->mkid.cb == 0)) { // Can happen on Win95. Queries the view folder. DWORD dwAttr; dwAttr |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER |SFGAO_HASPROPSHEET |SFGAO_DROPTARGET; *pdwAttribs &= dwAttr; } else { TCHAR szText[MAX_PATH]=_TEXT(""); TCHAR szTmp[MAX_PATH]=_TEXT(""); DWORD dwLen=MAX_PATH; for( UINT i=0; i<uCount; i++ ) { DWORD dwAttr = 0; switch( m_PidlMgr.GetItemType(pidls[i]) ) { case NWS_FOLDER: dwAttr |=SFGAO_FOLDER; //get the full pidl LPITEMIDLIST tmpPidl; tmpPidl = m_PidlMgr.Concatenate(m_pidlPath, pidls[i]); _tcsnset(szText,0,MAX_PATH); _tcsnset(szTmp,0,MAX_PATH); m_PidlMgr.GetFullName(tmpPidl,szTmp,&dwLen); if( dwLen >0 ) _tcscat(szText,szTmp); m_PidlMgr.Delete(tmpPidl); //if current folder include subfolder, in //Explorer's left treeView panel this folder //has + can to expand if( TRUE == m_PidlMgr.HasSubFolder(szText) ) dwAttr |= SFGAO_HASSUBFOLDER; break; } *pdwAttribs &= dwAttr; } } return S_OK; }
IShellView
Our implementation of view object is to create a simple ListView window and handle a part of our user interface messages which helps to realize subfoldering (has a window procedure to handle messages). In fact, when you select a folder in system namespace, then the Explorer’s internal view object will be responsible for creating and managing a ListView pane.
To make our NSE support subfolder, that is, when the user double clicks a folder in our ListView, this subfolder should be open, and in the Explorer Tree view, the clicked subfolder should be focused and expanded. To realize this, we should do the following:
- To fill the ListView with the contents of the current folder, we should include both subfolders and files.
- We must process the
LVN_ITEMACTIVATE
notification which will be triggered when user double clicks the folder object in our ListView. - We must save the
IShellBrowser
pointer passed in asIShellView::CreateViewWindow
input parameter, which allows our NSE to communicate with the Windows Explorer window. In handlingLVN_ITEMACTIVATE
message, we need to use this interface'sBrowseObject
function to instruct the Explorer to browse into the clicked folder.
For simplicity, we do nothing when the user double clicks the file object in our ListView.
Self-defined main data members
public: HWND m_hWnd; //handle of right panel //window (Shell view window) HWND m_hwndList; //handle of listview //keep the IShellBrowser interface pointer to //allow communication with the Windows Explorer //window. LPSHELLBROWSER m_pShellBrowser; protected: CMyVirtualFolder *m_pFolder; //pointer to current // folder object LPITEMIDLIST m_pidlRoot; //complex PIDL of // current folder CNWSPidlMgr m_PidlMgr;
Fill the ListView
We fill the ListView while handling WM_CREATE
message. While calling IShellFolder::EnumObjects
to enumerate objects in the current folder, you must assign both SHCONTF_NONFOLDERS
and SHCONTF_FOLDERS
to grfFlags
.
HRESULT CNSFShellView::_FillListView() { DWORD dwFlags = SHCONTF_NONFOLDERS | SHCONTF_FOLDERS; LPENUMIDLIST pEnumIDList; HRESULT Hr; HR(m_pFolder->EnumObjects(m_hWnd, dwFlags, &pEnumIDList)); { // Turn the listview's redrawing off ::SendMessage(m_hwndList, WM_SETREDRAW, FALSE, 0L); DWORD dwFetched = 0; LPITEMIDLIST pidl = NULL; while((S_OK == pEnumIDList->Next(1, &pidl, &dwFetched)) && (dwFetched!=0)) { LV_ITEM lvi = { 0 }; //set the mask lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; //item will be add to the end of the list lvi.iItem = ListView_GetItemCount(m_hwndList); //set the object's simple PIDL to lvi.lParam lvi.lParam = (LPARAM)m_PidlMgr.Copy(pidl); //set icon if( m_PidlMgr.GetItemType(pidl) == NWS_FOLDER) lvi.iImage = ICON_INDEX_FOLDER; else lvi.iImage = ICON_INDEX_FILE; //Column 0:Name TCHAR szName[MAX_PATH]=TEXT(""); m_PidlMgr.GetName(pidl,szName); lvi.pszText = szName; ListView_InsertItem(m_hwndList, &lvi); //Set Column 1:type text TCHAR szTemp[MAX_PATH]=TEXT(""); HR(m_PidlMgr.GetItemAttributes(pidl,ATTR_TYPE,szTemp)); ListView_SetItemText(m_hwndList,lvi.iItem, COL_TYPE,szTemp); } pEnumIDList->Release(); CListSortInfo sort = { m_pFolder, COL_NAME, TRUE }; ListView_SortItems(m_hwndList, ListViewSortFuncCB, (LPARAM) &sort ); // Turn the listview's redrawing back // on and force it to draw ::SendMessage(m_hwndList, WM_SETREDRAW, TRUE, 0L); ::InvalidateRect(m_hwndList, NULL, TRUE); ::UpdateWindow(m_hwndList); } return S_OK; }
Handle LVN_ITEMACTIVATE notify
First, you must declare a handler function for LVN_ITEMACTIVATE
in our IShellView
implementation's message map like this:
BEGIN_MSG_MAP(CNSFShellView) ...... //declare other msg handler functions NOTIFY_CODE_HANDLER(LVN_ITEMACTIVATE, OnItemActivated) ...... //declare other msg handler functions END_MSG_MAP()
Following is what we do to browse into a folder:
LRESULT CNSFShellView::OnItemActivated(UINT CtlID, LPNMHDR lpnmh, BOOL& bHandled) { LV_ITEM lvItem; ZeroMemory(&lvItem, sizeof(lvItem)); lvItem.mask = LVIF_PARAM; LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW)lpnmh; lvItem.iItem = lpnmlv->iItem; if(ListView_GetItem(m_hwndList, &lvItem)) { //folders to be activated if(NWS_FOLDER == m_PidlMgr.GetItemType((LPITEMIDLIST)lvItem.lParam)) { //Tells Windows Explorer to browse to another folder m_pShellBrowser->BrowseObject( (LPITEMIDLIST)lvItem.lParam, SBSP_DEFBROWSER | SBSP_RELATIVE); } } return 0; }
Save the IShellBrowser pointer passed from shell
STDMETHODIMP CNSFShellView::CreateViewWindow( LPSHELLVIEW lpPrevView, LPCFOLDERSETTINGS lpFS, LPSHELLBROWSER pSB, LPRECT prcView, HWND* phWnd) { ...... m_pShellBrowser = pSB; ...... }
How to debug
To debug your extension, you need to execute the Shell from the debugger. Follow these steps:
- Load the extension's project into the debugger, but do not run it.
- From the Start menu on the Microsoft® Windows® taskbar, choose Shut Down.
- Press CTRL+ALT+SHIFT, and click No in the Shut Down Windows dialog box. On Windows 2000, click Cancel instead of No. The Shell is now shut down, but all other applications are still running, including the debugger.
- Set the debugger to run the extension DLL with Explorer.exe from the Windows directory.
- Run the project from the debugger. The Shell will start up as usual, but the debugger will be attached to the Shell's process.
Conclusion
Writing on NSE is still a complex job. The aim of this article is to help you develop a NSE with subfolders. In my next article, I will to tell you how to create or delete a subfolder in your own NSE and make Explorer's Tree view and Shell view reflect these changes in a synchronized way.