Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Context Menu Shell Extension AppWizard

0.00/5 (No votes)
21 Nov 2002 14  
A wizard to ease implementing a context menu shell extension

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.

 [VC New dialog - 5K]

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.

 [AppWizard dialog - 10K]

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:

// IContextMenuImpl menu item maps

BEGIN_MENU_ITEM_MAP()
    // TODO: Add menu item entries

    // Menu item entries take

    // 1. a string resource identifier for the menu item name

    // 2. a string resource identifier for the menu item description

    // Ex: MENU_ITEM_ENTRY(IDS_ITEM1, IDS_DESC1)

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:

      ...
    // 2. a string resource identifier for the menu item description

    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()
    // TODO: Add menu command entries

    // Menu command entries take:

    // 1. a string resource identifier for the menu item name

    // 2. the name of a member function triggered upon selection

    // Ex: MENU_COMMAND_ENTRY(IDS_ITEM1, OnItem1)

END_MENU_COMMAND_MAP()

Again, the comments make it clear what your duties are. For our example, we will uncomment the last line, thus:

    ...
    // 2. the name of a member function triggered upon selection

    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:

// Menu item handlers

protected:
    // TODO: Add menu item handlers

    // Menu item handlers bear the following signature

    // Ex: HRESULT OnItem1(LPCMINVOKECOMMANDINFO lpici);

    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"


//////////////////////////////////////////////////////////////

// CShellExt

class ATL_NO_VTABLE CShellExt : 
    ...
    public IShellExtInitImpl,
{
DECLARE_REGISTRY_CONTEXTMENU_EXTENSION(IDR_CSHELLEXT, txt, 
                                       SimpleShlExt, CLSID_CShellExt)
...
// IShellExtInitImpl override

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"


////////////////////////////////////////////////////////////////////////////

// CShellExt

class ATL_NO_VTABLE CShellExt : 
    ...
    public IContextMenuImpl<CShellExt>
{
...
// Menu item handlers

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;
    }

// IContextMenuImpl menu item maps

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

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