This article aims at making implementation of a context menu shell extension trivial. This article was inspired
by Michael Dunn's most excellent series of tutorials
about writing shell extensions. I strongly encourage you to read through it in order to have a good understanding
of what follows.
Being already confident with ATL and C++, I had however previously no knowledge in implementing shell extensions,
but following Michael Dunn's instructions was absolutely effortless and very easy. However, implementing a shell
extension usually means typing the same code over and over again. This makes it a rather tedious and error-prone
task.
Therefore, a wizard is kind of an ideal candidate for providing the skeleton of the necessary source code.
Installing and running the Wizard
The Context Menu Shell Extension Wizard comes in a self-contained CTXMENUAPP.AWX file that needs to be copied
in your template folder. The template folder can be found under your main Microsoft Visual Studio installation
folder, under the \Common\MSDev98\Template folder.
The wizard is activated by using the File|New menu item that triggers a New Projects dialog box.
Selecting the Context Menu Shell Extension item brings the following wizard dialog box. This step allows
you to specify the type of files the shell extension will be invoked for, as well as the C++ class name that will
be generated by the wizard.
What does the Wizard give you?
The wizard produces a Visual Studio project similar to those created by the ATL/COM AppWizard.
The main difference is that the project already contains a class implementing the skeleton of a context menu extension.
Recall from Michael Dunn's tutorial that a context menu shell extension is a COM object that should implement
the IShellExtInit
and IContextMenu
interfaces. The class generated by the wizard already
provides all that is required to support these implementations in the form of two header files atlshellex.h
and atlctxmenu.h
that I will describe later.
Let's start with a sample
Let's attempt to write a shell extension similar to the one described in the tutorial to see how easy it goes.
From the screen shot above, notice that we choose to hook our shell extension to .TXT files, just like Michael's.
Notice also that the class in which the extension will be implemented in our example is called CShellExt
.
From Michael's requirements, we need to add a single command in the context menu that will be displayed by the
shell when triggered on a .TXT file. This command should also provide a friendly fly-by status help string. To
achieve this, just add two string resources to your project, one for the menu item text itself, and one for the
fly-by help string. In this example, we add the following strings:
- IDS_ITEM1: "SimpleShlExt Test Item"
- IDS_DESC1: "This is the simple shell extension's help"
Then we need to provide support for the menu item in our CShellExt
class. Open up the class header
file by double clicking on the class name in the project window's ClassView tab. Locate the context menu map
that looks like:
BEGIN_MENU_ITEM_MAP()
END_MENU_ITEM_MAP()
The comment makes it clear what tasks you have to perform in order to support adding menu items in the shell's
context menu. For our example, just uncomment the last line in the MENU_ITEM_MAP
, thus:
...
MENU_ITEM_ENTRY(IDS_ITEM1, IDS_DESC1)
END_MENU_ITEM_MAP()
At this stage, we have a functional shell extension, but we also need to add code that will be executed when
the menu item is selected. As in Michael's example, we will show the name of the file which was right-clicked.
Locate the menu command map in the CShellExt
's declaration:
BEGIN_MENU_COMMAND_MAP()
END_MENU_COMMAND_MAP()
Again, the comments make it clear what your duties are. For our example, we will uncomment the last line, thus:
...
MENU_COMMAND_ENTRY(IDS_ITEM1, OnItem1)
END_MENU_COMMAND_MAP()
The last part is to actually provide an implementation for the menu item handler, by writing the OnItem1
function. We will add those lines to the class declaration:
protected:
HRESULT OnItem1(LPCMINVOKECOMMANDINFO lpici)
{
TCHAR szText[1024];
lstrcpy(szText, _T("The selected file was:\n\n"));
lstrcat(szText, m_files[0]);
::MessageBox(lpici->hwnd, szText, _T("SimpleShlExt"),
MB_ICONINFORMATION);
return S_OK;
}
We're done! To give it a try, just compile the shell extension. Launch the Windows Explorer and try right-clicking
on a .TXT file... So there you have it. We have a completely functional context menu shell extension, that is also
automatically registered for us to the correct file types and that allows us to concentrate only on the functionality
of the shell extension.
How does it work?
If you paid attention to the few lines we added for the OnItem1
handler, you may have noticed the
mysterious m_files[0]
member variable. Where does it come from?
You also noticed that the shell extension was automatically registered for us to the correct file extension.
If you recall from Michael's article, this involved dealing with a .RGS file and wasn't particularly easy to handle.
So how it this achieved?
The answer lies in two header files atlshellex.h
and atlctxmenu.h
, that are silently
included with your shell extension as part of the Wizard creation. These files provide the infrastructure to implement
the required COM interfaces discussed above, IShellExtInit
and IContextMenu
.
IShellExtInitImpl
If you look back at the CShellExt
class declaration, you may have noticed the following lines of
code:
#include "atlshellex.h"
class ATL_NO_VTABLE CShellExt :
...
public IShellExtInitImpl,
{
DECLARE_REGISTRY_CONTEXTMENU_EXTENSION(IDR_CSHELLEXT, txt,
SimpleShlExt, CLSID_CShellExt)
...
protected:
bool IsValidFile(CString& aFile) const { return TRUE; }
...
In fact, your ATL class multiply-inherits from IShellExtInitImpl
which, in pure ATL style, implements
parts of the IShellExtInit
interface. This is the class that provides support for automatic registration
of the shell extension via the DECLARE_REGISTRY_CONTEXTMENU_EXTENSION
macro.
This class is responsible for maintaining an array of files that were selected when right-clicking in the shell.
This is the mysterious m_files
variable discussed above. In fact, shell extensions can work on multiple
files at once. Therefore we need to keep track of them all.
You can customize the behavior of this class by overriding the IsValidFile
member function in your
class. Notice that the Wizard created a simple implementation of this override that simply returns TRUE, whatever
the file. This means that all files will be valid and added to the m_files
array. With this function,
you can alter the behavior by testing the file before deciding whether it should be inserted or not. Simply return
FALSE if you don't want the file added to the array.
IContextMenuImpl
Looking back at the CShellExt
class declaration also reveals the presence of IContextMenuImpl<CShellExt>
multiple-inheritance.
#include "atlctxmenu.h"
class ATL_NO_VTABLE CShellExt :
...
public IContextMenuImpl<CShellExt>
{
...
protected:
HRESULT OnItem1(LPCMINVOKECOMMANDINFO lpici)
{
TCHAR szText[1024];
lstrcpy(szText, _T("The selected file was:\n\n"));
lstrcat(szText, m_files[0]);
::MessageBox(lpici->hwnd, szText, _T("SimpleShlExt"),
MB_ICONINFORMATION);
return S_OK;
}
BEGIN_MENU_ITEM_MAP()
MENU_ITEM_ENTRY(IDS_ITEM1, IDS_DESC1)
END_MENU_ITEM_MAP()
BEGIN_MENU_COMMAND_MAP()
MENU_COMMAND_ENTRY(IDS_ITEM1, OnItem1)
END_MENU_COMMAND_MAP()
};
The IContextMenuImpl
class takes responsibility for implementing the IContextMenu
interface. It will walk the menu item and menu command maps that you provide, and route the selection
code to the corresponding menu item handler.
Conclusion
As you have seen, implementing a context menu extension is now fast and easy. Besides, you can re-use the provided
atlctxmenu.h
and atlshellex.h
files in your own projects. This former file, atlshellex.h
implements the IShellExtInit
interface in a way that could also be directly relevant to implementing
Property Sheets Shell Extension, but I leave this as an exercise to the readers.
I hope this article is clear enough. Please let me know if you have some trouble.
History
20 Novemeber 2002 - updated downloads