Implementing the Property Page COM Object

A property sheet extension is a COM object implemented as an in-proc server. The property sheet extension must implement the IShellExtInit and IShellPropSheetExt interfaces. A property sheet extension is instantiated when the user displays the property sheet for an object of a class for which the property sheet extension has been registered in the display specifier of the class.

Implementing IShellExtInit

After the property sheet extension COM object is instantiated, the IShellExtInit::Initialize method is called. IShellExtInit::Initialize supplies the property sheet extension with an IDataObject object that contains data that pertains to the directory object that the property sheet applies.

The IDataObject contains data in the CFSTR_DSOBJECTNAMES format. The CFSTR_DSOBJECTNAMES data format is an HGLOBAL that contains a DSOBJECTNAMES structure. The DSOBJECTNAMES structure contains directory object data that the property sheet extension applies.

The IDataObject also contains data in the CFSTR_DS_DISPLAY_SPEC_OPTIONS format. The CFSTR_DS_DISPLAY_SPEC_OPTIONS data format is an HGLOBAL that contains a DSDISPLAYSPECOPTIONS structure. The DSDISPLAYSPECOPTIONS contains configuration data for use by the extension.

If any value other than S_OK is returned from IShellExtInit::Initialize, the property sheet is not displayed.

The pidlFolder and hkeyProgID parameters of the IShellExtInit::Initialize method are not used.

The IDataObject pointer can be saved by the extension by incrementing the reference count of the IDataObject. This interface must be released when no longer required.

Implementing IShellPropSheetExt

After IShellExtInit::Initialize returns, the IShellPropSheetExt::AddPages method is called. The property sheet extension must add the page or pages during this method. A property page is created by filling a PROPSHEETPAGE structure and then passing this structure to the CreatePropertySheetPage function. The property page is then added to the property sheet by calling the callback function passed to IShellPropSheetExt::AddPages in the lpfnAddPage parameter.

If any value other than S_OK is returned from IShellPropSheetExt::AddPages, the property sheet is not displayed.

If the property sheet extension is not required to add any pages to the property sheet, it should not call the callback function passed to IShellPropSheetExt::AddPages in the lpfnAddPage parameter.

The IShellPropSheetExt::ReplacePage method is not used.

Passing the Extension Object to the Property Page

The property sheet extension object is independent from the property page. In many cases, it is desirable to be able to use the extension object, or some other object, from the property page. To do this, set the lParam member of PROPSHEETPAGE structure to the object pointer. The property page can then retrieve this value when it processes the WM_INITDIALOG message. For a property page, the lParam parameter of the WM_INITDIALOG message is a pointer to the PROPSHEETPAGE structure. Retrieve the object pointer by casting the lParam of the WM_INITDIALOG message to a PROPSHEETPAGE pointer and then retrieving the lParam member of the PROPSHEETPAGE structure.

The following C++ code example shows how to pass an object to a property page.

case WM_INITDIALOG:
    {
        LPPROPSHEETPAGE pPage = (LPPROPSHEETPAGE)lParam;

        if(NULL != pPage)
        {
            CPropSheetExt *pPropSheetExt;
            pPropSheetExt = (CPropSheetExt*)pPage->lParam;

            if(pPropSheetExt)
            {
                return pPropSheetExt>OnInitDialog(wParam, lParam);
            }
        }
    }
    break;

Be aware that after IShellPropSheetExt::AddPages is called, the property sheet will release the property sheet extension object and never use it again. This means that the extension object would be deleted before the property page is displayed. When the page attempts to access the object pointer, the memory will have been freed and the pointer will not be valid. To correct this, increment the reference count for the extension object when the page is added and then release the object when the property page dialog is destroyed. This creates another issue because the property page dialog box is not created until the first time the page is displayed. If the user never selects the extension page, the page never gets created and destroyed. This results in the extension object never getting released, so a memory leak occurs. To avoid this, implement a property page callback function. To do this, add the PSP_USECALLBACK flag to the dwFlags member of the PROPSHEETPAGE structure and set the pfnCallback member of the PROPSHEETPAGE structure to the address of the PropSheetPageProc function implemented. When the PropSheetPageProc function receives the PSPCB_RELEASE notification, the ppsp parameter of the PropSheetPageProc contains a pointer to the PROPSHEETPAGE structure. The lParam member of the PROPSHEETPAGE structure contains the extension pointer which can be used to release the object.

The following C++ code example shows how to release an extension object.

UINT CALLBACK CPropSheetExt::PageCallbackProc(  HWND hWnd,
                                                UINT uMsg,
                                                LPPROPSHEETPAGE ppsp)
{
    switch(uMsg)
    {
    case PSPCB_CREATE:
        // Must return TRUE to enable the page to be created.
        return TRUE;

    case PSPCB_RELEASE:
        {
            /*
            Release the object. This is called even if the page dialog box was 
            never actually created.
            */
            CPropSheetExt *pPropSheetExt = (CPropSheetExt*)ppsp->lParam;

            if(pPropSheetExt)
            {
                pPropSheetExt->Release();
            }
        }
        break;
    }

    return FALSE;
}

Working With the Notification Object

Because the property sheet extension pages are displayed within a property sheet created by a component unknown to the extension, it is necessary to use a "manager" to handle the data transfer between the extension pages and the property sheet. This "manager" is called the notification object. The notification object operates as a moderator between the individual pages and the property sheet.

When the property sheet extension object is initialized, the extension must create a notification object by calling ADsPropCreateNotifyObj, passing the IDataObject obtained from IShellExtInit::Initialize and the directory object name. It is not necessary to increment the reference count of the IDataObject interface, because the notification object created by ADsPropCreateNotifyObj function will do this. The notification object handle provided by ADsPropCreateNotifyObj should be saved for later use. ADsPropCreateNotifyObj can be either called during IShellExtInit::Initialize or IShellPropSheetExt::AddPages. When the property sheet extension is shut down, it must send a WM_ADSPROP_NOTIFY_EXIT message to the notification object. This causes the notification object to destroy itself. This is best done when the PropSheetPageProc function receives the PSPCB_RELEASE notification.

The property sheet extension can obtain data in addition to that provided by the CFSTR_DSOBJECTNAMES clipboard format by calling ADsPropGetInitInfo. One of the advantages of using ADsPropGetInitInfo is that it provides an IDirectoryObject object used to programmatically work with the directory object.

Note

Unlike most COM methods and functions, ADsPropGetInitInfo does not increment the reference count for the IDirectoryObject object. The IDirectoryObject must not be released unless the reference count is manually incremented first.

 

When the property page is first created, the extension should register the page with the notification object by calling ADsPropSetHwnd with the window handle of the page.

ADsPropCheckIfWritable is a utility function that the property sheet extension can use to determine if a property can be written.

Miscellaneous

The handle of the property page is passed to the page dialog box procedure. The property sheet is the direct parent of the property page, so the handle of the property sheet can be obtained by calling the GetParent function with the property page handle.

When the contents of the extension page changes, the extension should use the PropSheet_Changed macro to notify the property sheet of changes. The property sheet will then enable the Apply button.

Multiple-Selection Property Sheets

With Windows Server 2003 and later operating systems, the Active Directory administrative MMC snap-ins support property sheet extensions for multiple directory objects. These property sheets are displayed when the properties are viewed for more than one item at a time. The primary difference between a single-selection property sheet extension and a multiple-selection property sheet extension is that the DSOBJECTNAMES structure supplied by the CFSTR_DSOBJECTNAMES clipboard format in IShellExtInit::Initialize will contain more than one DSOBJECT structure.

When the notification object is created, a multi-selection property sheet extension must pass a unique name that is provided by the snap-in rather than a name created by the extension. To obtain the unique name, request the CFSTR_DS_MULTISELECTPROPPAGE clipboard format from the IDataObject obtained from IShellExtInit::Initialize. This data is an HGLOBAL that contains a null-terminated Unicode string that is the unique name. This unique name is then passed to the ADsPropCreateNotifyObj function to create the notification object. The CreateADsNotificationObject example function in Example Code for Implementation of the Property Sheet COM Object demonstrates how to do this correctly, as well as being compatible with earlier versions of the snap-in that do not support multi-selection property sheets.

For multiple-selection property sheets, the system only binds to the first object in the DSOBJECT array. Because of this, ADsPropGetInitInfo only supplies the IDirectoryObject and write-able attributes for the first object in the array. The other objects in the array are not bound to.

A multiple-selection property sheet extension is registered under the adminMultiselectPropertyPages attribute.

New with Windows Server 2003

The following features are new with Windows Server 2003.

If the property page encounters an error, ADsPropSendErrorMessage can be called with the appropriate error data. ADsPropSendErrorMessage will store all error messages in a queue. These messages will be displayed the next time ADsPropShowErrorDialog is called. When ADsPropShowErrorDialog returns, the queued messages are deleted.

Windows Server 2003 introduces the ADsPropSetHwndWithTitle function. This function is similar to ADsPropSetHwnd, but includes the page title. This enables the error dialog box displayed by ADsPropShowErrorDialog to provide more useful data to the user. If the property sheet extension uses the ADsPropShowErrorDialog function, the extension should use ADsPropSetHwndWithTitle rather than ADsPropSetHwnd.

Example Code for Implementation of the Property Sheet COM Object