Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

Tips in Writing Namespace Extensions (I) - Implements Subfolder

4.85/5 (32 votes)
19 Oct 200513 min read 1   3.6K  
This article describes how to develop namespace extensions with subfolders.

Image 1

Contents

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 NULL SHITEMID.

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:

  1. Used by IShellView to enumerate and display contents within a folder in Shell view.
  2. 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 and IShellFolder.
  • Then, the Explorer retrieves a pointer to the folder object’s IPersistFolder interface, and calls its Initialize 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 includes SHCONTF_FOLDERS) to get a pointer to IEnumIDList interface , and call IEnumIDList::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 its IShellView interface. Finally, the Explorer will call IShellView::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.

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 new IShellFolder object for the subfolder, initialize it, and return that new object to the shell.
  • EnumObjects() - Creates a COM object (enumerator) which implements IEnumIDList, 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 as IShellView::CreateViewWindow input parameter, which allows our NSE to communicate with the Windows Explorer window. In handling LVN_ITEMACTIVATE message, we need to use this interface's BrowseObject 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:

  1. Load the extension's project into the debugger, but do not run it.
  2. From the Start menu on the Microsoft® Windows® taskbar, choose Shut Down.
  3. 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.
  4. Set the debugger to run the extension DLL with Explorer.exe from the Windows directory.
  5. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here