Chapter 6. Property Sheet Handlers

only for RuBoard - do not distribute or recompile

Chapter 6. Property Sheet Handlers

A standard set of properties is available for every file object via the Properties context menu or by selecting File figs/u2192.gif Properties from Explorer's main menu. These properties include things like file attributes, size , location, date created, and so on. The information is made available in a tabbed dialog, as shown in Figure 6.1, providing the user with an opportunity to change a file's attributes (in the most generic implementation). Property sheet handlers permit additional pages to be added to this dialog, allowing the possibility of additional file processing.

Microsoft Word is a good example of this functionality in action, as it adds four additional property pages to the standard dialog for its .doc file type. These additional property pages allow users to modify .doc file attributes like title, author, and subject of a document without having to start Microsoft Word. Figure 6.2, for instance, shows the Summary property sheet of a Microsoft Word .doc file, an interface element added by Word's property sheet handler.

Figure 6.1. Property page dialog
figs/vshl.0601.gif
Figure 6.2. Word Summary property sheet
figs/vshl.0602.gif
only for RuBoard - do not distribute or recompile
only for RuBoard - do not distribute or recompile

6.1 How Property Sheet Handlers Work

Property sheet handlers are required to implement two interfaces: IShellExtInit and IShellPropSheetExt . You are already familiar with IShellExtInit (see Chapter 4). IShellPropSheetExt contains only two methods : AddPages , which is called to add a page to a property dialog, and ReplacePage , which, as you might guess, replaces an existing property page. A property sheet handler implements AddPages only. ReplacePage is not implemented, since it applies only to Control Panel objects.

When the Properties menu item is selected for a file object, Explorer initializes the handler by calling IShellExtInit::Initialize . The selected file is passed to the handler via an IDataObject interface. Typically, the property sheet handler would save the name of the file in a private member variable for later use.

Then the shell calls IShellPropSheetExt::AddPages . The implementor of AddPages is required to fill out a PROPSHEETPAGE structure that contains information about the new property page. The structure is then passed to the CreatePropertySheetPage API, and a handle to the newly created property page is returned if the call was successful.

One of the parameters passed in by the shell to AddPages is a function address. The function specified by this address must then be called with the handle to the newly created page as its only parameter. If you are confused , don't worry. We'll go over this in detail when we implement IShellPropSheetExt .

only for RuBoard - do not distribute or recompile
only for RuBoard - do not distribute or recompile

6.2 Property Sheet Handler Interface

Now that you know how a property sheet handler works, let's discuss the interfaces involved in a little more detail.

6.2.1 IShellExtInit

The implementation of IShellExtInit is exactly the same as it was in Chapter 4. (For the details of the IShellExtInit interface, see Section 4.4 in Chapter 4.) Well, almost. The functionality provided by IShellExtInit which is used to determine which files are selected in the Exploreris generic enough to be wrapped in a class. All future extensions that must implement IShellExtInit in this book will use this class. The class is called clsDropFiles, and the complete class listing is shown in Example 6.1.

Example 6.1. clsDropFiles Class
 Option Explicit

Private m_nFiles As Long
Private m_sDropFiles(  ) As String

Public Sub GetDropFiles(pDataObj As IDataObject, _ 
                        ByVal sExtension As String)

    Dim FmtEtc As FORMATETC
    Dim pMedium As STGMEDIUM
    Dim i As Long
    Dim lresult As Long
    Dim sTemp As String
    Dim lIndex As Long
    
    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)
        
    lIndex = 0
    For i = 0 To m_nFiles - 1
        sTemp = Space(255)
        lresult = DragQueryFile(pMedium.pData, i, sTemp, Len(sTemp))
        If (lresult > 0) Then
            sTemp = Left$(sTemp, lresult)
            If LCase(Right(sTemp, 4)) = sExtension Then
                ReDim Preserve m_sDropFiles(lIndex + 1)
                m_sDropFiles(lIndex) = sTemp
                lIndex = lIndex + 1
            End If
        End If
    Next i
    
    m_nFiles = lIndex
    
    ReleaseStgMedium pMedium
    
End Sub

Public Property Get Count(  ) As Integer
    Count = m_nFiles
End Property

Public Property Get Files(nIndex As Integer) As String
    
    Files = ""
    
    If (m_nFiles) Then
        If (nIndex >= 0) And (nIndex < m_nFiles) Then
            Files = m_sDropFiles(nIndex)
        End If
    End If
        
End Property

Public Property Get SelectedFile(  ) As String
    
    SelectedFile = ""
    
    If (m_nFiles) Then
        SelectedFile = m_sDropFiles(0)
    End If
    
End Property 

Now that we have a class that handles the extraction of filenames from IDataObject , implementing IShellExtInit becomes a little easier. The complete listing for IShellExtInit::Initialize is shown in Example 6.2.

Example 6.2. New Initialize Implementation
 Private m_clsDropFiles As clsDropFiles

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

    Set m_clsDropFiles = New clsDropFiles
    m_clsDropFiles.GetDropFiles pDataObj, ".rad"
    
End Sub 

All IShellExtInit::Initialize needs to do is declare an instance of clsDropFiles and call the member function, GetDropFiles . GetDropFiles takes as parameters an IDataObject reference and a file filter and creates an internal array holding all of the filenames contained within the data object that have the extension .rad .

6.2.2 IShellPropSheetExt

IShellPropSheetExt contains two methods , AddPages and ReplacePage , for adding and replacing property sheets (see Table 6.1). There is really nothing unusual about this interface; we don't have to do anything crazy like vtable swapping! The implementation of this interface, on the other hand, is a wild ride, to say the least. But we'll talk about that later. For now, let's familiarize ourselves with the interface and the methods it contains.

Table6.1. IShellPropSheetExt

Method

Description

AddPages

Adds a page(s) to a property sheet for a file object.

ReplacePage

Replaces a page in a property sheet for a Control Panel applet. Not used for shell property sheet extensions.

6.2.2.1 AddPages

AddPages is the method responsible for adding a property sheet to an existing property page dialog. When a property sheet is about to be displayed, the shell calls AddPages for each handler registered to the selected file type. Its syntax is as follows :

 HRESULT STDMETHODCALLTYPE AddPages(
    LPFNADDPROPSHEETPAGE   lpfnAddPage,   LPARAM   lParam   ); 

The parameters are as follows:

lpfnAddPage

[in] The address of a function that the property sheet extension must call to display a property page.

lParam

[in] Parameter to pass to the function specified by lpfnAddPage .

6.2.2.2 ReplacePage

ReplacePage is used to replace property sheet pages for a Control Panel object. It is not used for property sheet handlers. Its syntax is as follows:

 HRESULT STDMETHODCALLTYPE ReplacePage(UINT   uPageID   , 
              LPFNADDPROPSHEETPAGE   lpfnReplacePage   , 
              LPARAM lParam ); 

The parameters to ReplacePage are the same as AddPages , with the exception of one parameter:

uPageID

This is the identifier of the page to replace. Since ReplacePage is used exclusively with Control Panel applets, the valid values for this parameter can be found in the Cplext.h header file in the Platform SDK.

6.2.2.3 Implementing IShellPropSheetExt

The IDL listing for IShellPropSheetExt is shown in Example 6.3.

Example 6.3. IShellPropSheetExt Interface Listing
 typedef struct {
        long x;
        long y;
	} POINT;

typedef struct MSG {
    HWND   hwnd;
    UINT   message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD  time;
    POINT  pt;
} MSG;

typedef struct { 
    HWND hwndFrom; 
    UINT idFrom; 
    UINT code; 
} NMHDR; 

typedef enum {
    PSPCB_RELEASE = 1,
    PSPCB_CREATE  = 2
} PROPSHEETCALLBACKMSG;

typedef enum {
    PSP_DEFAULT           = 0x00000000,
    PSP_DLGINDIRECT       = 0x00000001,
    PSP_USEHICON          = 0x00000002,
    PSP_USEICONID         = 0x00000004,
    PSP_USETITLE          = 0x00000008,
    PSP_RTLREADING        = 0x00000010,
    PSP_HASHELP	          = 0x00000020,
    PSP_USEREFPARENT      = 0x00000040,
    PSP_USECALLBACK       = 0x00000080,
    PSP_PREMATURE         = 0x00000400,
    PSP_HIDEHEADER        = 0x00000800,
    PSP_USEHEADERTITLE    = 0x00001000,
    PSP_USEHEADERSUBTITLE = 0x00002000
} PROPERTYSHEETFLAG;

typedef enum {
    PSN_SETACTIVE  = -200,
    PSN_KILLACTIVE = -201,
    PSN_APPLY = -202,
    PSN_RESET = -203,
    PSN_QUERYCANCEL = -209
} PROPSHEETNOTIFYMSG;

typedef struct {
    DWORD dwSize;
    DWORD dwFlags;
    HINSTANCE hInstance;
    LPCSTRVB pszTemplate;
    HICON hIcon;
    LPCSTRVB pszTitle;
    DLGPROC pfnDlgProc;
    LPARAM lParam;
    LPFNPSPCALLBACK pfnCallback;
    long pcRefParent;
    LPCTSTRVB pszHeaderTitle;
    LPCTSTRVB pszHeaderSubTitle;
} PROPSHEETPAGE;


[
    uuid(000214e9-0000-0000-c000-000000000046),
    helpstring("IShellPropSheetExt Interface"),
    odl
]
interface IShellPropSheetExt : IUnknown
{
    HRESULT AddPages([in] LPFNADDPROPSHEETPAGE lpfnAddPage, 
                     [in] LPARAM lParam);

    HRESULT ReplacePage([in] UINT uPageID, 
                        [in] LPFNADDPROPSHEETPAGE lpfnReplaceWith, 

                        [in] LPARAM lParam);
} 
only for RuBoard - do not distribute or recompile