Contents
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.
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.
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;
CShellImageLists m_ImageLists;
LPITEMIDLIST m_pidlNSFROOT;
TCHAR m_szInstallPath[MAX_PATH];
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.
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
.
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;
TCHAR szName[1];
}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);
HRESULT GetName(LPCITEMIDLIST,LPTSTR);
HRESULT GetFullName(LPCITEMIDLIST pidl,
LPTSTR szFullName,DWORD *pdwLen);
ITEM_TYPE GetItemType(LPCITEMIDLIST pidl);
BOOL HasSubFolder(LPTSTR pszPath);
....
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.
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);
LPITEMIDLIST pidlNew =
(LPITEMIDLIST) _Module.m_Allocator.Alloc(
TotalSize + sizeof(ITEMIDLIST));
if (pidlNew)
{
::ZeroMemory(pidlNew,TotalSize + sizeof(ITEMIDLIST));
LPITEMIDLIST pidlTemp = pidlNew;
pidlTemp->mkid.cb = (USHORT)TotalSize;
LPPIDLDATA pData;
pData = GetDataPointer(pidlTemp);
pData->type = iItemType;
::CopyMemory(pData->szName, pszName,
(_tcslen(pszName)+1) * sizeof(TCHAR));
pidlTemp = GetNextItem(pidlTemp);
pidlTemp->mkid.cb = 0;
pidlTemp->mkid.abID[0] = 0;
}
return pidlNew;
}
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);
LPPIDLDATA pData = GetDataPointer(pidlTemp);
return pData->type;
}
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;
}
}
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;
if(!pidl1 && !pidl2)
return NULL;
if(!pidl1)
{
pidlNew = Copy(pidl2);
return pidlNew;
}
if(!pidl2)
{
pidlNew = Copy(pidl1);
return pidlNew;
}
cb1 = GetByteSize(pidl1) - sizeof(ITEMIDLIST);
cb2 = GetByteSize(pidl2);
pidlNew =
(LPITEMIDLIST)_Module.m_Allocator.Alloc(cb1 + cb2);
if(pidlNew)
{
::ZeroMemory(pidlNew,cb1+cb2);
::CopyMemory(pidlNew, pidl1, cb1);
::CopyMemory(((LPBYTE)pidlNew) + cb1, pidl2, cb2);
}
return pidlNew;
}
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.
typedef struct tagENUMLIST
{ struct tagENUMLIST *pNext;
LPITEMIDLIST pidl;
}ENUMLIST, FAR *LPENUMLIST;
LPENUMLIST m_pFirst;
LPENUMLIST m_pLast;
LPENUMLIST m_pCurrent;
CNWSPidlMgr m_PidlMgr;
STDMETHOD (Next) (DWORD, LPITEMIDLIST*, LPDWORD);
STDMETHOD (Skip) (DWORD);
STDMETHOD (Reset) (void);
STDMETHOD (Clone) (LPENUMIDLIST*);
BOOL DeleteList(void);
BOOL AddToEnumList(LPITEMIDLIST pidl);
HRESULT _Init(LPCITEMIDLIST pidlRoot,DWORD dwFlags);
HRESULT _AddPidls(ITEM_TYPE iItemType, LPTSTR pszPath);
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))
_tcscpy(tmpPath,_T("ROOT"));
else
{
m_PidlMgr.GetFullName(pidlRoot,tmpPath,&dwLen);
}
if(dwFlags & SHCONTF_FOLDERS)
_AddPidls(NWS_FOLDER,tmpPath);
if(dwFlags & SHCONTF_NONFOLDERS)
_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);
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;
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)
{
pNew->pNext = NULL;
pNew->pidl = pidl;
if(!m_pFirst)
{
m_pFirst = pNew;
m_pCurrent = m_pFirst;
}
if(m_pLast)
m_pLast->pNext = pNew;
m_pLast = pNew;
return TRUE;
}
return FALSE;
}
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.
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).
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
.
CNWSPidlMgr m_PidlMgr;
LPITEMIDLIST m_pidlPath;
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.
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();
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;
}
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))
{
pEnum->AddRef();
Hr = pEnum->_Init(m_pidlPath, grfFlags);
if (SUCCEEDED(Hr))
Hr = pEnum->QueryInterface(IID_IEnumIDList,
(void**)ppEnumIDList);
pEnum->Release();
}
return Hr;
}
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))
{
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;
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( TRUE == m_PidlMgr.HasSubFolder(szText) )
dwAttr |= SFGAO_HASSUBFOLDER;
break;
}
*pdwAttribs &= dwAttr;
}
}
return S_OK;
}
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.
public:
HWND m_hWnd;
HWND m_hwndList;
LPSHELLBROWSER m_pShellBrowser;
protected:
CMyVirtualFolder *m_pFolder;
LPITEMIDLIST m_pidlRoot;
CNWSPidlMgr m_PidlMgr;
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));
{
::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 };
lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
lvi.iItem = ListView_GetItemCount(m_hwndList);
lvi.lParam = (LPARAM)m_PidlMgr.Copy(pidl);
if( m_PidlMgr.GetItemType(pidl) == NWS_FOLDER)
lvi.iImage = ICON_INDEX_FOLDER;
else
lvi.iImage = ICON_INDEX_FILE;
TCHAR szName[MAX_PATH]=TEXT("");
m_PidlMgr.GetName(pidl,szName);
lvi.pszText = szName;
ListView_InsertItem(m_hwndList, &lvi);
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 );
::SendMessage(m_hwndList, WM_SETREDRAW, TRUE, 0L);
::InvalidateRect(m_hwndList, NULL, TRUE);
::UpdateWindow(m_hwndList);
}
return S_OK;
}
First, you must declare a handler function for LVN_ITEMACTIVATE
in our IShellView
implementation's message map like this:
BEGIN_MSG_MAP(CNSFShellView)
......
NOTIFY_CODE_HANDLER(LVN_ITEMACTIVATE, OnItemActivated)
......
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))
{
if(NWS_FOLDER ==
m_PidlMgr.GetItemType((LPITEMIDLIST)lvItem.lParam))
{
m_pShellBrowser->BrowseObject(
(LPITEMIDLIST)lvItem.lParam,
SBSP_DEFBROWSER | SBSP_RELATIVE);
}
}
return 0;
}
STDMETHODIMP CNSFShellView::CreateViewWindow(
LPSHELLVIEW lpPrevView,
LPCFOLDERSETTINGS lpFS,
LPSHELLBROWSER pSB,
LPRECT prcView,
HWND* phWnd)
{
......
m_pShellBrowser = pSB;
......
}
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.
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.