Search the Catalog
Visual Basic Shell Programming

Visual Basic Shell Programming

Integrating Applications with the Windows Shell

By J. P. Hamilton
1st Edition July 2000
1-56592-670-6, Order Number: 6706
396 pages, $29.95

Chapter 4
Context Menu Handlers

The shell displays a context menu for a file object when it is clicked with the right mouse button. This context menu allows various operations to be performed on the file object from within the shell, like printing it or opening it with another program. For example, Figure 4-1 shows the context menu that's displayed when the user clicks on a file in Windows Explorer.

Figure 4-1. A context menu

 

The items on context menus fall into two categories: static and dynamic. Static context menu items are always the same for every file object of a given type. They can be associated with a file object with just a few registry entries and require no shell extension handlers. The "handler" in this circumstance--that is, the object that performs some action on the file object when that particular context menu item is selected--is usually a normal executable that is passed the name of the file as a command-line parameter. Dynamic context menus, on the other hand, are created with the help of a shell extension handler, which, as we discussed earlier, is a COM component that runs in-process to Explorer. This handler provides the means to display different context menu items for file objects of the same type. The exact appearance of the context menu typically is determined by some state internal to the file itself. Static menus warrant a brief discussion, but the main focus of this chapter will be on dynamic context menus.

Static Context Menus

Static context menu items are listed under the application identifier key under a subkey called shell (as opposed to the shellex key). These entries remain constant for every instance of the file object and require no implementation code.

Figure 4-2 illustrates how to add an Open context menu item to the .rad file. The subkey of shell (in this case open) is the verb value for the command. There are seven verbs, called canonical verbs, whose meaning is automatically recognized by the shell: open, find, explore, print, printto, openas, and properties. (The printto key is never shown in a context menu, but allows a file to be dragged to a printer object for printing.)

Figure 4-2. Registry entry for static "Open" context menu

 

The default value of the verb key contains the text for the context menu; in the case of Figure 4-2, the open verb is described in the context menu as "Open." The verb key's subkey is the command key, whose default value contains the path of the file that will be used to carry out the command. The %1 portion of this string in Figure 4-2 denotes the file that was selected within the shell. Whatever file is selected will be passed to notepad.exe on the command line. Of course, this only works because notepad.exe accepts command-line arguments.

However, don't believe for a second that you are limited to these seven canonical verbs. You can actually add you own commands to the context menu and call them anything you want. For example, let's add Register and Unregister commands to the context menu for DLLs. This will provide us with a convenient way to register and unregister components.

To accomplish this, we need to locate the application identifier key for a DLL, which happens to be dllfile. Then, under the shell subkey, we add two other keys: Register and Unregister. Figure 4-3 shows how the relevant portion of the registry should appear in order to support these two static commands.

Figure 4-3. Static menu handlers to register and unregister DLLs

 

As you can see from Figure 4-2, we must also add an additional subkey named command . The default value for this key will contain the command that we actually want to execute. The following script, DLLRegister.reg, will do everything for you:

REGEDIT4
 
[HKEY_CLASSES_ROOT\dllfile\shell\Unregister\command]
@="regsvr32.exe /u %1"
 
[HKEY_CLASSES_ROOT\dllfile\shell\Register\command]
@="regsvr32.exe %1"

Static Context Menus in IE 5.0

With the release of Internet Explorer 5.0, Microsoft has made it possible for you to define your own static context menu items. It's as simple as adding a new registry key at the following location:

HKEY_CURRENT_USER
    SOFTWARE
        Microsoft
            Internet Explorer
                MenuExt

The default value for the key can be either a URL or a program. An additional key called contexts must also be present. This key contains a binary value that determines to which context menu (Internet Explorer provides several, depending on the circumstances) you want to add the new menu item. The values are:

Context Menu

Value

Default

0x01

Image

0x02

ActiveX Control

0x04

Table

0x08

Selected Text

0x10

Hyperlink

0x20

Dynamic Context Menus

Static context menus are limited because they are the same for every file object of a given type. Also, the number of files that can be processed through a static menu is limited by the program that is used to carry out the command. What if you need to process 20 files? What if you need different processing options based on the state of the file itself? There are also situations where you might need one context menu for a group of files and another for a single file. This is where dynamic context menus come into play.

A context menu handler is an ActiveX DLL that implements two interfaces: IShellExtInit and IContextMenu. A third interface, IDataObject, is required to implement IShellExtInit. It is not implemented by the object itself but exists as a method parameter in IShellExtInit. We'll explore these interfaces in greater depth after we examine how the shell uses a context menu handler to assemble a context menu.

The process begins when one or more files is right-clicked in Explorer. When this occurs, the shell checks the shellex key under the application identifier key to see if a context menu handler has been defined for the selected file type. In the case of the .rad file, the shell would look under the following key:

HKEY_CLASSES_ROOT/
    radfile/
        shellex/
            ContextMenuHandlers/

If you select 15 files that are of all different types, there is still only one file with active focus: the last file selected in the group. It is this file for which the shell attempts to find an associated context menu handler.

If a context menu handler exists, the shell loads the handler and calls IShellExtInit::Initialize. One of the parameters of Initialize is a reference to IDataObject. The shell uses IDataObject to tell us how many files are selected and what their names happen to be. This gives us the opportunity (as the implementors of IShellExtInit) to save the filenames and the number of selected files for later use. This information can be stored in private member variables within the class. Later, when a command is actually selected from the context menu, the array of files can be referenced and processing decisions can be made.

Next, the shell calls IContextMenu::QueryContextMenu. This method is responsible for adding items to the context menu. The shell passes into the method a handle to the context menu, called an HMENU. An index representing a valid insertion point for the menu item is also passed in. Adding the menu item is simply a matter of calling the InsertMenu API.

You might want different menu items displayed based on whether one or multiple files have been selected. Since the number of files selected can be determined in IShellExtInit::Initialize, this becomes a trivial matter. You also have the ability to base the menu item on the file itself. In addition to the number of files selected, you would also already know the filenames in question. This means you could open the file, retrieve information, and base the menu item on actual data. Or you could examine some other attribute of the file (such as its creation date, its size, or its read-only status) and base the menu item on that information as well.

At this point, the shell displays the context menu with the additional menu items. Once the context menu is displayed, the shell attempts to call IContextMenu::GetCommandString whenever the mouse is moved over the new context menu item. This allows you to provide a help string that will be displayed in the status bar of Explorer when the context menu item is highlighted.

When the command is actually selected, the shell calls IContextMenu::InvokeCommand on the handler. The method allows you to determine which context menu item has been selected, and as a result your handler can carry out the appropriate actions.

Context Menu Handler Interfaces

The components we will write in this book will all implement any given number of system interfaces. "System" in this context (no pun intended) means that these interfaces have already been defined by Microsoft. They are documented, and you can read all about them in the Platform SDK (though the details may be a little murky sometimes).

You can think of an interface as a defined functionality. When a component implements an interface, it is really saying, "I support this functionality!" Consider a Triangle component. It implements the interface Shape. Shape defines two methods: Draw and Color. Therefore, you could expect to access the following functionality through Triangle:

Triangle.Draw
Triangle.Color

Because the Circle, Square, and Trapezoid components also implement Shape, you would expect these objects to have the same functionality as well. This is what it means to implement an interface.

The components in this book all implement some functionality that is required by the shell. This means that when the shell loads our components, it will be able to gain access to our component through a defined mechanism: an interface.

With that said, let's talk about the interfaces a context menu handler component needs to implement before it can be loaded by the shell.

IShellExtInit

IShellExtInit contains one method (besides the IUnknown portion of the interface), Initialize, as shown in Table 4-1.

Table 4-1: IShellExtInit

Method

Description

Initialize

Initializes the shell extension

IShellExtInit::Initialize is the first method called by the shell after it loads the context menu handler; it is the context menu handler's equivalent of a class constructor in C++ programming or the Class_Initialize event procedure of a class in VB. Typically, this method is used by the context menu handler to determine which file objects are currently selected within Explorer. Initialize is defined as follows:

HRESULT Initialize(LPCITEMIDLIST pidlFolder, 
		        IDataObject *lpdobj, 
		        HKEY hkeyProgID ); 

All three arguments are provided by the shell and passed to the context menu handler when it is invoked, which is indicated by the [in] notation in the following argument list. The three arguments are:

pidlFolder
[in] A pointer to an ITEMIDLIST structure (commonly referred to in shell parlance as a PIDL) with information about the folder containing the selected objects. If you want more information on PIDLs and what you can do with them, see Chapter 12, Browser Extensions. We are not going to use this member, and we are not even going to discuss it (yet), because the topic of PIDLs is a universe unto itself. All you need to know is that a PIDL provides a location of something (such as the path of a file or folder object) within the Windows namespace.

lpdobj
[in] A pointer to an IDataObject interface that provides information about the selected objects. The IDataObject interface is discussed in the following section.

hKeyProgID
[in] The handle of the registry key containing the programmatic identifier of the selected file. For instance, if a Word .doc file was right-clicked, hKeyProgID would be a handle to the HKEY_CLASSES_ROOT\Word.Document.8 key on systems with Office 2000 installed. Once the handle to this key is available, it is a trivial matter to find the host application that is responsible for dealing with this file type, which in the case of our example happens to be Microsoft Word. The context menu handler can then defer any operations to the host application, if necessary.

The only parameter in which we are interested is the second, lpdobj, which is a pointer to an IDataObject interface. Like the first parameter, IDataObject is also a world unto itself. Fortunately for us, we don't need to know too much about the interface at this juncture. In Chapter 8, Data Handlers, when we create a data handler, we will put this interface under the knife, so to speak, but until then let's just cover what we need to know. The shell uses this interface to communicate to us the files that were clicked on in Explorer. We'll see how this works momentarily.

Now that we know a little bit about this interface, let's get on to how we are actually going to implement it. There are some problems ahead.

IShellExtInit, like most of the interfaces in this book, is a VB-unfriendly interface. An unfriendly interface contains datatypes that are not automation compatible. You can think of an automation-compatible type as basically anything that will fit into a Variant. Table 4-2 lists all of the datatypes that are considered OLE automation compatible.

Table 4-2: OLE Automation-Compatible Types

Datatype

Description

boolean

Corresponds to the VB Boolean type

unsigned char

8-bit unsigned data item

double

64-bit IEEE floating-point number

float

32-bit IEEE floating-point number

int

Signed integer whose size is system-dependent

long

32-bit signed integer

short

16-bit signed integer

BSTR

Length-prefixed string; this is the String datatype in VB

CURRENCY

8-byte, fixed-point number

DATE

64-bit, floating-point fractional number of days since December 31, 1899

SCODE

Error code for 16-bit systems

Typedef enum myenum

Signed integer whose size is system-dependent

Interface IDispatch *

Pointer to the IDispatch interface

Interface IUnknown *

Any interface pointer that directly derives from IUnknown

dispinterface Typename *

Pointer to an interface derived from IDispatch

Co-class Typename *

Pointer to a co-class name

[oleautomation] interface Typename *

Pointer to an interface that derives from IUnknown

SAFEARRAY(TypeName)

Array of any of the preceding types

TypeName*

Pointer to any of the preceding types

Decimal

96-bit unsigned binary integer scaled by a variable power of 10 that provides a size and scale for a number (as in coordinates)

Now, to implement IShellExtInit successfully, the interface will have to be redefined with automation-compatible types and made available through a type library. This interface contains one method, Initialize. Let's tear it apart to see what we need to do in order to make this interface work for us.

Consider the first parameter of the Initialize method, which is an LPCITEMIDLIST. The documentation for the interface states that this is an address of an ITEMIDLIST. (We'll talk about ITEMIDLIST in Chapter 11, Namespace Extensions.) The structure is defined like this:

typedef struct _ITEMIDLIST {
        SHITEMID mkid;
    } ITEMIDLIST;

As you can see, the one and only member of this structure is another structure called SHITEMID, which is not an automation-compatible type. This means we cannot define this parameter as a pointer to an ITEMIDLIST when we define the IShellExtInit interface. What can we do? Well, a pointer is four bytes wide, so the automation-compatible type that can be used in place of LPCITEMIDLIST is a long. When we create our type library, we will just redefine LPCITEMIDLIST to mean a long, like so:

typedef [public] long LPCITEMIDLIST;

When we actually define the Initialize method (see Example 4-1), we can still use LPCITEMIDLIST for the datatype of the first parameter. Then, when VB displays the parameters for the method via IntelliSense, rather than seeing long, we will see LPCITEMIDLIST. This acts as a reminder of what the original definition is supposed to be.

We'll do the same thing for the third parameter, which is an HKEY. An HKEY is a handle to a registry key. Handles to anything are four bytes, so a long works in this case, too:

typedef [public] long HKEY;

We don't have to redefine anything as far as the second parameter goes. It's an IDataObject interface pointer. And interface pointers that are derived from IUnknown or IDispatch are automation compatible, so this portion of the definition is fine as is.

Let's talk about these parameters we have redefined for a moment. As it turns out, we will not need the first or the third parameters of this method in order to implement a context menu handler. But what if we did? After all, these types have been redefined as long values. Well, an HKEY is really a void pointer--that is, a pointer that does not point to any specific datatype. As a long, you can use this value as is with any of the registry API functions that take HKEYs.

How do we access the pointer to the ITEMIDLIST when all we have is a long value? We can use the RtlMoveMemory API (a.k.a. CopyMemory) to make a local copy of the UDT. This API call is defined like so:

Public Declare Sub CopyMemory Lib "kernel32" _ 
    Alias "RtlMoveMemory" (pDest As Any, _
                           pSource As Any, _ 
                           ByVal ByteLen As Long)

The code on the VB side would then look something like the following:

Private Sub IShellExtInit_Initialize(_ 
    ByVal pidlFolder As VBShellLib.LPCITEMIDLIST, _ 
    ByVal pDataObj As VBShellLib.IDataObject, _ 
    ByVal hKeyProgID As VBShellLib.HKEY)
 
    Dim idlist As ITEMIDLIST
 
    CopyMemory idlist, ByVal pidlFolder, len(idlist)

Notice, though, that the second parameter to CopyMemory (our ITEMIDLIST that has been redefined as a long) is passed to the function ByVal. This is because this long value represents a raw address. We'll talk more about this later, since we will use techniques similar to this throughout the course of this book.

Example 4-1 shows the modified definition for the IShellExtInit interface as it exists in our type library.

Example 4-1: IShellExtInit Interface

    typedef [public] long HKEY;
    typedef [public] long LPCITEMIDLIST;
 
	[
		uuid(000214E8-0000-0000-C000-000000000046),
		helpstring("IShellExtInit Interface"),
		odl
	]
	interface IShellExtInit : IUnknown
	{
		[helpstring("Initialize")]
		HRESULT Initialize([in] LPCITEMIDLIST pidlFolder, 
                              [in] IDataObject   *pDataObj, 
                              [in] HKEY          hKeyProgID);
	}

Structures and IDL

We had to redefine the ITEMIDLIST pointer for the IShellExtInit::Initialize method because the structure contained a non-automation-compatible type. However, there are some circumstances in which we'll deal with pointers to structures that contain types whose members are all automation compatible. In situations like these, we don't have to redefine the pointer to the structure as a long like we had to for ITEMIDLIST.

Consider the POINT structure, which is defined as follows:

typedef struct {
long x;
long y;
} POINT;

This structure's members are all automation compatible.

Let's assume we have an interface (this is hypothetical, of course) that contains a GetCoordinate method. This method returns a pointer to POINT that specifies the location of some object. In IDL, its definition looks like this:

HRESULT GetCoordinate([in] POINT *location)

If we were to implement this method in VB, we could do the following:

Private Sub IBattleShip_GetCoordinate(_
location As POINT)

location.x = 10
location.y = 20
 
End Sub

Instead of having to use CopyMemory to create a local copy of POINT, we can access the structure directly.

The [public] attribute used in Example 4-1 makes the typedef values available through the type library; otherwise, they would just be available for use inside of the library itself.

The [odl] attribute is required for all interfaces compiled with MKTYPLIB. MIDL supports this attribute as well, but only for the sake of backward compatibility. The attribute itself does absolutely nothing.

The [helpstring] attribute, as you can probably guess, denotes the text that will be displayed for a library or an interface from within Object Browser or the Project/References dialog.

The [in] attribute is known as a directional attribute. This indicates that the parameter is passed from the caller to the COM component. (In the case of our context menu handler, it indicates that the shell is passing our COM component a parameter.) Another attribute, [out], specifies the exact opposite, which is a parameter that is passed from the component to the caller. All parameters to a method have a directional attribute. This is either [in], [out], or [in, out]. But VB cannot handle [out]-only parameters. Parameters designated as [out] usually require the caller to free memory. VB likes to shield responsibility from the programmer whenever possible, especially when it comes to memory management.

Look at the GUID for IShellExtInit, (000214E8-0000-0000-C000-000000000046). This GUID comes straight from the registry. It has been defined by Microsoft as the GUID for IShellExtInit. It is important that you use the correct GUID for interfaces already defined by the system, because, after all, that is their true name. The GUID for the library block (see Appendix A, VBShell Library Listing ), on the other hand, can be anything since it's being defined by us--but not anything you can think of off the top of your head. Whenever you need to define your own GUID, you should use GUIDGEN (see Figure 4-4). GUIDGEN is a program used for generating GUIDs that guarantees them to be unique (theoretically) and copies them to the clipboard. GUIDGEN ships with Visual Studio, but if you don't have it, you can always make your own, as Example 4-2 demonstrates.

Figure 4-4. The GUIDGEN utility

 

Example 4-2: Source Code for a Self-Created GUIDGEN Utility

Option Explicit
 
Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type
 
Private Declare Function CoCreateGuid Lib "ole32.dll" _
    (g As GUID) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias _ 
    "RtlMoveMemory" (pDst As Any, pSrc As Any, _
    ByVal ByteLen As Long)
Private Declare Function StringFromCLSID Lib "ole32.dll" _ 
    (pClsid As GUID, lpszProgID As Long) As Long
 
Private Sub StrFromPtrW(pOLESTR As Long, strOut As String)
    
    Dim ByteArray(255) As Byte
    Dim intTemp As Integer
    Dim intCount As Integer
    Dim i As Integer
    
    intTemp = 1
    
    'Walk the string and retrieve the first byte of each WORD.
    While intTemp <> 0
        CopyMemory intTemp, ByVal pOLESTR + i, 2
        ByteArray(intCount) = intTemp
        intCount = intCount + 1
        i = i + 2
    Wend
    
    'Copy the byte array to our string.
    CopyMemory ByVal strOut, ByteArray(0), intCount
    
End Sub
 
Private Sub Command1_Click(  )
 
    Dim g As GUID
    Dim lsGuid As Long
    Dim sGuid As String * 40
    
    If CoCreateGuid(g) = 0 Then
        StringFromCLSID g, lsGuid
        StrFromPtrW lsGuid, sGuid
    End If
    
    InputBox "This is your GUID!", "GUID", sGuid
    
End Sub

Figuring out the details of this code is an exercise for you. However, this will be much easier to do after you have finished this book, since we will discuss all of the functions in this listing extensively.

IDataObject

IDataObject is not implemented by the context menu handler directly, but rather, it is a parameter to IShellExtInit::Initialize. Therefore, it has to be defined in the type library. IDataObject provides the means to determine which files have been right-clicked within the shell. IDataObject is a fairly complex interface that contains nine methods: GetData, GetDataHere, QueryData, GetCanonicalFormat, SetData, EnumFormatEtc, DAdvise, DUnadvise, and EnumDAdvise. This interface is the soul of OLE data transfers.

In regards to context menu handlers, there is only one method, GetData, that we will use to implement the extension. Its syntax is:

HRESULT GetData(FORMATETC * pFormatetc, STGMEDIUM * pmedium);

Its parameters are:

pFormatetc
[in] Pointer to a FORMATETC structure. The FORMATETC structure represents a generalized clipboard format. It's defined like this:

typedef struct {
long cfFormat;
long ptd;
DWORD dwAspect;
long lindex;
TYMED tymed;
} FORMATETC;

pmedium
[in] Pointer to a STGMEDIUM structure. STGMEDIUM is a generalized global-memory handle used for data-transfer operations. It is defined like this:

typedef struct tagSTGMEDIUM {
DWORD tymed;
union {
HBITMAP hBitmap;
HMETAFILEPICT hMetaFilePict;
HENHMETAFILE hEnhMetaFile;
HGLOBAL hGlobal;
LPWSTR lpszFileName;
IStream *pstm;
IStorage *pstg;
};
IUnknown *pUnkForRelease;
}STGMEDIUM;

Because VB does not support unions, our type library will contain a more generalized definition of this structure:

typedef struct {
TYMED tymed;
long pData;
IUnknown *pUnkForRelease;
} STGMEDIUM;

Admittedly, the discussion of FORMATETC and STGMEDIUM is rather cryptic here. This is intentional. When we implement IShellExtInit later in the chapter, just understand that the shell is using IDataObject to transfer a list of files to us. IDataObject is the primary interface involved in OLE data transfers. That's about all you need to know right now. We will learn much more about this interface in Chapter 8.

IContextMenu

As Table 4-3 shows, IContextMenu contains three methods: GetCommandString, InvokeCommand, and QueryContextMenu. This is the core of the context menu handler. The methods of this interface provide the means to add items to a file object's context menu, display help text in Explorer's status bar, and execute the selected command, respectively. We'll discuss each of these methods in turn.

Table 4-3: IContextMenu

Method

Description

GetCommandString

Returns the help string that Explorer will display in the status bar.

InvokeCommand

Implements menu commands when the menu items are selected.

QueryContextMenu

Adds items to the context menu.

GetCommandString

GetCommandString allows the handler to specify the text that will be displayed in the status bar of Explorer. This occurs when a particular context menu item is selected. Its syntax is:

HRESULT GetCommandString(
    UINT idCmd,		
    UINT uFlags,
    UINT *pwReserved,
    LPSTR pszName,
    UINT cchMax
   );

Its parameters are:

idCmd
The ordinal position of the selected menu item.

uFlags
A flag specifying the information to return.

pwReserved
Unused; handlers must ignore this parameter, which should be set to NULL.

pszName
A pointer to the string buffer that holds the null-terminated string to be displayed.

cchMax
Size of the buffer defined by pszName.

When the method is invoked by the shell, the shell passes the following items of information to the GetCommandString method:

The method can then place the desired string in the pszName buffer. As a general rule, the string should be 40 characters or less and should not exceed cchMax.

InvokeCommand

The shell calls this method to execute the command selected in the context menu. Its syntax is:

HRESULT InvokeCommand(LPCMINVOKECOMMANDINFO lpici);

with the following parameter:

lpici
A pointer to a CMINVOKECOMMANDINFO structure that contains information about the command to execute when the menu item is selected.

The CMINVOKECOMMANDINFO structure is defined in the Platform SDK as follows:

typedef struct _CMInvokeCommandInfo{ 
    DWORD cbSize; 
    DWORD fMask; 
    HWND hwnd; 
    LPCSTR lpVerb; 
    LPCSTR lpParameters; 
    LPCSTR lpDirectory; 
    int nShow; 
    DWORD dwHotKey; 
    HANDLE hIcon; 
} CMINVOKECOMMANDINFO, *LPCMINVOKECOMMANDINFO; 

Its members are:

cbSize
The size of the structure in bytes.

fMask
Zero, or one of the following values:

Constant

Description

CMIC_MASK_HOTKEY

The dwHotKey member is valid.

CMIC_MASK_ICON

The hIcon member is valid.

CMIC_MASK_FLAG_NO_UI

Tells the system to refrain from displaying user-interface elements, like error messages, while carrying out a command.

hwnd
The handle of the window that owns the context menu.

lpVerb
Contains the zero-based menu item offset in the low-order word.

lpParameters
Not used for shell extensions.

lpDirectory
Not used for shell extensions.

nShow
If the command opens a window, specifies whether it should be visible or not visible. Can be either SW_SHOW or SW_HIDE.

dwHotKey
fMask must contain CMIC_MASK_HOTKEY for this value to be valid. It contains an optional hot key to assign to the command.

hIcon
Icon to use for any application activated by the command.

QueryContextMenu

This method is called by the shell to allow the handler to add items to the context menu. Its syntax is:

HRESULT QueryContextMenu(
    HMENU hmenu,
    UINT indexMenu,
    UINT idCmdFirst,
    UINT idCmdLast,
    UINT uFlags
   );

with the following parameters:

hmenu
Handle of the menu.

indexMenu
Zero-based position at which to insert the first menu item.

iCmdFirst
Minimum value that the handler can use for a menu-item identifier.

iCmdLast
Maximum value that the handler can use for a menu-item identifier.

uFlags
Flags specifying how the context menu can be changed. These flags are discussed later in this chapter.

In invoking the method, the shell provides the context menu handler with all of the information needed to customize the context menu. The QueryContextMenu method can then use this information when calling the Win32 InsertMenu function to modify the context menu.

The documentation for the interface states that QueryContextMenu should return the menu identifier of the last menu item added, plus one. This presents an interesting problem, because VB does not allow access to the HRESULT. Fortunately, there is a workaround. We will discuss this in detail when we actually implement the interface. The complete IDL listing for IContextMenu is shown in Example 4-3.

Example 4-3: IContextMenu

typedef [public] long HMENU;
typedef [public] long LPCMINVOKECOMMANDINFO;
typedef [public] long LPSTRVB;
typedef [public] long UINT;
 
[
    uuid(000214e4-0000-0000-c000-000000000046),
    helpstring("IContextMenu Interface"),
    odl
]
interface IContextMenu : IUnknown
{
    HRESULT QueryContextMenu([in] HMENU hmenu, 
                             [in] UINT indexMenu, 
                             [in] UINT idCmdFirst, 
                             [in] UINT idCmdLast,
                             [in] QueryContextMenuFlags uFlags);
 
    HRESULT InvokeCommand([in] LPCMINVOKECOMMANDINFO lpcmi);
 
    HRESULT GetCommandString([in] UINT    idCmd,
                             [in] UINT    uType,
                             [in] UINT    pwReserved,
                             [in] LPSTRVB pszName,
                             [in] UINT    cchMax);    	
}

Notice the last parameter of QueryContextMenu, which takes a type of QueryContextMenuFlags. This is actually an enumeration defined within the type library. Enumerations are a good way to restrict the range of values that can be accepted as a method parameter. We will define many such enumerations throughout the course of this book. This provides some type safety for this method, though not much. The enum does not require an attributes block, although you could add one if you wanted. QueryContextMenuFlags is defined as follows:

typedef enum {
	CMF_NORMAL        = 0x00000000,
	CMF_DEFAULTONLY   = 0x00000001,
	CMF_VERBSONLY     = 0x00000002,
	CMF_EXPLORE       = 0x00000004,
	CMF_NOVERBS       = 0x00000008,
	CMF_CANRENAME     = 0x00000010,
	CMF_NODEFAULT     = 0x00000020,
	CMF_INCLUDESTATIC = 0x00000040,
	CMF_RESERVED      = 0xffff0000 
} QueryContextMenuFlags;

Creating a Context Menu Handler

Let's put all of this into action and actually implement a context menu handler for the .rad file. We'll add a context menu item that displays the noise an animal makes in a message box. The menu item itself will be displayed in the format (Animal Name) Noise . Animal Name will be determined from the .rad file in question. Let's begin.

Type Library

The first step to creating the .rad file context menu handler is to compile the type library containing the interface definitions and constants that will be needed from VB. Constants and UDTs will also be put into the type library with their associated interfaces. But only the groups of constants that are needed will be put in the library. For instance, we need the menu constants MF_BYPOSITION, MF_STRING, and MF_SEPARATOR. Therefore, the library will contain all of the MF_ constants. We don't need any of the menu state constants (MFS_ ), so they will not be included with the library.

The complete listing for the type library that will be used throughout the course of this book can be found in Appendix A. To compile the library, you need to have MKTYPLIB in your path. MKTYPLIB takes one argument on the command line, the name of the ODL file containing the type library definition. To compile, simply type:

mktyplib vbshell.odl 

from the command line. If everything is in order, this should produce a file named vbshell.tlb. This is the type library.

To use this library from Visual Basic, you should select Project References . . . from the main menu. You should then browse to the location of the .tlb file and select it. This will do two things. First, it will register the type library at that location; second, it will make it available to the References dialog for all future projects.

The Project

The context menu handler begins life as an ActiveX DLL project called RadEx. Our first step is to register the type library so that interface definitions are available for us to implement. That is done by selecting Project References from VB and then Browse (the library is not registered, so it will not be in the list). Navigate to the library that is associated with this chapter and add the reference. The library will be available in the References list box from this point on.

Next, add the class that will implement the handler to the project. Call this class clsContextMenu. With the class added to the project, IShellExtInit and IContextMenu can be implemented as follows:

Option Explicit
 
Implements IContextMenu
Implements IShellExtInit

Implementing IShellExtInit

Let's implement IShellExtInit::Initialize first. Notice that, in the code shell that Visual Basic creates for the Initialize method, the parameters are prefixed with the name of the library in which their definitions are located:

Private Sub IShellExtInit_Initialize( _
   ByVal pidlFolder As VBShellLib.LPCITEMIDLIST, _
   ByVal pDataObj As VBShellLib.IDataObject, _
   ByVal hKeyProgID As VBShellLib.HKEY) 

In some cases, you might want to add a private variable to your class to hold the IDataObject reference passed in by the shell, since from it you can determine how many files are selected in the user interface and what the names of those files happen to be. We will use IDataObject to get the selected files from IShellExtInit::Initialize immediately, but it may be preferable to wait until a menu item is actually selected before the selected files are determined (possibly for performance reasons). In this particular case, saving the IDataObject reference is not necessary. Rather than hold a reference to IDataObject, we will use pDataObj directly and build an array containing the names of the selected files. This array will be kept as private data. The entire listing for the Initialize method is shown in Example 4-4.

Example 4-4: Implementing IShellExtInit::Initialize

'handler.bas
 
Public Declare Function DragQueryFile Lib "shell32.dll" _ 
    Alias "DragQueryFileA" (ByVal HDROP As Long, _ 
    ByVal pUINT As Long, ByVal lpStr As String, _ 
    ByVal ch As Long) As Long
 
Public Declare Function ReleaseStgMedium Lib "ole32.dll" _ 
    (pMedium As STGMEDIUM) As Long
 
 
'clsContextMenu.cls
 
Option Explicit
 
Implements IContextMenu
Implements IShellExtInit
 
Private m_sFiles(  ) As String
Public m_nFiles As Byte
 
Private Sub IShellExtInit_Initialize( _ 
    ByVal pidlFolder As VBShellLib.LPCITEMIDLIST, _ 
    ByVal pDataObj As VBShellLib.IDataObject, _ 
    ByVal hKeyProgID As VBShellLib.HKEY)
 
    Dim FmtEtc As FORMATETC
    Dim pMedium As STGMEDIUM
    Dim i As Long
    Dim lresult As Long
    Dim sTemp As String
    
    With FmtEtc
       .cfFormat = CF_HDROP
        .ptd = 0
        .dwAspect = DVASPECT_CONTENT
        .lindex = -1
        .TYMED = TYMED_HGLOBAL
    End With
    
    pDataObj.GetData FmtEtc, pMedium
        
    m_nFiles = DragQueryFile(pMedium.pData, &HFFFFFFFF, _
               vbNullString, 0)
    
    ReDim m_sFiles(m_nFiles - 1)
    
    For i = 0 To (m_nFiles - 1)
        sTemp = String(1024, 0)
        lresult = DragQueryFile(pMedium.pData, i, sTemp, _            
                                Len(sTemp))
        If (lresult > 0) Then
           m_sFiles(i) = Left$(sTemp, lresult)
        End If
    Next
    
    ReleaseStgMedium pMedium
 
End Sub

There's quite a bit going here, so let's just take it from the top, starting with the call to GetData. GetData takes two parameters: an [in] parameter containing a pointer to a FORMATETC structure, and an [in, out] parameter that returns a pointer to a STGMEDIUM structure. The function is called like so:

pDataObj.GetData FmtEtc, pMedium

The parameters are as follows:

FORMATETC
FORMATETC is a generalized clipboard format used by OLE wherever data format information is required. The structure contains the clipboard format, a pointer to a target device, the view of the data, how much of the data should be transferred, and the medium used to transfer the data. The members of the structure are assigned values in the following code fragment from Example 4-4:

Pointers

There are three undocumented functions that allow the use of pointers from Visual Basic. These functions are VarPtr, StrPtr, and ObjPtr. VarPtr will return the address of all VB datatypes (even UDTs), except for Strings. StrPtr is used to get the address of a String. ObjPtr is used to get the address of an Object. Internally, these three functions are all mapped to one function, VarPtr. But use these functions with caution. StrPtr and VarPtr will not return the same value for a String. StrPtr returns a pointer to the Unicode string value, and VarPtr returns the address where VB stores the pointer to the Unicode BSTR. For more information on VB's undocumented pointer functions, see Appendix B, Pointers.

    With FmtEtc
        .cfFormat = CF_HDROP
        .ptd = 0
        .dwAspect = DVASPECT_CONTENT
        .lindex = -1
        .TYMED = TYMED_HGLOBAL
    End With
In this case, the data transferred will be a handle to a drop structure (our list of files) specified by CF_HDROP. The target device (specified by ptd ) is 0, because we don't care about its value; it's actually device-independent. DVASPECT_CONTENT means we want the actual data. A clipboard format can support more than one aspect or view. Here, we don't need a view, we just need the data. lindex is unimportant to the discussion. Last is the TYMED_HGLOBAL flag, which means the transfer will take place using global memory (as opposed to a file or structured storage objects). The TYMED member specifies which member of the STGMEDIUM union will be valid.

STGMEDIUM
The second parameter to GetData is a pointer to a STGMEDIUM union. The union is based on the type of medium, which in this case is TYMED_HGLOBAL (specified by FORMATETC). Therefore, under normal circumstances, the union member hGlobal would contain the handle to the drop structure. However, since this structure has been redefined, the pData member will always point to the data. This handle can be passed directly to the Win32 DragQueryFile function, which then allows us to find out how many files have been selected:

nFiles = DragQueryFile(pMedium.pData, &HFFFFFFFF, vbNullString, 0)

Passing DragQueryFile the value &HFFFFFFFF tells it that we want the number of files selected. We can also pass it a number between 0 and the total number of files selected to get the name of the file itself.

The value for nFiles allows us to redimension our file array. DragQueryFile can then be called in a loop with the index of the requested file supplied as the second argument to the function. The filename (which is written to the buffer that passed as the third argument to the function) is retrieved and stored in the file array. If multiple files of different types are selected and the file with primary focus is a .rad file, our handler will still be called. But we have to filter these extraneous types if necessary. To do this, we can have IContextMenu::InvokeCommand loop through this array and process the context menu command for every valid file that is selected.

Here's one last detail: the STGMEDIUM structure has been allocated by the call to GetData. It is common to see this structure populated by a "provider" outside of the code in which it is being used, as is the case in Example 4-4. This means freeing the memory is our responsibility, and that is what the final call to ReleaseStgMedium (a routine found in Ole32.dll ) is doing.

Implementing IContextMenu

The IContextMenu interface is responsible for displaying the text of the menu item, for showing help text associated with the menu item, and for defining the action to be performed if the menu item is selected. In this section, we'll examine the code for the methods responsible for those operations.

GetCommandString
The source code for the GetCommandString method is shown in Example 4-5. GetCommandString is called by the shell for the purpose of retrieving help text for a context menu item. This help text is then displayed in the status bar. This method is notable in that this is the first time we have to worry about implementing a method that will run under both Windows 98 and Windows NT. As you might guess, this has to do with how both platforms deal with strings. Windows 98 uses ANSI strings internally; Windows NT uses Unicode. VB uses Unicode strings internally, regardless of what platform is being used. Confusing, to say the least.

The menu item in question is determined by the idCmd parameter passed in by the shell. uType indicates the flags that inform us of the information being requested. We will return the same string regardless of these flags. The only distinction we are interested in is whether the values should be ANSI or Unicode. (There are separate ANSI and Unicode versions of each constant stored to uType.) A buffer for the help string is provided through the pszName parameter. cchMax is the size of this buffer.

The ANSI portion of the listing uses StrConv to convert the string from Unicode to ANSI. From this point forward, a common tactic is used. The string is copied into a byte array, and its starting address is copied to the memory location provided by the shell.

Example 4-5: GetCommandString Listing

Private Sub IContextMenu_GetCommandString( _
    ByVal idCmd As VBShellLib.UINT, _ 
    ByVal uType As VBShellLib.UINT, _ 
    ByVal pwReserved As VBShellLib.UINT, _ 
    ByVal pszName As VBShellLib.LPSTRVB, _ 
    ByVal cchMax As VBShellLib.UINT)
 
    Dim szName As String
    Dim bszName(  ) As Byte
    
    Dim sMenuHelp As String
    
    Select Case idCmd
        Case 0 'Noise
            szName = "Display Animal Noise"
        
        'Other menu items would be added like so:
 
        'Case 1 'Menu item 2
        '    szName = "Menu Item 2"
        
        'Case 2 'Menu item 3
        '    szName = "Menu Item 3"
    End Select
    
    szName = Left$(szName, cchMax) & vbNullChar
    
    Select Case uType
        Case GCS_VERBA, GCS_HELPTEXTA, GCS_VALIDATEA
            If (szName <> "") Then
                bszName = StrConv(szName, vbFromUnicode)
                CopyMemory ByVal pszName, _ 
                           bszName(0), _ 
                           UBound(bszName) + 1
            End If
        Case GCS_VERBW, GCS_HELPTEXTW, GCS_VALIDATEW
            If (szName <> "") Then
                bszName = szName
                CopyMemory ByVal pszName, _ 
                           bszName(0), _ 
                           UBound(bszName) + 1
            End If
    End Select
    
End Sub

InvokeCommand
InvokeCommand is called when the shell is ready to execute the context menu command. Its source code is shown in Example 4-6. The implementation of this method is fairly straightforward. Of interest is the pointer to the CMINVOKECOMMANDINFO structure that is passed in by the shell. CMINVOKECOMMANDINFO is one of those structures that mean something different depending on the context in which it is used. Check the Platform SDK for full details on this one.

This structure, while weighty as far as information goes, contains only one member that is of interest to us: lpVerb. The low-order word of lpVerb contains the menu identifier of the command being invoked.

By the time the shell calls InvokeCommand, we already have an array of the selected files stored as private data within our component. This allows us to grab every file in a loop, to find out the animal type of the file with a call to GetPrivateProfileString, and to display the appropriate information.

Example 4-6: InvokeCommand Listing

Private Sub IContextMenu_InvokeCommand(ByVal lpcmi As VBShellLib.LPCMINVOKECOMMANDINFO)
 
    Dim cmi As CMINVOKECOMMANDINFO
    CopyMemory cmi, ByVal lpcmi, Len(cmi)
 
    Dim i As Long
    Dim sNoise As String
    sNoise = Space(255)
    
    If LOWORD(cmi.lpVerb) = 0 Then
        For i = 0 To m_nFiles - 1
            
            GetPrivateProfileString "Animal", _
                                    "Noise", _
                                    "Unknown", _
                                    sNoise, _
                                    Len(sNoise), _
                      m_sFiles(i)
            
            MsgBox Trim(sNoise), vbOKOnly, "Animal Noise"
            
        Next i
    End If
    
End Sub

The LOWORD function is defined in handler.bas. There is also a HIWORD function thrown in for good measure. The two functions look like this:

Public Function LOWORD(ByVal lVal As Long) As Integer
LOWORD = lVal And &HFFFF&
End Function
 
Public Function HIWORD(ByVal lVal As Long) As Integer
HIWORD = 0
If lVal Then
HIWORD = lVal \ &H10000 And &HFFFF&
End If
End Function

QueryContextMenu
QueryContextMenu is used to add menu items to a file object's context menu. Implementing IContextMenu::QueryContextMenu is going to be a tricky process. The Platform SDK states that this method must return a positive integer representing the menu identifier of the last menu item added plus one. You might have noticed that these interface methods are implemented as subs, not functions. Even though we are dealing with a sub, VB still returns a value for each of these methods: a 0 if everything is okay or an error code that is available through the Err object. We have no direct access to the value returned from these methods.

There is a solution to this dilemma. We will write a replacement function for QueryContextMenu and put it in a code module located in the project. Then we will find the vtable entry for QueryContextMenu in our object (see Chapter 1, Introduction). We will use the AddressOf operator, in conjunction with CopyMemory, and swap the two addresses. Our new function, QueryContextMenuVB, will be called instead of the class implementation. Of course, QueryContextMenuVB will be a function, and we can return any value we want. When the object is released, the two addresses will be swapped back for posterity's sake. Our troubles are solved.

The addresses of the two functions need to be swapped as quickly as possible. Therefore, the Initialize and Terminate events (which are shown in Example 4-7 and Example 4-9, respectively) of the context menu handler class are used for this purpose.

Example 4-7: Swapping vtable Entries

Private m_pOldQueryCtxMenu As Long
 
Private Sub Class_Initialize(  )
 
    Dim pVtable As IContextMenu
    Set pVtable = Me
    
    m_pOldQueryCtxMenu = SwapVtableEntry(ObjPtr(pVtable), _ 
        4, AddressOf QueryContextMenuVB)
    
End Sub
A variable of type IContextMenu is set to Me. This gives us a pointer to the IContextMenu portion of the vtable. This memory location is copied into pVtable, effectively giving us a pointer to the IContextMenu portion of our object's vtable. Then, SwapVtableEntry (shown in Example 4-8) is called with the address of the first method of IContextMenu (this is the portion of the vtable where IContextMenu begins), the relative position in the vtable of the method we want to replace (in this case, 4--we'll see why in a few moments), and the address of the new function. One thing of interest in SwapVtableEntry is the call to VirtualProtect. VB has marked the object memory as protected. This call changes the access permissions, allowing us to swap the addresses.

Example 4-8: SwapVtableEntry Listing

Public Function SwapVtableEntry(pObj As Long, _ 
    EntryNumber As Integer, _ 
    ByVal lpfn As Long) As Long
 
    Dim lOldAddr As Long
    Dim lpVtableHead As Long
    Dim lpfnAddr As Long
    Dim lOldProtect As Long
 
    CopyMemory lpVtableHead, ByVal pObj, 4
    lpfnAddr = lpVtableHead + (EntryNumber - 1) * 4
    CopyMemory lOldAddr, ByVal lpfnAddr, 4
 
    Call VirtualProtect(lpfnAddr, 4, _ 
                        PAGE_EXECUTE_READWRITE, _ 
                        lOldProtect)
 
    CopyMemory ByVal lpfnAddr, lpfn, 4
    Call VirtualProtect(lpfnAddr, 4, lOldProtect, lOldProtect)
 
    SwapVtableEntry = lOldAddr
 
End Function

How do we know where QueryContextMenu is located in relation to this address? Well, we can't look at our class file for clues, because VB just displays all of the implemented methods in alphabetical order. This is not an accurate representation of our object.

To determine the vtable order of the method in question, look at the ODL listing. The methods are listed in the order in which they appear in the vtable. You can also use OLE View to get this information (should ODL be unavailable). Object Browser, however, does not provide it; it just lists the methods in alphabetical order. If you examine the IContextMenu interface definition in this manner, you will see that QueryContextMenu is the first method listed in the interface. Taking into consideration that the interface is derived from IUnknown, which contains three methods, QueryContextMenu is the fourth method. Thus, we pass 4 to SwapVtableEntry.

When the object terminates, the addresses can be switched back in the same manner, as shown in the class Terminate event handler in Example 4-9.

Example 4-9: Restoring vtable Entries

Private Sub Class_Terminate(  )
    Dim pVtable As IContextMenu
    Set pVtable = Me
    m_pOldQueryCtxMenu = SwapVtableEntry(ObjPtr(pVtable), _ 
        4, m_pOldQueryCtxMenu)
End Sub

QueryContextMenuVB
QueryContextMenuVB gives us some insight into just how a class works. We already know that a class keeps track of its member functions with the vtable. But once we are inside one of those member functions, how is it that we can have access back to the class? To the other methods? To Private and Public data members? Well, when a member function is called, a pointer to the class is also passed with the parameters to the function. VB (also C++) handles this behind the scenes, making everything look nice and smooth. C++ programmers refer to the parameter as the this pointer. VB can use this pointer to resolve all references back to the object.

QueryContextMenuVB must make allowances for this parameter, because it is not a part of a class; it is a function defined in a code module. This means we have to add our own this pointer to the parameter list. Example 4-10 shows how we can then define a local copy of clsContextMenu and use the this pointer to get a reference back to our class. This is really cool, because we don't have to use a global variable to get at our class now.

Example 4-10: QueryContextMenuVB Implementation

'ContextMenu.bas
 
Public Function QueryContextMenuVB (ByVal this As IContextMenu, _ 
    ByVal hMenu As Long, _ 
    ByVal indexMenu As Long, _ 
    ByVal idCmdFirst As Long, _ 
    ByVal idCmdLast As Long, _ 
    ByVal uFlags As Long) As Long
 
    Dim ctxMenu As clsContextMenu
    Set ctxMenu = this

The main task of QueryContextMenuVB (which we seem to have ignored for a while) is to add menu items to the context menu. First, the circumstance in which the context menu is activated needs to be determined. This is accomplished with the uFlags parameter that is passed in by the shell. The following code fragment shows the various situations in which the context menu can be activated. The flag we are primarily interested in is CMF_EXPLORE:

If (uFlags And &HF) = CMF_NORMAL Then

'Implement this for Drag-and-Drop handler.
 
ElseIf (uFlags And CMF_VERBSONLY) Then

'This is a context menu for a shortcut item.
 
ElseIf (uFlags And CMF_EXPLORE) Then

'Right-click on file in Explorer.
'This is what we are interested in for our context
'menu.
 
ElseIf (uFlags And CMF_DEFAULTONLY) Then

'Indicates a default action is being performed (typically a
'user is double-clicking on the file).

End If

Once it has been determined that files have been right-clicked in Explorer, the context menu item can be added accordingly. The menu item added is based on the number of files selected and the type of animal represented by the file. The animal type is determined with a call to GetPrivateProfileString:

ElseIf (uFlags And CMF_EXPLORE) Then
        
    'Right-click on file in Explorer
        
    If ctxMenu.FileCount > 1 Then
        sMenuItem = "Bunches o' Animal noises"
    Else
        GetPrivateProfileString "Animal", _
                                "Type", _
                                "Unknown", _
                                sAnimal, _
                                Len(sAnimal), _
                                ctxMenu.FileName
                                  
        sAnimal = Trim(sAnimal)
        sAnimal = Left$(sAnimal, Len(sAnimal) - 1)
          
        sMenuItem = sAnimal & "Noise"
        
    End If
 
    Call InsertMenu(hMenu, _ 
                    indexMenu, _ 
                    MF_STRING Or MF_BYPOSITION, _ 
                    idCmd, _ 
                    sMenuItem)
 
    idCmd = idCmd + 1
    indexMenu = indexMenu + 1
   
    'If you want to add another item just repeat the following code.
    '
    'sMenuItem = "Animal Name"
 	'Call InsertMenu(hMenu, _ 
    '                indexMenu, _ 
    '                MF_STRING Or MF_BYPOSITION, _ 
    '                idCmd, _ 
    '                sMenuItem)
    '
    'idCmd = idCmd + 1
    'indexMenu = indexMenu + 1
        
    'etc. , etc., etc.
    '
    'Do not increment idCmd for separators!
    'IndexMenu is always incremented.
 
    Set ctxMenu = Nothing
 
	'Lastly, the number of menu items added + 1 is returned.
    QueryContextMenuVB = indexMenu

Drag-and-Drop Handlers

Drag-and-drop handlers are displayed when a user drags a file with the right mouse button. They are really just specialized context menu handlers; therefore, they don't get a chapter of their own.

To implement one, you would need to insert menu items based on the context menu flag being equal to CMF_NORMAL, and only equal to CMF_NORMAL. It cannot be AND ed with any other value. In addition, you would have to add the handler to the registry as shown in Figure 4-5.

The handler is also added to the approved shell extensions list in the registry (see Chapter 2, COM Basics).

Figure 4-5. Defining a drag-and-drop handler in the registry

 

Registration and Operation

Last but not least, the handler needs to be registered. As always, the file rad.reg that is included with this chapter's downloadable code contains the appropriate registry entries. Example 4-11 contains the entire listing. Note that items in square brackets must exist on the same line (the listing is formatted to fit on the page).

Example 4-11: rad.reg

REGEDIT4
 
[HKEY_CLASSES_ROOT\.rad]
@ = "radfile"
 
[HKEY_CLASSES_ROOT\radfile]
 
[HKEY_CLASSES_ROOT\radfile]
@ = "Rudimentary Animal Data"
 
[HKEY_CLASSES_ROOT\radfile\shellex]
 
[HKEY_CLASSES_ROOT\radfile\shellex\ContextMenuHandlers]
@ = "RadFileMenu"
 
[HKEY_CLASSES_ROOT\radfile\shellex\ContextMenuHandlers\RadFileMenu]
@ = "{D4F9CECF-E84E-11D2-BB7C-444553540000}"
 
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved]
"{D4F9CECF-E84E-11D2-BB7C-444553540000}" = "RAD file context menu extension"

These are the same entries that were discussed in Chapter 3, Shell Extensions. Review the entries until you become familiar with them. It shouldn't take too long. If you want to register the extension by hand, you will need to find the CLSID for the object. The easiest way to do that is to search under HKEY_CLASSES_ROOT for the programmatic identifier, or ProgID, of the extension object. The ProgID is formed by appending the class name to the project name with a period in the middle. So look for "RadEx.clsContextMenu," and there should be a CLSID subkey with the needed value.

After you have registered the handler, kill off any instances of Explorer you might have running. The handler, which is shown in Figure 4-6, will be available with the next instance you run. There are sample .rad files included with the source of this book that you can use to test the handler.

Figure 4-6. Context menu in action

 

Back to: Visual Basic Shell Programming


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com