This is part 3 of the mini shell framework article series. Here, I'll discuss how to create a namespace extension by creating a custom ShellFolder that works together with standard system
FolderView. ShellFolders are complex shell extensions due to the large amount of features that need to be implemented. SDK shell documentation is often incomplete or missing. Even some critical Win32 shell API functions are undocumented! The
ShellFolderImpl class is explained with help of the VVV sample. Code for this sample is included in the framework. This sample operates on .vvv files (renamed .ini files). This is the same sample I had used in part I and part II, but here it is extended for sub-folders. There are two kinds of namespace extensions: rooted and non-rooted. The VVV sample demonstrates a non-rooted type.
Everyone who has browsed through the MSDN documentation to learn and understand how to create shell extensions will sooner or later discover the terms
PIDLs. These are the key data structures used by the shell to identify shell objects. The current MSDN documentation gives a thorough explanation of the concepts.
The definition of an
typedef struct _ITEMIDLIST
The definition of a
typedef struct _SHITEMID
USHORT cb; // Size of the ID (including
// cb itself)
BYTE abID; // The item ID (variable length)
Every item in the shell needs to have its own internal unique identification. This shell item identifier (
SHITEMID) needs to be unique within its own (virtual) folder.
SHITEMID can be combined into a list of IDs (the PIDL). A typical PIDL for an item of a file system would be:
[C:] – [My Documents] – [sample.vvv] – 
This PIDL contains three
SHITEMID and a terminating
SHITEMID with size 0.
The main question is which identifier to use for a certain item. The MSDN documentation is not very clear about this so here are some guidelines:
- Don’t use pointers as item identifiers. While pointers are unique, this will cause problems when two instances of a ShellFolder display the same item. Item IDs need to be identical if they point to the same item. The typical use case for this scenario is when the user opens two Windows to the same folder location. Tip: Always verify that your extension works when using two shell views pointing to the same location.
- For a simple view (one that need not support item delete, copy paste, etc.) an array based solution can be used with the index in the array as identifiers.
- For file system items the most logical selection is the filename. Most file systems enforce that file names should be unique in a directory. The shell can handle that, an ID of an item is changed as long as there is a relationship with the display name. The
SHChangeNotify function can be used to fire a 'RENAME' to notify the system that a certain PIDL is replaced by another PIDL.
- The shell will always call the ShellFolder instance to parse the
SHITEMID. This allows you to store any info into the
SHITEMID structure. The vvv sample stores all the info into the
- For performance reasons caching of items is required. Be careful to refresh the cache when more than one view can be used to edit the folder.
For the VVV sample I started by storing all items in a
std::vector and using the index as a PIDL. This was a dead end when I tried to add edit functionality to the sample. The current sample uses a unique slot position ID to identify items. This also allows the sample to support sub folders and identical display names.
A shell namespace extension consists of a couple of COM objects that work together. The four most important objects are:
- A COM object that supports the
- A COM object that supports the
- A COM object that supports the
- A COM object that supports the
Windows (shell32.dll) provides a default implementation for a COM object that supports the
IShellView interface. This default system COM object supports the common view modes (details, icons, etc.) and can handle most events. A callback interface called
IShellFolderViewCBImpl is used by the system view object to pass events and allow control of the behavior of the system view object. The
IShellFolderImpl class uses by default this standard system view object. The advantages of using the default ShellView are identical look and feel and behavior as the rest of the explorer UI.
The core of a namespace extension is the
ShellFolder object. This object is responsible for maintaining the items that are inside the folder. Every operation on these items is controlled by the
ShellFolder object. The framework provided
IShellFolderImpl template class is designed to make it easy to create a
ShellFolder object. It expects two template arguments:
TDerived is the type name of the derived class. All pre processed events are forwarded to this class. The
TDerived class needs to provide the following functions:
CComPtr<IEnumIDList> CreateEnumIDList(HWND hwnd,
SFGAOF GetAttributeOf(unsigned int cidl,
const TItem& item, SFGAOF sfgofMask) const;
All other functions have a default implementation provided by the
TItem is the type name of a wrapper around a PIDL item. The
IShellFolderImpl will wrap every incoming PIDL in a
TItem object. The
TItem class needs to support the following functions:
TItem(const SHITEMID& shitemid);
CString GetDisplayName(SHGDNF shgdnf = SHGDN_NORMAL)
int Compare(const TItem& item, int nCompareBy,
bool bCanonicalOnly) const;
ShellFolder must return an enumeration object to the
ShellView uses this enumerator object to fill it’ internal display list. The framework depends currently on the ATL class
CComEnumOnSTL to provide this functionality.
To interact with copy\paste and drag and drop the
ShellFolder must support an object of
IDataObject that can interact with shell clipboard operations. The framework provides a
CShellFolderDataObjectImpl template class and a couple of clipboard format handlers to implement the required functionality.
The framework relies on exceptions to handle runtime errors. Two types of exceptions are expected to be thrown (see also part I):
_com_error exceptions and exceptions derived from
std::exception. A couple of
IShellFolder functions have a
HWND argument that can be used to display messages to the user. If an exception occurs, the
IShellFolderImpl class will catch it and forward the exception to an
OnError function. The default implementation of
OnError is empty but the derived class can translate the error condition to a string and display it to the user. The VVV sample code is shown below:
void OnError(HRESULT hr, HWND hwnd,
CString strMsg =
MB_OK | MB_ICONERROR);
Before we begin to discuss the implementation in detail it is good to have a list of features that our sample namespace extension should be able to perform:
- View the items stored in a .vvv file.
- Sort items in detailed view mode.
- Supports a custom item infotip.
- Supports a custom property sheet.
- Supports sub folders.
- rename items,
- delete items,
- change attributes of items using the custom property sheet,
- copy/cut paste/drag-drop items to the file system from a .vvv file,
- copy/cut paste/drag-drop items from the file system to a .vvv file.
As with all other shell extensions a namespace extension must be registered as a COM object before it can be used. The framework provides a registration function and ATL registration scripts for this purpose. Windows 98 needs a special registration script as it doesn’t have version 5 of shell32.dll. The code below shows the static registration function of the sample that will be called by ATL when registration is required.
static HRESULT WINAPI
CVVVSample::UpdateRegistry(BOOL bRegister) throw()
IDR_SHELLFOLDER, IDR_SHELLFOLDER_WIN98, bRegister,
L"Sample ShellExtension ShellFolder", wszVVVExtension,
Two template arguments are required . The first is the class name of the class. The other is the name of a class that can wrap a PIDL and act on it. A lot of requests are forwarded to this
class ATL_NO_VTABLE CShellFolder :
public IShellFolderImpl<CShellFolder, CVVVItem>,
The constructor of the sample ‘registers’ the columns the folder supports. The
ShellView object will query these columns when it is configured to display the items in detailed mode.
// Register the columns the folder supports
// in 'detailed' mode.
IShellFolderImpl class has a default implementation for the
IShellFolder::Initialise function. This default implementation will just store the root folder. This root folder can be retrieved with the
The primary task of a
ShellFolder is to manage the list of items in that folder. The
ShellView object will call the
IShellFolder::EnumObjects function to retrieve an enumerator for all items.
IShellFolderImpl will forward this request to the
CreateEnumIDList function. This function needs to be implemented by the user class.
CComPtr<IEnumIDList> CreateEnumIDList(HWND /*hwnd*/,
auto_ptr<vector<CVVVItem> > qitems =
return CEnumIDList::CreateInstance(GetUnknown(), qitems);
To construct the requested enumerator object the sample asks the
CVVVFile object for a list of current items. The
CVVVFile object opens and parses the current .vvv file and returns a
std::vector with the items. This vector is then used to create a enumerator object around it. This enumerator object is based on the ATL
CComEnumOnSTL template base class. The shell will use the enumerator to get all the PIDLs and then release the enumerator object.
When the system
ShellView displays the items in a 'detailed' mode the user can sort the items by clicking on the header of the
ShellView will forward this event to the
ColumnClick function. The framework's default implementation returns
S_FALSE to indicate that the system
ShellView should handle the event. The system
ShellView object will then sort the column. If the framework detects that it runs on a version of shell32.dll that doesn’t support this default handling (Win 95\Win 98) it will explicitly trigger the
ShellView to do the sorting. During the sorting process the
IShellFolder::CompareIDs every time it needs to compare two items for the sorting process.
IShellFolderImpl will handle this request by wrapping the PIDLs in
TItems and then forwarding the call to the item. The code below shows how the sample handles it:
int CVVVItem::Compare(const CVVVItem& item,
int nCompareBy, bool /*bCanonicalOnly*/) const
return UIntCmp(_nSize, item._nSize);
ATLASSERT(!"Illegal nCompare option detected");
When the user hovers his mouse pointer over an item the system
ShellView either shows a infotip for that item or displays a status text in the status bar. The
ShellFolder object determines and controls the text to be displayed. The
ShellView will call
GetUIObjectOf with a request for an
IQueryInfo interface. The
IShellFolderImpl class will check if the interface is requested only for one item and then wrap the PIDL in a
TItem is then queried for its tooltip text. If this string is not empty a
QueryInfo object is created and returned.
Depending on the requirements for the shell folder it may be necessary to support sub folders. To support sub folders the following steps must be followed:
The first step is to return the attribute
SFGAO_FOLDER for items that are sub folders. The tree view in explorer can then be used to access these sub folders. When the shell needs access to a folder it will follow the given procedure:
- Create an instance of the
BindToObject for a new instance bound to the correct subfolder. This sub folder can be more than one level deep.
- Release the original
IShellFolderImpl class has support for handling sub folders. It will parse the item list containing the sub folders to a vector of
TItems. It will then create a new
ShellFolder instance, initialize the root folder and pass the vector of items to the function
InitializeSubFolder function must be implemented by the derived class. The sample code demonstrates this.
To support the standard feature where a user can open a sub folder inside the folder view the
ShellFolder must provide a context menu for sub folder items.
IDataObject* pdataobject, UINT /*uFlags*/,
if (cfshellidlist.GetItemCount() == 1)
// Add 'open' if only 1 item is selected.
// Note: XP will automatic make
// the first menu item the default.
// Win98, ME and 2k don't do this,
// so must add as default item.
The actual 'open' request must be handled:
void CShellFolder::OnOpen(HWND hwnd,
ATLASSERT(items.size() == 1);
SBSP_DEFBROWSER | SBSP_RELATIVE);
CString strMessage =
_T("Open on: ") + items.GetName();
_T("Open"), MB_OK | MB_ICONQUESTION);
The sample supports open on folders and non-folders. An 'open' command on a folder item will actually open the sub folder, but non folder items will just show a message box.
Two important requirements need to be fulfilled before edit features start to work. When the user creates a selection of items and wants to perform an operation on it the
ShellView asks the
ShellFolder what it can do with this collection of items. The
IShellFolderImpl class provides a default implementation that will first try to detect if a global setting exists. If no global settings exist it will call
GetAttributeOf for every item in the selection. The next sample code shows how to handle the global setting request. The sample will return that, items can be renamed, deleted, copied and moved. If only one item is selected it reports that it supports a property option. The
sfgofMask parameter contains the bit mask of what the caller really wants to know and can be used to skip expensive computations.
// Purpose: called by MSF to detect if a
// global attribute setting exists.
SFGAOF CShellFolder::GetAttributesOfGlobal(UINT cidl,
// Tell the shell what it can do with
// an item inside a VVV file.
SFGAOF sfgaof = SFGAO_CANRENAME | SFGAO_CANDELETE |
SFGAO_CANCOPY | SFGAO_CANMOVE;
if (cidl == 1)
sfgaof |= SFGAO_HASPROPSHEET;
The second requirement is that we need to tell the system folder view that it should register itself for the 'change' events our
ShellFolder generates. To tell the system folder view, we need to provide an
IShellFolderViewCB interface while creating the system folder view and handle the
The framework provides a default COM object implementation for the
IShellFolderViewCB interface. The following code sample shows how to use this default implementation.
// Create a derived class and set the event bitmask.
SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER | SHCNE_DELETE)
// Create a helper function that can
// create an instance of this COM
// object and initialize it.
CreateInstance(const ITEMIDLIST* pidlFolder)
// Replace the default framework
Tip: During the testing of edit functionality it is useful to have two explorer views open on the same folder. This gives quick feedback if the notifications of an action reaches all the views.
To support renaming of items the
GetAttributeOf(Global) needs to return
SFGAO_CANRENAME. This will trigger the
FolderView to offer a 'rename' option to the user. When the user has renamed the item the view will call
IShellFolder::SetNameOf passing the new name. The
IShellFolderImpl will wrap the PIDL in a
TItem and then pass the call to the
OnSetNameOf function to perform the actual update. If the name could actually be changed the
IShellFolderImpl will fire a
SHCNE_RENAMEITEM notification to update all open explorer views. The code below shows how the sample handles the rename request:
void CShellFolder::OnSetNameOf(HWND /*hwnd*/, CVVVItem& item,
const TCHAR* szNewName, SHGDNF shgndf)
RaiseExceptionIf(shgndf != SHGDN_NORMAL &&
shgndf != SHGDN_INFOLDER);
IShellFolderImpl provides support for deleting items. When the
ShellView asks for the attributes of an item (forwarded to the
GetAttributesOf function) and the
SFGAO_CANDELETE the explorer offers the user the option to delete the item(s). When the user requests to delete the current selected items the request is forwarded to the
IShellFolderImpl performs a quick check to make sure every item has the
SFGAO_CANDELETE attribute set and will then call
Titems). The code below shows how the sample handles this:
long CShellFolder::OnDelete(HWND hwnd, CVVVItems& items)
if (hwnd != NULL && !UserConfirmsFileDelete(hwnd, items))
return 0; // user wants to abort the
// file deletion process.
When the items are deleted the
OnDelete function should return the event that must be broadcasted to the system. This event ensures that all other views will update itself.
An alternative method of renaming an item with the
ShellView is to use a property page. This also allows the user to view and change other attributes of the item. The
IShellFolderImpl will call the function
OnProperties when it receives a request to show the 'properties' dialog.
long CShellFolder::OnProperties(HWND hwnd, CVVVItems& items)
ATLASSERT(items.size() == 1);
CVVVItem& item = items;
if (CVVVPropertySheet(item, this).DoModal(hwnd,
wEventId) > 0 && wEventId != 0)
shellfolder controls the GUI of the properties dialog window. It is up to the implementation to select an appropriate GUI window. It can be a simple dialog or a property sheet. The framework provides a simple
CPropertySheet class that can be used in combination with the ATL
CSnapInPropertyPageImpl class to create a property sheet (with pages).
IShellFolderImpl has built in support for clipboard transactions and drag and drop operations. To enable drag and drop support the derived class must:
IDropTarget interface to the interface map.
COM_INTERFACE_ENTRY(IDropTarget) // enable drag and drop support.
CShellFolderDataObject class that supports the
IDataObject interface. To implement this class the framework provides the
CShellFolderDataObjectImpl template base class. The special thing about a
DataObject that participates in shell drag and drop operations is that it needs to accept additional clipboard formats. Windows provides the function
CIDLData_CreateFromIDArray to create such a
CShellFolderDataObjectImpl class is a wrapper around this
DataObject. Besides wrapping it allows registered clipboard formats to take precedence. The sample code below shows how the sample registers two handlers for the standard clipboard formats (
void CShellFolderDataObject::Init(const ITEMIDLIST* pidlFolder,
UINT cidl, const ITEMIDLIST** ppidl,
__super::Init(pidlFolder, cidl, ppidl, pperformeddropeffectsink);
Which clipboard formats to support and how to convert a PIDL, for example into a file system object, depends completely on the implementation. The framework provides only the skeleton to make it easy to pack that functionality into a clipboard format handler class.
Coping items to the clipboard is a process that consists of several steps. The user makes first a selection of the items in the view window. The
ShellView will then call the
GetUIObjectOf function with a request for a
IDataObject interface for these selected items. The framework will forward this request to the
CreateDataObject function. The standard pattern to handle this request is shown in the sample code below. The folder object creates a new
CShellFolderDataObject COM object.
CShellFolder::CreateDataObject(const ITEMIDLIST* pidlFolder,
UINT cidl, const ITEMIDLIST** ppidl)
cidl, ppidl, this);
ShellView has this
DataObject it depends on the next action of the user what the
ShellView will do with it. The user can delete, copy, cut or request the context menu for the selected items.
ShellView passes the
DataObject and the copy request together the
IShellFolderImpl class can handle the copy operation by itself. It will check whether
SFGAO_CANCOPY is set for all selected items and then put the
DataObject on the clipboard.
void IShellFolderImpl::OnCopy(HWND, IDataObject* pdataobject)
The 'cut' operation is also completely handled by the framework. The main difference with the copy operation is that the
ShellView must be notified about a ‘cut’
DataObject being put on the clipboard. This will trigger the
ShellView to make the ‘cut’ items dimmed and allow the user to cancel the cut operation with the ‘Esc’ key.
void IShellFolderImpl::OnCut(HWND, IDataObject* pdataobject)
There are two ways a
ShellFolder can receive an external
DataObject. The user can paste a
DataObject from the clipboard or it can drag and drop a selection of items on the
ShellView. Both operations are supported by the
IShellFolderImpl class. The derived
ShellFolder class needs to implement two functions to receive the external
The code below is from the vvv sample. The
true when the
DataObject supports the
CF_HDROP (list of files) clipboard format, other formats are not supported.
CCfHDrop is a class provided by the framework.
IsSupportedClipboardFormat is called when the user has 'pasted' something or when the drag-drop object enters the
ShellFolder supports the clipboard format the framework will call the
AddItemsFromDataObject function. The sample code below demonstrates how to handle this:
DWORD dwDropEffect, IDataObject* pdataobject)
unsigned int nFiles = cfhdrop.GetFileCount();
for (unsigned int i = 0; i < nFiles; ++i)
Depending on the action of the user the
dwDropEffect argument is set to
DROPEFFECT_COPY. The VVV sample always copies files from the filesystem into the VVV file. Other implementations can perform a filesystem file move as an optimization.
There are a lot of improvements that can be done to the current framework implementation; support for XP task bands, toolbar buttons, performance improvements, a rooted example, better sub folder support etc.
The current version provides a basic foundation for creating a
ShellFolder namespace extension. With the basic implementation ready, it is easy to add small improvements.