Objective Toolkit : Chapter 25 Namespace Extension Wizard : Tutorial
Tutorial
This tutorial demonstrates how to create a useful namespace extension using the set of classes in Objective Toolkit and Stingray namespace extension wizard. Every namespace must implement at least the IEnumIDList, IShellFolder and IShellView interfaces. Most namespaces also implement IContextMenu and IExtractIcon. Objective Toolkit has implementations for all of these classes, while the Stingray namespace extension wizard lays out the basic framework for how all these interfaces work together. Combining these two makes namespace development much easier.
To develop a namespace extension, we must deal with pointers to item ID list, such as PIDL, which involves dealing with the raw memory. This is the hard part of namespace extension development. To ease this, Stingray developers created the SECPidlMgr<T> template class, which encapsulates the complication.
In this tutorial, we use Objective Toolkit classes and the namespace extension wizard to create a light version of the WinView namespace extension, which was developed by Dino Esposito in his book Visual C++ Window Shell Programming. For serious shell and namespace extension development, this book is a must. As you will see, the framework created by our namespace wizard makes the development much easier.
Create the Skeleton Namespace Extension Project
The first step is to create a skeleton namespace extension project as follows:
1. Create a DLL server type project using ATL COM AppWizard and naming the project NSExtTutor. Every namespace extension must be an in-proc server.
2. After the ATL COM AppWizard finishes, right-click the root node named NSExtTutor in the solution explorer.
3. Select Add, and click Add Class from the menu.
4. Select the Stingray category in the ATL Object Wizard, then select the Namespace extension icon in the objects list box and choose the Next button.
5. Give the namespace extension a name, NSExtComp, in the Short Name: edit box of the Names property page.
6. Select the Options page, choose the Custom option in the Interface section and No in the Aggregation section, and leave every other check box unchecked. Since we are not going to implement the interface INSExtComp, we also avoid the dual interface and aggregation support, making our extension lightweight.
7. Select the Namespace page and check the following options:
Desktop in the Show up section.
Current User in the Register For section.
IExtractIcon and IContextMenu in the Support UI Object section.
Verbose comment and Some sample code.
After you click OK, the wizard will generate a working namespace extension object.
Now you can compile and register the namespace extension we just created. After the compilation is done, you can open Explorer and your namespace extension NSExtComp will show up under the Desktop node. Figure 167 demonstrates what it looks like.
Figure 167 – Example Namespace Extension
Work Through the Generated Code
The Wizard generates five classes for our namespace extension: CNSExtComp, CNSExtCompEnum, CNSExtCompIcon, CNSExtCompMenu, and CNSExtCompView.
CNSExtComp is the main class of our namespace extension. It implements the IShellFolder interface and handles the registration for our namespace extension.
CNSExtCompEnum implements the IEnumIDList interface.
CNSExtCompIcon implements the IExtractIcon interface.
CNSExtCompMenu implements the IContextMenu interface—making the menu addition and handling extremely easy.
CNSExtCompView implements the IShellView interface.
As we mentioned before, only CNSExtComp, CNSExtCompEnum and CNSExtCompView are required for a namespace extension. As documented in the shell SDK, the Explorer starts to interact with our namespace extension COM object through the IShellFolder interface. Through this interface, Explorer obtains IEnumIDList and IShellView interfaces. The relationship of IEnumIDList to IShellView in the namespace extension is very similar to that of CDocument to CView in standard doc/view application, with IEnumIDList behaving as a kind of document and IShellView behaving as a view.
NOTE >> Do not try to implement required interfaces or optional interfaces inside a single class; it will not work properly.
The CNSExtCompEnum class extends the SECIEnumIDListImpl template class and implements the CreateEnumIDList() method. The first step in extending the skeleton namespace extension is to add a meaningful implementation for this virtual function. The base class has a variable, m_pPidlMgr, that is a pointer to our PIDL manager class. The PIDL manager is derived from SECPidlMgr template class. All the PIDL should be created and freed using the PIDL manager; the PIDL manager in turn uses the IMallc interface from the shell to manage the memory. This class is responsible for creating the PIDL list for the namespace extension. The framework requires that our CNSExtCompEnum class have a default constructor. When Explorer asks IShellFolder for IEnumIDList interface, the framework creates a new instance of IEnumIDList implementation class, and immediately calls CreateEnumIDList() once, passing the PIDL and some flags to this functions, and then returns the interface IEnumIDList to Explorer if the CreateEnumIDList() has succeeded.
The CNSExtCompView class extends the SECIShellViewImpl template class. This class must have a constructor with two arguments; the first argument is a pointer to the IShellFolder interface and the second argument is a pointer to PIDL. When Explorer calls the CreateViewObject() method of IShellFolder interface, the framework creates a new instance of our IShellView implementation class with the IShellFolder pointer and the PIDL, then returns the interface to Explorer if everything is all right. CNSExtCompView is responsible for providing a view window to the Explorer shell, so it is derived from CWindowImpl class as well. Generally, we should handle the WM_CREATE() and WM_SIZE() messages to create a child window, such as a list view to display our folder contents, and resize the child window. Then we can pass all other messages to the base class for further processing. The example CNSExtCompView class uses a CListView as child window; this window is held in the m_listView member. You can use any other kind of child window. The base class SECIShellViewImpl saves the argument IShellFolder into a smart pointer and makes a copy of the argument PIDL.
The CNSExtComp class simplifies registration by extending the SECIShellFolderImpl class and using the ATL COM object implementation. Our SECIShellFolderImpl class takes four template arguments. The first one is our final implementation class, the second is our IShellView implementation class, the third is our IEnumIDList implementation class, and the last one is our PIDL manager class. Since we chose to support IExtractIcon and IContextMenu when we run the namespace extension wizard, the wizard added some code into the GetUIObject() method to inform the caller(i.e. Explorer) that we support these optional interfaces.
Since we have chosen to support two optional interfaces, IContextMenu and IExtractIcon, the wizard also generated classes for each of these two interface implementations. CNSExtCompIcon basically just initializes its base class; CNSExtCompMenu contains the command map definition and some sample code demonstrating adding menu items to the context menu and adding a menu command. The command map cannot be removed, even you don’t have any command items inside.
If we had chosen to support other optional interfaces, the wizard would have generated the corresponding implementation classes.
Change The Data Structure For PIDL
Now let us begin to make our skeleton namespace extension functional. First, we have to decide what data to put into the PIDL. Since Explorer doesn’t know anything of our data, it just passes the data around as PIDL. We must therefore package our data completely into a PIDL. The data in the PIDL cannot be pointers that point to some other data outside the PIDL. For this tutorial, we will use the following structure.
 
struct CMyData
{
HWND hWnd;
};
Go to NSExtComp.h and change the following structure into our new data structure.
 
struct YourDataStruct
{
DWORD data;
};
After changing the data structure, you must also make two other changes. One of them is the #define immediately following the structure.
 
typedef YourDataStruct SECPidlData;
You should change the above line to the following:
 
typedef CMyData SECPidlData;
The second change is in the CreateEnumIDList() function of CNSExtCompEnum class. You should remove the code that uses the old data structure.
Implementing CreateEnumIDList()
Since we are going to enumerate windows, we need a helper data structure for the enumeration. Let’s add the following global data structure at the top of NSExtComp.h.
 
struct ENUMHWND {
LPARAM lParam;
HWND hwndParent;
DWORD dwFlags;
};
After that, we need to change the implementation of CreateEnumIDList(). The new implementation should be similar to the following.
 
virtual BOOL CreateEnumIDList(LPCITEMIDLIST pidl,
DWORD dwFlags)
{
HWND hWnd = NULL;
if( pidl != NULL )
{
//The GetLastDataPointer method of
// SECPidlMgr<SCEPidlData> will
//return a pointer to our data structure SECPidlData.
hWnd = m_pPidlMgr->GetLastDataPointer(pidl)->hWnd;
}
if( hWnd == NULL )
{
// We start with the desktop window
SECPidlData data;
data.hWnd = GetDesktopWindow();
AddToEnumList(m_pPidlMgr->CreateItem
( (LPBYTE)&data, sizeof(data)) );
 
//Initialize the current iterator
m_iterCurrent = m_idlList.begin();
 
//Return TRUE if succeeded, otherwise return FALSE.
return TRUE;
 
}
ENUMHWND ew;
ew.lParam = (LPARAM)this;
ew.hwndParent = hWnd;
ew.dwFlags = dwFlags;
 
::EnumChildWindows(hWnd, CreateEnumIDListHelper,
(LPARAM)&ew);
 
//Intialize the current iterator
m_iterCurrent = m_idlList.begin();
 
//Return TRUE if succeeded, otherwise return FALSE.
return TRUE;
}
One important point to notice is that when you use the CreateItem() function of SECPidlMgr class, you need to package the data into an SECPidlData structure and pass the structure to CreateItem() function together with the actual size of the data. If you look at the implementation of CreateItem(), you will see that it copies the block of data passed into it. To complete the implementation of CreateEnumIDList()), we need another static member function for the EnumChildWindow() method. The implementation of CreateEnumIDListHelper() follows.
NOTE >> This method uses CreateItem() exactly as in the CreateEnumIDList() method.
static BOOL CALLBACK CreateEnumIDListHelper(HWND hwndChild,
LPARAM lParam)
{
ENUMHWND* pew = (ENUMHWND*)(lParam);
HWND h = ::GetParent(hwndChild);
if( h != NULL && (h != pew->hwndParent) )
return TRUE;
CNSExtCompEnum* pEnumIDList = (CNSExtCompEnum*)(pew->lParam);
 
// Explorer wants non-folder items
if(pew->dwFlags & SHCONTF_NONFOLDERS)
{
SECPidlData data;
data.hWnd = hwndChild;
pEnumIDList->AddToEnumList(
pEnumIDList->m_pPidlMgr->CreateItem((LPBYTE)&data, sizeof(data)) );
return TRUE;
}
 
// Explorer wants folder items
if(pew->dwFlags & SHCONTF_FOLDERS)
{
// If it has no children, drop it because it has
// already been added.
if(::GetWindow(hwndChild, GW_CHILD) == NULL)
return TRUE;
else
{
SECPidlData data;
data.hWnd = hwndChild;
pEnumIDList->AddToEnumList(
pEnumIDList->m_pPidlMgr->CreateItem((LPBYTE)&data,
sizeof(data)) );
}
}
return TRUE;
}
 
The last change we need to make is in the InitList() method of CNSExtCompView class. The old data structure is used in this method.
Modify CNSExtCompView::InitList()
Let’s first make the simplest change to this method by changing one line of code to use our new data structure instead of the old one. The change is in bold text.
 
void InitList()
{
// large portion of code omitted here
 
spEnumIDList->Reset();
while((spEnumIDList->Next(1, &pidl, &dwFetched)
== S_OK) && dwFetched)
{
 
// Add code here to add item to the listview !!!
wsprintf(buf, _T("As binary: 0x%x"),
m_pPidlMgr->GetLastDataPointer(pidl)->hWnd);
 
m_listView.InsertItem(LVIF_TEXT,
m_listView.GetItemCount(), buf, -1, -1, 0, NULL);
}
// remainder of code omitted here
}
After we recompile and register the namespace, it will look something like Figure 168 in the Explorer.
Figure 168 – Recompiled and Registered Namespace
Notice that a context menu for the node appears in the left pane of Explorer, because we chose to support context menus when we ran the wizard. The wizard generated two sample context menu items for us. The default handler displays the menu item ID in a message box.
If you want the menu command to do meaningful things, you can override the CommandHandler() method of SECIContextMenuImpl class in the CNSExtCompMenu class; see the sample code shown inside the comment of this class. As you can see, the SECIContextMenuImpl class hides all the complications of context menu implementation. All you have to do is to add the menu item into the existing command map. You give the menu item text, help string, and the menu item ID, and then override the CommandHandler() method to look for your menu ID to decide what to do. It is really simple and easy. Please note that the context menu on the right pane is the responsibility of CNSExtCompView. You have to handle the WM_CONTEXTMENU() message and draw the context menu.
Figure 169 – Namespace With Context Menu
To make it more interesting, perform the following modification to the InitList() method.
 
void InitList()
{
// Empty the list view
m_listView.DeleteAllItems();
 
m_listView.InsertColumn(0, _T("State"),
LVCFMT_LEFT | LVCF_TEXT, 100, -1);
m_listView.InsertColumn(1, _T("Class Name"),
LVCFMT_LEFT | LVCF_TEXT, 100, -1);
m_listView.InsertColumn(2, _T("HWND"),
LVCFMT_LEFT | LVCF_TEXT, 100, -1);
m_listView.InsertColumn(3, _T("Title"),
LVCFMT_LEFT | LVCF_TEXT, 100, -1);
 
CComPtr<IEnumIDList> spEnumIDList;
 
// Here we call this function to get an IEnumIDList interface.
// This function call will call CreateEnumIDList to create the
//EnumIDList form the content of the m_pPidl !!!
HRESULT hr = m_spShellFolder->EnumObjects(m_hWnd,
SHCONTF_NONFOLDERS | SHCONTF_FOLDERS,
&spEnumIDList);
 
if(SUCCEEDED(hr))
{
LPITEMIDLIST pidl = NULL;
 
// Stop redrawing to avoid flickering
m_listView.SetRedraw(FALSE);
 
TCHAR buf[100] = {0};
 
// Add items
DWORD dwFetched;
 
//First reset the Enum
spEnumIDList->Reset();
while((spEnumIDList->Next(1, &pidl, &dwFetched)
== S_OK) && dwFetched)
{
HWND h = m_pPidlMgr->GetLastDataPointer(pidl)->hWnd;
 
// Column 1: state
// If has children
if( ::GetWindow(h, GW_CHILD) != NULL )
_tcsncpy(buf, _T("[Parent]"), 100);
else
_tcsncpy(buf, _T("No Children"), 100);
 
int i = m_listView.InsertItem(LVIF_TEXT,
m_listView.GetItemCount(), buf, -1, -1, -1, NULL);
 
// Fill the subitem 2: HWND
TCHAR szBuf[MAX_PATH] = {0};
wsprintf(szBuf, _T("0x%04X"), h);
m_listView.SetItemText(i, 2, szBuf);
 
// Fill the subitem 3: Title
::GetWindowText(h, szBuf, MAX_PATH);
m_listView.SetItemText(i, 3, szBuf);
 
// Fill the subitem 1: Class
::GetClassName(h, szBuf, MAX_PATH);
m_listView.SetItemText(i, 1, szBuf);
}
// Redraw the list view
m_listView.SetRedraw();
m_listView.Invalidate();
m_listView.UpdateWindow();
// Dont't call spEnumIDList->Release() here,
// spEnumIDList will automatically
// released when it go out of this scope!!!
}
}
Now if we recompile and register the namespace extension, it looks something similar to the following image in the Explorer.
Figure 170 – Namespace Extension After Recompilation
Give Each Node an Informative Name
The node name in the left pane of Explorer should be more informative. To correct this, we need to override the GetPidlName() method of SECIShellFolderImpl() method in the CNSExtComp class. The default implementation of this function displays the first byte data of the PIDL corresponding to the node as a binary. Let’s override GetPidlName() in CNSExtComp and change the code to the following.
 
virtual void GetPidlName(LPCITEMIDLIST pidl,
LPTSTR lpszOut, DWORD nSize)
{
HWND hwnd = NULL;
if( pidl != NULL )
hwnd = m_pPidlMgr->GetLastDataPointer(pidl)->hWnd;
 
if( hwnd == NULL )
hwnd = GetDesktopWindow();
 
TCHAR szClass[100] = {0}, szTitle[100] = {0};
::GetWindowText(hwnd, szTitle, 100);
::GetClassName(hwnd, szClass, 100);
 
// Add a description to the desktop window (of class "#32769")
if(!_tcscmp(szClass, _T("#32769")))
_tcscpy(szClass, _T("Desktop"));
 
// Return a string in the form "title [class]"
if(_tcslen(szTitle))
{
TCHAR buf[210] = {0};
_stprintf(buf, _T("%s [%s]"), szTitle, szClass);
_tcsncpy(lpszOut, buf, nSize);
}
else
{
_stprintf(szTitle, _T("[%s]"), szClass);
_tcsncpy(lpszOut, szTitle, nSize);
}
}
Since we know the data in each item is a window handle, we should display the window’s class name, or, if it has a title, the combination of class name and title. If the handle is NULL, it means we are at the top node, which we know it is the desktop window. After we recompile, the namespace looks like Figure 171.
Figure 171 – Renamed Namespace
Change the Default Context Menu Handling
As we saw before, the context menu didn’t do anything other than display the corresponding menu item ID. In order to make the context menu useful, we have to override the CommandHandler() function. Let’s uncomment the CommandHandler() method in the CNSExtCompMenu class generated by our wizard. Assume we want to display the information about the node the mouse right-clicked on. For this tutorial, we want to display the Windows class name and the window handle as binary data. In order for our context menu to display information about the clicked node, we need to save the PIDL for the node passed to us by Explorer when it requested the IContextMenu interface in the GetUIObject() method of IShellFolder. The PIDL is available from our class CNSExtCompMenu in the GetUIObject() method. GetUIObject() also saves the owner window handle into our class. This window handle can be used when we want to display a dialog box or message box inside our CNSExtCompMenu implementation code. The implementation of CommandHandler() is listed below.
 
virtual void CommandHandler(HWND hwndOwner, UINT nID)
{
if( nID == ID_SAMPLE_COMMAND ||
nID == ID_SAMPLE_COMMAND1)
{
TCHAR buf[MAX_PATH] = {0};
GetPidlName(m_pPidl, buf, MAX_PATH);
 
TCHAR buf1[100] = {0};
HWND hWnd = NULL;
if( m_pPidl == NULL )
hWnd = GetDesktopWindow();
else
hWnd =
m_pPidlMgr->GetLastDataPointer(m_pPidl)->hWnd;
 
stprintf(buf1, _T("\n\nWindow handle = 0x%x"),
(DWORD)hWnd);
 
int len = _tcslen(buf);
_tcsncpy(buf + len, buf1, MAX_PATH - len);
 
TCHAR szTitle[100] = {0};
_stprintf(szTitle, _T("Sample command %d selected"),
nID==ID_SAMPLE_COMMAND ? 1 : 2);
MessageBox(hwndOwner, buf, szTitle,
MB_OK | MB_ICONINFORMATION);
}
}
This function makes uses the GetPidlName() method, which we can copy from CNSExtComp. The m_pPidlmember is in our base class. Now when you right-click any node and select one of the two menu items, the message box will display the relevant information for this node. The context menu is shown in Figure 172.
Figure 172 – Namespace With Context Menu Handling
Give Node An Icon
The last thing we are going to do is to give the node our own icon. The default implementation of SECIExtractIconImpl gives every node a small Windows logo icon. To provide a different icon for the node on the left pane, we have to override the Extract() function of IExtractIcon interface. First, add a small icon into our project and name the icon ID to IDI_ICONNODE, as shown in Figure 173.
Figure 173 – Icon for ID IDI_ICONNODE
Then we change the default implementation of Extract() method in CNSExtCompIcon class to the following:
 
STDMETHOD (Extract)(LPCTSTR pszFile, UINT nIconIndex,
HICON* phiconLarge, HICON* phiconSmall,
UINT nIconSize)
{
*phiconLarge = LoadIcon(_Module.GetResourceInstance(),
MAKEINTRESOURCE(IDI_ICONNODE));
*phiconSmall = LoadIcon(_Module.GetResourceInstance(),
The completed namespace is shown in Figure 174.
Figure 174 – Namespace With Icon
That’s all for this tutorial. You can add more features to the namespace extension, such as modifying the menu of Explorer, adding toolbars into Explorer, and displaying status text. We strongly recommend the excellent reference Visual C++ Windows Shell Programming by Dino Esposito, published by Wrox Press, as well as the MSDN.