Snap-in Tutorial

The Benefits sample Snap-In demonstrates the use the ATL Snap-In Wizard and ATL Snap-In templates to build a fully functional Snap-In for use with the Microsoft Management Console. By following these steps, you should be able to recreate the Snap-In and learn some of the architectural issues dealt with:

Step 1: Creating the Project

Step 2: Adding the Snap-in Object

Step 3: Creating Multiple Node Types

Step 4: Managing Nodes

Step 5: Displaying Scope Items

Step 6: Displaying Result Items

Step 7: Adding Menus

Step 8: Adding Toolbars

Step 9: Adding Command Handlers

Step 10: Implementing Console Verbs

Step 11: Adding Persistence

Step 12: Displaying Property Pages

Step 13: Creating Taskpads


Step 1: Creating the Project

First, you will create the initial ATL project using the ATL COM AppWizard.

  1. In the Visual C++ environment, on the File menu, click New, and then click the Projects tab.
  2. Select ATL COM AppWizard.
  3. Type BenSvr as the project name.

Your dialog box should look like this:

NewProject.gif (14154 bytes)

Click OK. The ATL COM AppWizard presents a dialog box offering several choices to configure the initial ATL project.

ATLAppWizard.gif (15649 bytes)

Because you are creating a snap-in, leave the Server Type as a DLL; a snap-in must be an in-process server. All the default options are fine, so click Finish. A dialog box appears that lists the main files that will be created. These files are listed in the following table, along with a description of each file that the ATL COM AppWizard generates.

File | Description
BenSvr.cpp | Contains the implementation of DllMain, DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer. Also contains the object map, which is a list of the ATL objects in your project. This is initially blank, because you have not created an object yet.
BenSvr.def | Standard Windows module definition file for the DLL.
BenSvr.dsw | Project workspace.
BenSvr.dsp | File that contains the project settings.
BenSvr.h | File contains empty file. The contents are generated as a result of the MIDL build process.
BenSvr.idl | Interface definition language file, which describes the interfaces specific to your objects.
BenSvr.ncb | Class browser file used only by DevStudio.
BenSvr.rc | Resource file, which initially contains the version information and a string containing the project name.
Resource.h | Header file for the resource file.
BenSvrps.mk | Make file that can be used to build a proxy/stub DLL. You will not need to use this.
BenSvrps.def | The module definition file for the proxy/stub DLL.
StdAfx.cpp | The file that will #include the ATL implementation files.
StdAfx.h | The file that will #include the ATL header files.

To make the BenSvr DLL useful, you need to add a snap-in object, using the ATL Object Wizard.

Next Step


Step 2: Adding the Snap-in Object

To add an object to an ATL project, you use the ATL Object Wizard. On the Insert menu, click New ATL Object to display the ATL Object Wizard.

ATLObjectWizard.gif (9201 bytes)

In the first ATL Object Wizard dialog box, select the category of object you want to add to your ATL project. In this tutorial, you will create an MMC SnapIn object, so click Objects on the left, and then click MMC SnapIn on the right. Finally, click Next.

A set of property pages is displayed that allows you to configure the snap-in you are inserting into your project. Type "Benefits" as the short name. The other fields are automatically completed.

SnapInWizardNames.gif (8154 bytes)

You now need to enable support for extending snap-in features:

  1. Click the MMC Snapin tab.
  2. Select the IExtendContextMenu check box. This enables you to extend menus.
  3. Select the IExtendPropertySheet check box. This enables you to create property sheets for the snap-in's nodes.
  4. Select the IExtendControlBar check box. This enables you to create custom toolbars for your snap-in.

ATLObjectWizard2.gif (8467 bytes)

You are finished selecting options for your snap-in. Click OK.

When you created your snap-in, the following files were created:

File | Description
Benefits.cpp | Contains some of the implementation of the snap-in.
Benefits.h | Contains the remaining implementation of the snap-in.
bmp00001.bmp | Bitmap resource used as template for new node's toolbar.
Benefits.rgs | A text file that contains the registry script used to register the control.

The wizard also performed the following code changes:

  • The registry script Benefits.rgs was added to the project resource.
  • BenSvr.idl was changed to include details of the new SnapIn object and an about information object.
  • The SnapIn object and a SnapIn about object were added to the object map in BenSvr.cpp.

You are now ready to build your snap-in:

  1. On the Build menu, click Build BenSvr.dll.
  2. After your snap-in has finished building, click Execute on the Build menu. The Executable for Debug Session dialog box appears.
  3. Click the right arrow next to the Executable file name box and then click Browse on the pop-up menu. The Browse dialog box appears.
  4. Locate the MMC.EXE file on your local machine. It will probably be in your Windows\System32 directory. Click the file name and then click Open.
  5. Click OK. The Microsoft Management Console application is launched.

You are now ready to use your newly created snap-in. To insert your snap-in into the currently open console:

  1. On the Console menu, click Add/Remove Snap-in.... The Add/Remove Snap-in dialog box appears.
  2. Click Add... The Add Standalone Snap-in dialog box appears.
  3. Click the folder with the title "Benefits" next to it. Click OK. The dialog box disappears.
  4. In the Add/Remove Snap-in dialog box, click OK.

At this point, a primitive version of your snap-in should appear in the current console:

BenefitsConsole1.gif (8228 bytes)

Close the Microsoft Management Console application.


Step 3: Creating Multiple Node Types

To create nodes with unique functionality and behavior, you must create new node classes. The easiest way to create a new node is to copy the CBenefitsData class to a new CPP file and rename it, as demonstrated earlier. You will also need to create some helper templates that will simplify the code you'll write.

Helper Templates

In the Benefits sample, you will create three unique node types:

  • The Health node will demonstrate the ability to display HTML pages.
  • The Key node will demonstrate a traditional list view.
  • The Retirement node will demonstrate the ability to display a custom OCX as a result.

To simplify the implementation, you will create helper classes and new templates.

CBenefitsData should be converted to a template. This template contains a number of helper functions that are used by all of the unique nodes you will create. The support macros, including the menu and toolbar mapping macros, are removed from this template. Those nodes that do not have "leaf nodes" will use this template:

template< class T >
class CBenefitsData : public CSnapInItemImpl< T >
{
...
}

View the CBenefitsData code listing for its complete definition. You can copy and paste this code over the existing CBenefitsData definition for simplicity.

Some of the unique nodes you will create also contain "leaf nodes." These child nodes are managed with the help of another utility template, CChildrenBenefitsData. Useful enumeration and selection handling functions are stored in this template. Those nodes that have leaf nodes will use this template:

template< class T >
class CChildrenBenefitsData : public CBenefitsData< T >
{
...
}

View the CChildrenBenefitsData code listing for its complete definition. You can copy and paste this code over the existing CChildrenBenefitsData definition for simplicity.

Creating a Node

With the helper templates defined, you can rapidly create new node types.

  1. Insert into your project a new header file titled BenNodes.h. For additional information, refer to Inserting a File Into Your Project.
  2. Insert into your project a new implementation file titled BenNodes.cpp. For additional information, refer to Inserting a File Into Your Project.
  3. Copy the CBenefitsData template to BenNodes.h. Refer to CBenefitsData code listing.
  4. Rename CBenefitsData to CHealthNode.
  5. Remove template decorations (<>).
  6. Add necessary macros. If you wish for your node to have a custom menu, you should add the SNAPINMENUID(...) macro. If your node has a custom toolbar, you should add the BEGIN_SNAPINTOOLBARID_MAP(...).
  7. Derive your class from either CChildrenBenefitsData or CBenefitData. In cases where the node has leaf nodes, the node should utilize the CChildrenBenefitsData. For nodes without children, the node should utilize the CBenefitsData template. In the case of the Health node, it will have no leaf nodes, so use the CBenefitsData template.

Your new class declaration should look like:

class CHealthNode : public CBenefitsData< CHealthNode >
{
...
}

You should add constructors, destructors, and further functions for CHealthNode to BenNodes.cpp (refer to the CHealthNode listing to copy and paste these miscellaneous functions). In addition, you must initialize static members that are unique to the newly created node. These static members help the Microsoft Management Console identify the node. You can also use them to initialize the text to be displayed by the node. These static members must exist for every unique node.

To initialize the new node's static members, you must first generate a unique GUID. This is best done using GUIDGEN.EXE (located in your Visual C++ BIN directory). Use the generated GUID to initialize the following lines:

static const GUID CHealthNodeGUID_NODETYPE =
{ 0xec362ef1, 0xd94d, 0x11d1, { 0x84, 0x74, 0x0, 0x10, 0x4b, 0x21, 0x1b, 0xe5 } };
const GUID* CHealthNode::m_NODETYPE = &CHealthNodeGUID_NODETYPE;
const TCHAR* CHealthNode::m_SZNODETYPE = _T("EC362EF1D94D-11D1-8474-00104B211BE5");
const TCHAR* CHealthNode::m_SZDISPLAY_NAME = _T("Health & Dental Plan");
const CLSID* CHealthNode::m_SNAPIN_CLASSID = &CLSID_Benefits;

Use the same process to generate two additional nodes. These nodes should be named CKeyNode and CRetirementNode. Because the Key node contains children, its declaration looks slightly different in that it uses the CChildrenBenefitsData template you prepared earlier:

class CKeyNode : public CChildrenBenefitsData< CKeyNode >
{
...
}


Step 4: Managing Nodes

The ATL Object Wizard creates a single root node for the Benefits snap-in. Because you are creating unique nodes, you must add code to create the nodes and to handle their storage.

Initializing Nodes

The ATL snap-in implementation automatically creates an instance of the root node in the IComponentDataImpl<> template, which CBenefits uses, and stores a pointer to the node in a member called m_pNode. Because you are adding additional nodes as leaf nodes of this root node, you must add initialization code to CBenefits. By default, CBenefits implements an Initialize() function that is called from within the ATL snap-in templates. To initialize the Health, Key, and Retirement nodes, do the following:

  1. Add an InitializeSubNodes() function to CRootNode.
  2. Add a call to InitializeSubNodes() from within CBenefits::Initialize(...).
  3. Add code to InitializeSubNodes() that allocates the leaf nodes and stores them.

The code in InitializeSubNodes() should look like the following:

BOOL CRootNode::InitializeSubNodes()
{
    CSnapInItem* pNode;

    //
    // Allocate subnodes and add them to our internal list.
    //
    pNode = new CHealthNode( &m_Employee );
    if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
        return( FALSE );

    pNode = new CRetirementNode( &m_Employee );
    if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
        return( FALSE );

    pNode = new CKeyNode( &m_Employee );
    if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
        return( FALSE );

    return( TRUE );
}

A reference to the m_Employee member is passed to each node (discussed in the Persistence and Property Page steps), and each node is added to an internal storage class (discussed in the following section).

Storing Nodes

Storage is implemented by the CChildrenBenefitsData<> template. Any nodes that have leaf nodes can use this template to simplify their storage, display, and cleanup.

To store leaf nodes, add the following member to the CChildrenBenefitsData<> template definition:

    //
    // Used as containment for all child nodes.
    //
    CSimpleArray<CSnapInItem*> m_Nodes;

Users of the template can create nodes with the InitializeSubNodes() function and use CSimpleArray<>::Add(...) to store the nodes.

To clean up the nodes, add the following code to the template's destructor:

    //
// Overridden to automatically clean up any child nodes.
//
virtual ~CChildrenBenefitsData()
{
//
// Free any added nodes.
//
for ( int i = 0; i < m_Nodes.GetSize(); i++
)
{
CSnapInItem* pNode;

pNode = m_Nodes[ i ];
_ASSERTE( pNode != NULL
);
delete pNode;
}
}

You can also add to this template helper functions for displaying and enumerating
leaf nodes. Steps for adding this support are described in Displaying Scope Items and Displaying Result Items. A complete listing is
available of CChildrenBenefitsData<>.


Step 5: Displaying Scope Items

A Snap-In adds items to the scope pane by responding to a notification sent by the Microsoft Management Console. The ATL Snap-In templates route this notification to your node's implementation class. As a result, code that responds to the notification should be added to your root node to display the three new leaf nodes we've created. When finished, you nodes would be displayed as:

EnumeratedNodes.gif (4179 bytes)

MMC sends a MMCN_EXPAND notification to CRootNode::Notify(...). To enumerate scope items:

  1. Add handler to CRootNode::Notify(...) that catches the MMCN_EXPAND case and dispatches the notifcation to template.
  2. Add OnExpand(...) implementation to CChildrenBenefitsData<> template.

In the CRootNode::Notify(...) function, a default switch statement branches on the given event. You will insert code that simply catches the notifcation and dispatches to the CChildrenBenefitsData<>::OnExpand(...) function:

        case
MMCN_EXPAND:
//
// Since the insert
item is never called, we don't have a valid
// HSCOPEITEM as you
would in sub-nodes. The Expand message is
// intercepted and
stored for use later.
//
m_scopeDataItem.ID =
param;
hr = OnExpand( event,
arg, param, spConsole, type );
break;

The CChildrenBenefitsData<>::OnExpand(...) implementation is a common method of enumerating a list and adding the items to the console:

    STDMETHOD( OnExpand )( MMC_NOTIFY_TYPE
event,
long arg,
long param,
IConsole* pConsole,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( event );
UNUSED_ALWAYS( arg );
UNUSED_ALWAYS( type );
ATLTRACE2(atlTraceSnapin, 0,
_T("CChildrenBenefitsData::OnExpand\n"));

CComQIPtr<IConsoleNameSpace,
&IID_IConsoleNameSpace> pNameSpace( pConsole );
HRESULT hr = E_FAIL;

//
// Loop through and add each subnode.
//
for ( int i = 0; i < m_Nodes.GetSize(); i++
)
{
CSnapInItem* pNode;
SCOPEDATAITEM*
pScopeData;

pNode = m_Nodes[ i ];
_ASSERTE( pNode != NULL
);

//
// Get the scope pane
info for the node and set
// relative id.
//
pNode->GetScopeData(
&pScopeData );
_ASSERTE( pScopeData !=
NULL );
pScopeData->relativeID = param;

//
// Add the item to the
scope list using the newly
// populated scope data
item.
//
hr =
pNameSpace->InsertItem( pScopeData );
_ASSERTE( SUCCEEDED( hr
) );
}

return( hr );
};

Given that your template handles the storage of the leaf nodes (see Managing Nodes), the function can iterate the contents of m_Nodes, initialize pScopeData given the node, and inserts the item using the IConsoleNameSpace interface.


Step 6: Displaying Result Items

There are a number of ways you can display results within the Microsoft Management Console. This sample illustrates a result list, a web page, and an OCX. The Health node displays a web page that is compiled into the snap-in as a resource. The Key node displays a list view, each item representing a building to which card-key access has been granted for the current employee. The Retirement node displays an OCX in which the control simulates retrieving retirement plan performance.

Displaying a List

EnumeratedResults.gif (6906 bytes)

Displaying result items is very similar to displaying scope items. Though the IComponent interface is called for displaying results, the ATL snap-in templates automatically route calls to your node implementation. This means that you can simplify and share your code to handle the display of scope and result items.

When inserting results, you will be responding to the MMCN_SHOW event in your CKeyNode::Notify(...) implementation. You will:

  1. Add a handler to CKeyNode::Notify(...) that catches the MMCN_SHOW case and dispatches the notification to the template.
  2. Add the OnShow(...) implementation to the CChildrenBenefitsData<> template.

In the CKeyNode::Notify(...) function, a default switch statement branches on the given event. You will insert code that catches the notification and dispatches it to the CChildrenBenefitsData<>::OnShow(...) function. (The OnShowColumn(...) call modifies column headings and is described in Enhancing the Result Pane.):

        case
MMCN_SHOW:
//
// Only set up columns
if we're displaying the result pane.
//
if ( arg == TRUE )
hr = OnShowColumn( spHeader );
hr = OnShow( event,
arg, param, spConsole, type );
break;

The CChildrenBenefitsData<>::OnShow(...) implementation, which is very similar to the template's OnExpand(...) function, is a common method of enumerating a list and adding the items to the console:

    STDMETHOD( OnShow )( MMC_NOTIFY_TYPE
event,
long arg,
long param,
IConsole* pConsole,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( event );
UNUSED_ALWAYS( param );
UNUSED_ALWAYS( type );
HRESULT hr = E_NOTIMPL;
ATLTRACE2(atlTraceSnapin, 0,
_T("CChildrenBenefitsData::OnExpand\n"));

//
// Only add the items if we're being selected.
//
if ( arg == TRUE )
{
CComQIPtr<IResultData,&IID_IResultData> spResultData( pConsole );

//
// Loop through and add
each subnode.
//
for ( int i = 0; i <
m_Nodes.GetSize(); i++ )
{
CSnapInItem* pNode;
RESULTDATAITEM* pResultData;

pNode = m_Nodes[ i ];
_ASSERTE( pNode != NULL );

//
// Get the scope pane info for the node and set
// relative id.
//
pNode->GetResultData( &pResultData );
_ASSERTE( pResultData != NULL );

//
// Add the item to the scope list using the newly
// populated scope data item.
//
hr = spResultData->InsertItem( pResultData );
_ASSERTE( SUCCEEDED( hr ) );
}
}

return( hr );
};

Again, your function enumerates the m_Nodes member of the template
and inserts the items using the IResultData interface.

Displaying a Web Page

DisplayWebPage.gif (12990 bytes)

To change the Health node from displaying a standard list to displaying a web page, you must add (and therefore override) the GetResultViewType(...) function. By overriding this function, you customize the type of view to display. The ppViewType and pViewOptions parameters to the function should specify how you want to display results.

In the case of the Health node, you will want to display a custom web page. To specify the location of a web page, the given ppViewType must contain a string containing the URL of the page to be displayed. It is important to note that, when initializing ppViewType with the URL, you must use the OLE allocator CoTaskMemAlloc(...):

STDMETHODIMP CHealthNode::GetResultViewType( LPOLESTR*
ppViewType, long* pViewOptions )
{
USES_CONVERSION;
TCHAR szPath[ _MAX_PATH ];
TCHAR szModulePath[ _MAX_PATH ];

//
// Use the HTML page that is embedded as a resource of
// this module for display purposes.
//
GetModuleFileName( _Module.GetModuleInstance(), szModulePath, _MAX_PATH
);

//
// Append the necessary decorations for correct access.
//
_tcscpy( szPath, _T( "res://" ) );
_tcscat( szPath, szModulePath );
_tcscat( szPath, _T( "/HEALTH.HTM" ) );

//
// CoTaskMemAlloc(...) must be used since the MMC client frees the
space using
// CoTaskMemFree(...). Include enough space for NULL.
//
*ppViewType = (LPOLESTR) CoTaskMemAlloc( ( _tcslen( szPath ) + 1 ) *
sizeof( OLECHAR )     _ASSERTE( *ppViewType != NULL );
ocscpy( *ppViewType, T2OLE( szPath ) );

return( S_OK );
}

The GetModuleFileName(...) call allows you to use a web page that is embedded in your snap-in as a resource. When you specify the location of your DLL and append the HTML file name, MMC will load the resource as an HTML file and display it appropriately.

Displaying an OCX

DisplayingOCX.gif (7540 bytes)

Displaying an OCX in the result pane is done in much the same way as displaying a web page. GetResultViewType(...) is overridden to customize the type of view to display.

In the case of the Retirement node, instead of returning a URL in the ppViewType parameter, a CLSID is specified:

STDMETHODIMP CRetirementNode::GetResultViewType(
LPOLESTR* ppViewType, long* pViewOptions )
{
USES_CONVERSION;

//
// For this example to work, the sample control must be installed.
//
TCHAR* pszControl = _T(
"{B3EBD025-E381-11D1-8479-00104B211BE5}" );

// CoTaskMemAlloc(...) must be used since the MMC client frees the
space using
// CoTaskMemFree(...). Include enough space for NULL.
//
*ppViewType = (LPOLESTR) CoTaskMemAlloc( ( _tcslen( pszControl ) + 1 )
* sizeof( OLECHAR ) );
_ASSERTE( *ppViewType != NULL );
ocscpy( *ppViewType, T2OLE( pszControl ) );

return( S_OK );
}

The control is instantiated by MMC and displayed. If the sample control does not display properly, ensure that you have SampCtrl.ocx (included in the SampCtrl directory).

To perform additional initialization of the control or to hold a pointer to the control in which you can fire events, you need to add a handler for the MMCN_INITOCX event that is sent to the CRetirementNode::Notify(...) function.

In the Benefits sample case, no further initialization is required. However, because you will later trigger events on the control, you should add a member to hold a pointer to the control:

_SampleControlPtr m_spControl;

To get a valid pointer to the control, you should add the following handler to the MMCN_INITOCX event:

        case MMCN_INITOCX:
            //
            // The IUnknown of the OCX is passed in. Do
            // any other initialization of the OCX here.
            //
            m_spControl = (LPUNKNOWN) param;
            hr = S_OK;
            break;

The pointer can be used to make calls into the control. In response to some menu and button commands, you will add code to call into this control. See Adding Command Handlers.


Step 7: Adding Menus

You will be extending menus for each of the unique nodes you have created. The ATL snap-in templates greatly simplify the steps needed to extend MMC menus. The templates process standard Microsoft Developer Studio-compatible resources and call the appropriate MMC interfaces to add menu items.

Creating Menu Resources

In your sample, a menu resource with the ID of IDR_BENEFITS_MENU has been created for you by the ATL Object Wizard. To create custom menus for each of your nodes:

  1. Rename the resource ID of IDR_BENEFITS_MENU to IDR_ACCESS_MENU.
  2. Copy and paste the menu resource and rename the ID to IDR_ENROLL_MENU.
  3. Paste the menu resource again and rename the ID to IDR_UPDATE_MENU.

The Health node uses the IDR_ENROLL_MENU resource, the Key node uses the IDR_ACCESS_MENU resource, and the Retirement node uses the IDR_UPDATE_MENU resource. To indicate which node uses which menu resource, the SNAPINMENUID(...) macro is provided. The macro must be added in each node implementation:

  1. Add SNAPINMENUID( IDR_UPDATE_MENU ) to the CRetirementNode declaration.
  2. Add SNAPINMENUID( IDR_ENROLL_MENU ) to the CHealthNode declaration.
  3. Add SNAPINMENUID( IDR_ACCESS_MENU ) to the CKeyNode declaration.

After adding the macro, your node declarations should look something like:

class CRetirementNode : public CBenefitsData< CRetirementNode >
{
public:
    SNAPINMENUID( IDR_UPDATE_MENU )
...
}

Creating Individual Menu Items

The menu resource added by the ATL Object Wizard looks something like:

SnapinMenu.gif (2895 bytes)

The ATL snap-in templates use the layout of the menu only for logical layout. The template processing code inserts a new menu item into a specific MMC menu according to where you insert the new item in the menu layout:

  • Menu items added to the Top menu will be added to the base menu.
  • Menu items added to the New menu will be added to the New submenu.
  • Menu items added to the Task menu will be added to the Task submenu.
  • Menu items added to the View menu will be added to the View submenu.

In your sample, the following menu items are added to the indicated menu. To add each individual menu item:

  1. Double-click on the menu resource in DevStudio's Resource View. The menu editor appears with the selected menu available.
  2. Click on menu you wish to add the specific menu item. A drop-down menu appears.
  3. Double-click on the next available menu slot. The Menu Item Properties dialog appears.
  4. Enter the menu ID in the ID field.
  5. Enter the text for the menu in the Caption field.

IDR_ACCESS_MENU

  • "Building Access" added to the New menu. Use ID_NEW_BUILDINGACCESS as its ID.

IDR_ENROLL_MENU

  • "Import" added to the Top menu. Use ID_TOP_IMPORT as its ID.
  • "Export" added to the Top menu. Use ID_TOP_EXPORT as its ID.
  • "Enroll" added to the Task menu. Use ID_TASK_ENROLL as its ID.
  • "View web resources" added to the View menu. Use ID_VIEW_TASKPAD as its ID.

IDR_UPDATE_MENU

  • "Update" added to the Top menu. Use ID_TOP_UPDATE as its ID.
  • "Enroll" added to the Task menu. Use ID_TASK_ENROLL as its ID.

To respond to menu input, refer to Adding Command Handlers.


Step 8: Adding Toolbars

To customize toolbars for the Key and Health nodes, the approach is much the same as customizing menu resources. Toolbar resources can be modified and added to the project's resources. The ATL snap-in templates process the resources and call the appropriate MMC interfaces to display your node's toolbar. Further, the ATL snap-in templates implement functionality to automatically display, remove, and clean up these toolbars.

Toolbar.gif (2523 bytes)

Creating the Toolbars

To insert a toolbar resource into your resource script:

  1. Right-click BenSvr resources in Developer Studio's Workspace window.
  2. Click Insert. The Insert Resource dialog box appears.
  3. Under Resource Type, click Toolbar
  4. Click New.

A new, bare-bones toolbar is added to your project. Repeat the operation to create a second toolbar. You can then access each toolbar's properties and change their resource IDs to IDR_ACCESSTOOLBAR and IDR_ENROLLTOOLBAR.

Associating Toolbars with Nodes

Any number of toolbars can be associated with a node. The ATL snap-in templates handle toolbar loading, initialization, display, and teardown. You associate a toolbar with an individual node using macros very similar to the macros used for associating menu resources:

    BEGIN_SNAPINTOOLBARID_MAP( CHealthNode )
        SNAPINTOOLBARID_ENTRY( IDR_ENROLLTOOLBAR )
    END_SNAPINTOOLBARID_MAP()

The BEGIN_SNAPINTOOLBARID_MAP(...) and END_SNAPINTOOLBARID_MAP(...) macros indicate the start and end of entries in the indicated node's toolbar map. You associate an individual toolbar with a node by adding the SNAPINTOOLBARID_ENTRY(...) macro, containing the toolbar's resource ID, to the list braced by the BEGIN_SNAPINTOOLBARID_MAP(...) and END_SNAPINTOOLBARID_MAP(...) macros.

In your snap-in, the nodes use only two toolbars. The Health node uses the IDR_ENROLLTOOLBAR resource and the Key node uses the IDR_ACCESSTOOLBAR resource. After the association of the menu and toolbar resources, the health node looks like:

class CHealthNode : public CBenefitsData< CHealthNode >,
{
...
public:
    SNAPINMENUID( IDR_ENROLL_MENU )
    BEGIN_SNAPINTOOLBARID_MAP( CHealthNode )
        SNAPINTOOLBARID_ENTRY( IDR_ENROLLTOOLBAR )
    END_SNAPINTOOLBARID_MAP()
...
};

The ATL snap-in templates will now load and display the toolbar resource IDR_ENROLLTOOLBAR and use it to call the appropriate MMC interfaces when the Health node is selected. When the node is deselected, the templates will remove and even destroy the toolbar as necessary.

Add the toolbar macros to the CKeyNode definition. The definition should look like:

class CKeyNode : public CChildrenBenefitsData< CKeyNode >
{
...
public:
    SNAPINMENUID( IDR_ACCESS_MENU )
    BEGIN_SNAPINTOOLBARID_MAP( CKeyNode )
        SNAPINTOOLBARID_ENTRY( IDR_ACCESSTOOLBAR )
    END_SNAPINTOOLBARID_MAP()
...
}


Step 9: Adding Command Handlers

Command handlers are functions that you specify should be called when a user selects a menu item, button, or other activity. In many cases, the same function is used for a menu item and button that are identical in nature.

Associating Commands with Functions

The Benefits sample contains very simple command handlers to respond to menu and button clicks from the user. To associate a handler with either a menu item or a toolbar button, identical macros are used:

    BEGIN_SNAPINCOMMAND_MAP( CHealthNode,
FALSE )
SNAPINCOMMAND_ENTRY(ID_TASK_ENROLL, OnEnroll)
SNAPINCOMMAND_ENTRY(ID_TOP_IMPORT, OnImport)
SNAPINCOMMAND_ENTRY(ID_TOP_EXPORT, OnExport)
SNAPINCOMMAND_ENTRY(ID_VIEW_TASKPAD, OnTaskpad)
END_SNAPINCOMMAND_MAP()

Similar to the toolbar map, the SNAPINCOMMAND_ENTRY(...) macro maps a resource ID to a function. It identifies a menu item ID or button ID and the function that should be called when the user clicks the item or button. The BEGIN_SNAPINCOMMAND_MAP(...) and END_SNAPINCOMMAND_MAP(...) macros mark the beginning and end of the command map.

Add the following entries to your snap-in:

  • SNAPINCOMMAND_ENTRY(ID_TASK_ENROLL, OnEnroll) to CHealthNode.
  • SNAPINCOMMAND_ENTRY(ID_TOP_IMPORT, OnImport) to CHealthNode.
  • SNAPINCOMMAND_ENTRY(ID_TOP_EXPORT, OnExport) to CHealthNode.
  • SNAPINCOMMAND_ENTRY(ID_NEW_BUILDINGACCESS, OnGrantAccess) to CKeyNode.
  • SNAPINCOMMAND_ENTRY(ID_TASK_ENROLL, OnEnroll) to CRetirementNode.
  • SNAPINCOMMAND_ENTRY(ID_TOP_UPDATE, OnUpdate) to CRetirementNode.

Any number of IDs can be associated with any one function. Menus and toolbar buttons whose resource IDs differ can be linked to the same functionality by making two SNAPINCOMMAND_ENTRY(...) macro entries into the command map.

Implementing a Command Handler

MessageBox.gif (2073 bytes)

When the user clicks a menu item or button, the ATL snap-in framework calls the associated command handler function, using the map defined earlier. Command handler functions must be declared like:

    STDMETHOD( OnEnroll )( bool& bHandled, CSnapInObjectRootBase* pObj );

The parameters bHandled and pObj are ignored in your command handlers in most cases, as they are used by the ATL snap-in templates. The body can contain any functionality you choose. In response to the user clicking the Enroll menu item, the following function displays an MMC console message box:

STDMETHODIMP CHealthNode::OnEnroll( bool& bHandled,
CSnapInObjectRootBase* pObj )
{
UNUSED_ALWAYS( bHandled );
UNUSED_ALWAYS( pObj );
CComPtr<IConsole> spConsole;
int nResult;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( T2OLE( _T( "Enrolled" ) ),
T2OLE( _T( "Benefits" ) ),
MB_ICONINFORMATION | MB_OK,
&nResult );

return( S_OK );
}

Add command handlers to the Key and Retirement nodes for the maps defined earlier.


Step 10: Implementing Console Verbs

Console verbs allow the use of predefined MMC commands. The commands, like Properties, Copy, and Paste, are supplied by MMC as menu items. These console verbs can be made available on the context menu of any of your nodes by enabling them. As opposed to routing a user's input through the command maps described in Adding Command Handlers, notifications are sent to the appropriate node's Notify(...) function.

Enabling Console Verbs

PropertiesMenu.gif (4719 bytes)

Your snap-in will support property pages and copy/paste functionality. The Key node will support the pasting of CBuildingNodes, its leaf node types. CBuildingNode will support the Delete and Copy verbs. Finally, CRootNode will support the Properties verb to display the property pages you will create in Displaying Property Pages.

The most appropriate time to enable console verbs is when a node is selected by a user. Add MMCN_SELECT processing to the Building, Key, and Root nodes to call an OnSelect(...) function that you will define later:

...
switch (event)
{
case MMCN_SELECT:
hr = OnSelect(
spConsole );
break;
...

In the case of the Root node, add the following:

STDMETHODIMP CRootNode::OnSelect( IConsole* pConsole )
{
//
// Since we display property pages, make sure that the property page
// verb is enabled.
//
CComPtr<IConsoleVerb> spConsoleVerb;
HRESULT hr = pConsole->QueryConsoleVerb( &spConsoleVerb );

//
// Enable the properties verb.
//
hr = spConsoleVerb->SetVerbState( MMC_VERB_PROPERTIES, ENABLED, TRUE
);
_ASSERTE( SUCCEEDED( hr ) );

return( hr );
}

The function first queries the IConsole interface for the IConsoleVerb interface. The IConsoleVerb interface is the interface used to modify the state of console verbs. The SetVerbState(...) function allows you to identify the console verb you wish to modify, the verb's new state, and whether or not the verb should be displayed. In the case of CRootNode, you have identified that the Properties verb should be displayed.

Add OnSelect(...) handlers to the Building and Key nodes to enable the MMC_VERB_COPY, MMC_VERB_DELETE, and MMC_VERB_PASTE console verbs.

Handling Console Verbs

You do not add command handlers and map entries to respond to console verbs. For each console verb, notifications have been defined and are sent to your node's Notify(...) function whenever the user selects one of the verbs. In the case of the MMC_VERB_PASTE, the MMCN_PASTE notification is sent to your node. The MMCN_COPY notification is sent to your node when the user selects the MMC_VERB_COPY verb menu item, and so on.

In your snap-in, add processing to the Root node, Key node, and Building node that catches the appropriate notification in the Notify(...) function. Functions will be added to each node's implementation to simplify the processing and make the code more readable. The Key node's processing should look like:

        case
MMCN_PASTE:
//
// Called by MMC when
the item should be pasted.
//
hr = OnPaste(
spConsole, (LPDATAOBJECT) arg, (LPDATAOBJECT*) param );
break;

In the case of responding to the Paste verb, you would create a function that creates a new node and uses the arg and param parameters to initialize it. Each console verb notification is handled in a unique way. The Properties verb is handled entirely differently and is described in Displaying Property Pages.


Step 11: Adding Persistence

Before it can display properties, your snap-in must be given some properties. A definition of an employee must be added because the Benefits sample performs functionality regarding an employee. Further, the ability to store the employee information between sessions is crucial for your snap-in.

Adding the Employee Definition

Insert a new class into your project called CEmployee. Add members to this class to contain general information regarding the employee:

    WCHAR m_szFirstName[ 256 ];         // Holds first name.
    WCHAR m_szLastName[ 256 ];        // Holds last name.
    WCHAR m_szSocialSecurity[ 256 ]; // Holds the social security number.
    WCHAR m_szMotherMaiden[ 256 ];    // Holds mother's maiden name for identification.
    WCHAR m_szAddress1[ 256 ];        // Holds first line of address.
    WCHAR m_szAddress2[ 256 ];        // Holds second line of address.
    WCHAR m_szCity[ 256 ];             // Holds city name.
    WCHAR m_szState[ 256 ];             // Holds the state.
    WCHAR m_szZip[ 256 ];             // Holds the zip code.
    WCHAR m_szPhone[ 256 ];             // Holds a phone number.

It is important to note that WCHAR is being used as the character type for your snap-in. This is to ensure compatibility of the stored structure when serialized between Unicode and non-Unicode versions of your snap-in.

Structures containing task-oriented data can also be added. In this case, sample structures called HEALTHPLAN, RETIREMENTPLAN, and ACCESS are added:

    // Information used for the subnodes.
HEALTHPLAN m_Health;
            // Health information.
RETIREMENTPLAN m_Retirement;    // Retirement
information.
ACCESS m_Access;

// Card-key access information.

Accessing the Employee

Because your snap-in only accesses one employee at any given time, only one instance of CEmployee is necessary. Because there is only one instance of CRootNode, this is the logical class to contain your employee definition:

class CRootNode : public CChildrenBenefitsData<
CRootNode >
{
...
//
// Contains the the employee's entire datastore for this
// sample.
//
CEmployee m_Employee;
...

}

Because the Root node creates the subnodes, the constructor of each node type can be modified to accept a pointer to an instance of CEmployee:

    CKeyNode( CEmployee* pCurEmployee );

To ease implementation, a member pointer to CEmployee should be added to CBenefitsData<>, the template used by all of the Benefits nodes. As a result, any node can access the current employee's information by using CBenefitsData<>::m_pEmployee.

Persisting Your Snap-In

Because the Persist check box was selected in the ATL Object Wizard when you created the snap-in, the wizard generated all of the functions necessary to support the IPersistStreamInit interface. The remaining task is to add code to these stubbed functions to serialize the contents of CEmployee.

CBenefits, containing the IComponentData support, is the appropriate class to contain persist functionality. While CBenefitsComponent, containing IComponent support, includes persistence support, information stored there should be more view-oriented. You will modify CBenefits to call nearly identical functions implemented by its member m_pNode, your Root node. As a result, you should add the definition of the following functions to CRootNode:

    //
// Uses the dirty flag to determine whether or not this node
// needs to be persisted.
//
STDMETHOD(IsDirty)()
{
return ( m_fDirty ? S_OK : S_FALSE );
}

//
// Loads the employee information from the stream.
//
STDMETHOD(Load)(LPSTREAM pStm)
{
DWORD dwRead;

pStm->Read( &m_Employee, sizeof(
m_Employee ), &dwRead );
_ASSERTE( dwRead == sizeof( m_Employee ) );

return( S_OK );
}

//
// Stores the employee information to the stream and clears
// our dirty flag.
//
STDMETHOD(Save)(LPSTREAM pStm, BOOL fClearDirty)
{
DWORD dwWritten;

pStm->Write( &m_Employee, sizeof(
m_Employee ), &dwWritten );
_ASSERTE( dwWritten == sizeof( m_Employee ) );

//
// Clear the dirty flag.
//
if ( fClearDirty )
m_fDirty = FALSE;

return( S_OK );
}

//
// Returns the size of the employee structure.
//
STDMETHOD(GetSizeMax)(ULARGE_INTEGER FAR* pcbSize )
{
pcbSize->LowPart = sizeof( m_Employee );
return( S_OK );
}

This implementation simply writes or reads the data from the given stream and manages the dirty flag. True persist interfaces could be added to the implementation of CEmployee.


Step 12: Displaying Property Pages

The ATL snap-in templates provide excellent property page support. Because of the options you selected when running the ATL Object Wizard, IExtendPropertySheet interface support was added to both CBenefits and CBenefitsComponent. In addition, a CBenefitPage class, containing the basic framework for all property pages, was created for you.

Because your snap-in will contain a dialog box for input of employee information, you need to create two property pages. CEmployeeNamePage implements a user interface to collect name information about the employee and CEmployeeAddressPage implements a user interface to collect address information about the employee. Both pages will be available by accessing the Properties of the Root node. When the snap-in is inserted, both pages will be presented to the user in a wizard mode to collect initial employee information.

To create the pages:

  1. Rename CBenefitsPage to CEmployeeNamePage.
  2. Modify enum to specify IDD_NAME_PAGE.
  3. Copy CEmployeeNamePage and paste to new location in file.
  4. Rename pasted class definition to CEmployeeAddressPage.
  5. Modify enum to specify IDD_ADDRESS_PAGE.
  6. Change the ID of the default dialog resource from IDD_BENEFITS to IDD_NAME_PAGE.
  7. Copy and paste the dialog resource.
  8. Change new dialog's ID to IDD_ADDRESS_PAGE.

You should create the necessary controls for each page's dialog box to collect the necessary information to populate the CEmployee's properties:

EmployeeNamePage.gif (3891 bytes)

EmployeeAddressPage.gif (3847 bytes)

To store the information, add the OnFinish(...) function to both implementations to set and store values to and from the controls. The OnFinish(...) function for CEmployeeNamePage looks like:

BOOL CEmployeeNamePage::OnWizardFinish()
{
USES_CONVERSION;
BOOL fValid = TRUE;
TCHAR szBuf[ 256 ];

//
// Get the values from the appropriate edit controls.
// For demo purposes, always assume success unless the last name
// and first are bad.
//
GetDlgItemText( IDC_EDIT_LASTNAME, szBuf, sizeof( szBuf ) );
wcscpy( m_pEmployee->m_szLastName, T2W( szBuf ) );
GetDlgItemText( IDC_EDIT_FIRSTNAME, szBuf, sizeof( szBuf ) );
wcscpy( m_pEmployee->m_szFirstName, T2W( szBuf ) );
GetDlgItemText( IDC_EDIT_SOCIALSECURITY, szBuf, sizeof( szBuf ) );
wcscpy( m_pEmployee->m_szSocialSecurity, T2W( szBuf ) );
GetDlgItemText( IDC_EDIT_MOTHERNAME, szBuf, sizeof( szBuf ) );
wcscpy( m_pEmployee->m_szMotherMaiden, T2W( szBuf ) );

//
// Check for validity of first name and last name.
//
if ( wcslen( m_pEmployee->m_szLastName ) == 0 || wcslen(
m_pEmployee->m_szFirstName ) == 0 ||
m_pEmployee->m_szLastName[ 0 ] == ' ' ||
m_pEmployee->m_szFirstName[ 0 ] == ' ' )
{
//
// Inform the user of the error.
//
MessageBox( _T( "Must enter valid first
and last name." ) );

fValid = FALSE;
}
else
{
//
// Data is valid. As a result, post a
notification to the snap-in
// that the employee contents have changed.
This demonstration does
// not use any sort of hinting, so NULL can be
safely pass in as
// the notification's argument.
//
PropertyChangeNotify( NULL );
}

return( fValid );
}

The call to PropertyChangeNotify(...) causes MMC to notify your snap-in's IComponent and IComponentData that its properties have changed.

Displaying Property Pages

To determine if property pages can or should be viewed, MMC utilizes the IExtendPropertySheet interface. First, MMC calls the QueryPagesFor(...) function of your snap-in. You must add your own QueryPagesFor(...) implementation to CRootNode:

STDMETHOD( QueryPagesFor )(DATA_OBJECT_TYPES type)
    {
        if ( type == CCT_SCOPE || type == CCT_RESULT || type == CCT_SNAPIN_MANAGER )
            return S_OK;
        return S_FALSE;
    }

Based on the current state of MMC, the value of type changes. Because you would like your pages displayed when the scope pane is used to invoke the Properties console verb, a CCT_SCOPE type is a legitimate value. Because the pages should be displayed when invoked from the result pane, the CCT_RESULT type is legitimate. While normal for most cases, your snap-in will also display the property pages in a wizard mode when the snap-in is first inserted. As a result, CCT_SNAPIN_MANAGER is considered a legitimate value.

Assuming that S_OK was returned from QueryPagesFor(...), MMC then asks your snap-in to display the pages using the CreatePropertyPages(...) function. Your function should test the current mode of MMC and set the display of the pages to either wizard mode or a standard property sheet display. After initializing the pages with the employee structure, the pages are constructed, added, and created:

STDMETHODIMP
CRootNode::CreatePropertyPages(LPPROPERTYSHEETCALLBACK lpProvider,
long handle,
IUnknown* pUnk,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( pUnk );
HRESULT hr = E_UNEXPECTED;

if ( type == CCT_SCOPE || type == CCT_RESULT || type ==
CCT_SNAPIN_MANAGER )
{
bool fStartup;

//
// Set the start-up flag based on the type of
pages to be
// created.
//
fStartup = type == CCT_SNAPIN_MANAGER ? true :
false;

//
// Allocate the new page. The second parameter
of the constructor
// indicates whether or not this is the
start-up wizard. The dialog
// handler will update the UI appropriately.
//
CEmployeeNamePage* pNamePage = new
CEmployeeNamePage( handle, fStartup, false, _T( "Employee Name" ) );
CEmployeeAddressPage* pAddressPage = new
CEmployeeAddressPage( handle, fStartup, false, _T( "Employee Address" ) );

//
// Set the page's employee.
//
pNamePage->m_pEmployee = &m_Employee;
pAddressPage->m_pEmployee = &m_Employee;

lpProvider->AddPage( pNamePage->Create()
);
lpProvider->AddPage(
pAddressPage->Create() );

//
// The second parameter to the property page
class constructor
// should be true for only one page.
//
hr = S_OK;
}

return( hr );
}


Step 13: Creating Taskpads

The 1.1 version of the Microsoft Management Console includes a variety of new features. One of the most significant enhancements is the inclusion of Taskpads. Taskpads allow a snap-in to utilize an interface to enumerate common tasks it can perform. These tasks are then used by MMC to display them as a script based web page, though this may change in the future. As a result, a snap-in can programmatically describe tasks and handle task notifications without worrying about specific user interface details

The current release of the ATL Snap-In Wizard and templates do not inherently support these taskpad features. But, because of the flexible nature of the Snap-In templates, you can extend them with a few additional templates to incorporate these features. With the Benefits sample, you will create a number of templates, hook them into your existing code, and add a few resources to access the Taskpad from your Snap-In's user interface.

Taskpad.gif (6842 bytes)

Creating the IExtendTaskpadImpl Template

To utilize Taskpads, your Snap-In is required to support the IExtendTaskpad interface. By implementing IExtendTaskpad in a template, you can utilize the template to quickly create Taskpads in future Snap-Ins. Since this will be somewhat portable, it is best to create the template in a new header file:

  1. Select the Project menu.
  2. Select New... from the Add To Project pop-up menu. The New dialog appears.
  3. Select C/C++ Header File from the File tab.
  4. Enter "AtlTask.h" into the File name field.
  5. Select OK.

At this point, a new header file titled AtlTask.h has been created for you. Open the file to begin inserting your new template definition. Add the following template definition:

template <class T>
class ATL_NO_VTABLE IExtendTaskPadImpl : public IExtendTaskPad
{
};

Your new template has now been declared. You will now add the supporting code to the template that implement the required IExtendTaskPad functions. The functions that should be added are included in the ATL Taskpad Template page. The following function is one of the most basic functions and a brief explanation of its workings follow.

    STDMETHOD( GetTitle )( LPOLESTR
pszGroup, LPOLESTR * pszTitle )
{
UNUSED_ALWAYS( pszGroup );
USES_CONVERSION;
HRESULT hr = E_FAIL;
T* pT = static_cast<T*>(this);

try
{
//
// Allocate memory for
the title.
//
*pszTitle = (LPOLESTR)
CoTaskMemAlloc( ( wcslen( pT->m_pszTitle ) + 1 ) * sizeof( OLECHAR ) );
if ( pszTitle == NULL )
throw;

//
// Copy the title.
//
wcscpy( *pszTitle,
pT->m_pszTitle );
hr = S_OK;
}
catch( ... )
{
}

return( hr );
}

IExtendTaskPadImpl::GetTitle(...)
is called by MMC to retrieve the title you wish to have displayed when it is displayed to
the user. As with many other MMC functions, the returned value should be allocated using CoTaskMemAlloc(...)
and set to the given pszTitle parameter. The template utilizes pT->m_pszTitle
to initilize the title, which is a member of the class using the template. You will add a
number of supporting members and initialize them within your snap-in later.

When supporting multiple Taskpads, the pszGroup parameter allows your Snap-In to identify unique Taskpad implementations and respond to MMC requests as appropriate. In your case, you do not support multiple Taskpads and will always ignore the pszGroup parameter.

IExtendTaskPadImpl::GetBackground(...) is called by MMC to retrieve an image you wish to use as a background image when the Taskpad is displayed as a web page. The image can be a bitmap or GIF (MMC will automatically process either). In your implementation, the function determines the path of your Snap-In's module, appends the image's title identified by pT->m_szBackgroundPath, and initializes the given MMC_TASK_DISPLAY_OBJECT with it. Again, you will initialize pT->m_szBackground later.

IExtendTaskPadImpl::GetDescriptiveText(...) is called by MMC to retrieve tooltip text that is displayed to the user when the cursor hovers over the Taskpad. This implementation does not implement this function.

IExtendTaskPadImpl::GetListPadInfo(...) is called when you implement a Taskpad in a list view. The sample does not use a list view to display its tasks, so the function is not implemented.

IExtendTaskPadImpl::EnumTasks(...) is called when MMC needs to determine the available tasks in your Snap-In. MMC expects the function to return an IEnumTASK interface that enumerates MMC_TASK structures. The template's implementation derives a CTaskpadItem pointer, which you will later add to the health node, from the currently selected node within MMC. This function then calls CTaskpadItem::EnumTasks(...), which does the meat of the work.

IExtendTaskPadImpl::TaskNotify(...) is called when a user selects a task Tasks can be setup to do a number of things, which include loading other web pages, executing a script, or notify your Snap-In when it is selected. IExtendTaskPadImpl::TaskNotify(...) is only called when a task is setup to be notified by setting its type to MMC_ACTION_ID. In the template's case, it again obtains a CTaskpadItem pointer and calls CTaskpadItem::TaskNotify(...).

Creating the CTaskpadItem Helper Class

Copy the helper class, CTaskpadItem, to the ATLTask.h file you have created. The class is used to maintain the node-centric architecture used through-out the ATL Snap-In template architecture and is necessary for the IExtendTaskPadImpl implementation to work. Any node that wishes to have tasks available for Taskpad support would derive from CTaskpadItem. While the class supplies a few helper functions, a node using the class should override EnumTasks(...) and TaskNotify(...) to do any real work.

The major function included with the class, CTaskpadItem::CoTasksDup(...), allows nodes which override EnumTasks(...) and TaskNotify(...) to define a static member array containing a list of supported tasks and call it to perform the appropriate memory allocations necessary.

Adding the Template Support to the Sample

Once the above templates and classes are defined, you can begin to modify CHealthNode to support three sample tasks. Two of the tasks will dispatch to other web pages and one task will notify your Snap-In that it has been selected.

Add the IExtendTaskPadImpl template to your CBenefitsComponent declaration. To make Taskpad functionality available to MMC, your IComponent implementation must implement IExtendTaskPad, which the template you created does. The CBenefitsComponent definition should look like:

class CBenefitsComponent : public CComObjectRootEx<CComSingleThreadModel>,
    public CSnapInObjectRoot<2, CBenefits >,
    public IExtendPropertySheetImpl<CBenefitsComponent>,
    public IExtendContextMenuImpl<CBenefitsComponent>,
    public IExtendControlbarImpl<CBenefitsComponent>,
    public IComponentImpl<CBenefitsComponent>,
    public IExtendTaskPadImpl<CBenefitsComponent>

Since the IExtendTaskPadImpl template expects the background image and title of the Taskpad to be available, the following members should be added to the CBenefitsComponent definition:

    LPOLESTR m_pszTitle;
    LPOLESTR m_pszBackgroundPath;

In the CBenefitsComponent constructor, these members should be initialized:

    CBenefitsComponent()
{
//
// Taskpad initialization stuff.
//
m_pszTitle = L"Benefits Taskpad";
m_pszBackgroundPath = NULL;
}

Since we are not using a background image for the Benefits Taskpad, it is
safe to initilize m_pszBackgroundPath to NULL. At this point, all the
necessary modifications to CBenefitsComponent have been made.

Since the health node will support a Taskpad and number of tasks, you will need to add a number of functions to CHealthNode. Because the CTaskpadItem class is required by the IExtendTaskPadImpl, you must add its derivation to your CHealthNode definition:

class CHealthNode : public CBenefitsData< CHealthNode >,
    public CTaskpadItem

To indicate to MMC that you wish to utilize a Taskpad for display, you must modify CHealthNode::GetResultViewType(...):

STDMETHODIMP CHealthNode::GetResultViewType( LPOLESTR*
ppViewType, long* pViewOptions )
{
USES_CONVERSION;
TCHAR szPath[ _MAX_PATH ];
TCHAR szModulePath[ _MAX_PATH ];

//
// Set the view options to no preferences.
//
*pViewOptions = MMC_VIEW_OPTIONS_NONE;

if ( m_fTaskpad )
{
//
// In the taskpad case, the module path of
MMC.EXE should be
// obtained. Use the template contained
therein.
//
GetModuleFileName( NULL, szModulePath,
_MAX_PATH );

//
// Append the necessary decorations for correct
access.
//
_tcscpy( szPath, _T( "res://" ) );
_tcscat( szPath, szModulePath );
_tcscat( szPath, _T( "/default.htm" )
);
}
else
{
//
// Use the HTML page that is embedded as a
resource of
// this module for display purposes.
//
GetModuleFileName( _Module.GetModuleInstance(),
szModulePath, _MAX_PATH );

//
// Append the necessary decorations for correct
access.
//
_tcscpy( szPath, _T( "res://" ) );
_tcscat( szPath, szModulePath );
_tcscat( szPath, _T( "/health.htm" )
);
}

//
// CoTaskMemAlloc(...) must be used since the MMC client frees the
space using
// CoTaskMemFree(...). Include enough space for NULL.
//
*ppViewType = (LPOLESTR) CoTaskMemAlloc( ( _tcslen( szPath ) + 1 ) *
sizeof( OLECHAR ) );
_ASSERTE( *ppViewType != NULL );
ocscpy( *ppViewType, T2OLE( szPath ) );

return( S_OK );
}

The process for displaying a Taskpad is much the same as displaying a web page. In the case of a Taskpad, you want to utilize the GetModuleFileName(...) function, with it's first parameter as NULL, to identify the location of MMC.EXE. You utilize the module path and "default.htm" to build a document location that causes MMC to load its internal Taskpad template. After loading its internal Taskpad template, MMC will utilize the IExtendTaskpad interface to obtain your Snap-In's tasks.

It is necessary to identify identify the tasks that CHealthNode supports. Add the following to BenNodes.cpp:

//
// Hard coded tasks to be associated with the health node.
//
MMC_TASK g_HealthTasks[ 3 ] =
{
    { MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\WebPage.gif", L"img\\WebPage.gif", L"Microsoft", L"General Microsoft resources", MMC_ACTION_LINK, (long) L"http://www.microsoft.com" },
    { MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\WebPage.gif", L"img\\WebPage.gif", L"Microsoft Management Site", L"More MMC oriented resources", MMC_ACTION_LINK, (long) L"http://www.microsoft.com/management" },
    { MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\Query.gif", L"img\\Query.gif", L"Local Query", L"Start query on local database", MMC_ACTION_ID, TASKPAD_LOCALQUERY },
};

This static variable defines three tasks that you will support from CHealthNode. Simply remove or add additional definitions to remove or add new tasks to your Taskpad. By using the MMC_ACTION_LINK styles, the first two tasks identify web pages MMC should load when the task is selected. The third task uses MMC_ACTION_LINK, which means that the TASKPAD_LOCALQUERY ID will be sent to your Snap-In when selected. The remaining parameters are well documented in the SDK.

To make these tasks available, you must enumerate these tasks when EnumTasks(...) is called. You must override the CTaskpadItem::EnumTasks(...) function by adding the following implementation:

STDMETHODIMP CHealthNode::EnumTasks( LPOLESTR szTaskGroup,
IEnumTASK** ppEnumTASK )
{
UNUSED_ALWAYS( szTaskGroup );
MMC_TASK CoTasks[ sizeof( g_HealthTasks ) / sizeof( MMC_TASK ) ];
typedef CComObject< CComEnum< IEnumTASK, &IID_IEnumTASK,
MMC_TASK, _Copy<MMC_TASK> > > enumvar;
enumvar* p = new enumvar;

//
// Copy the local tasks to our temporary task structures. This
// performs the CoTaskMemAlloc for the strings, etc. It also
// maps image type resources to the local module name.
//
if ( CoTasksDup( CoTasks, g_HealthTasks, sizeof( g_HealthTasks ) /
sizeof( MMC_TASK ) ) )
{
p->Init( &CoTasks[ 0 ], &CoTasks[
sizeof( g_HealthTasks ) / sizeof( MMC_TASK ) ], NULL, AtlFlagCopy);
return( p->QueryInterface( IID_IEnumTASK,
(void**) ppEnumTASK ) );
}

return( E_FAIL );
}

The function duplicates the tasks using CTaskpadItem::CoTasksDup(...),
initializes an ATL based COM object that implements IEnumTASK, and
returns its IEnumTASK interface.

The remaining function, CTaskpadItem::TaskNotify(...), must be implemented to respond to the notification sent from MMC when the third task is selected:

STDMETHODIMP CHealthNode::TaskNotify( IConsole* pConsole,
VARIANT* arg, VARIANT* param )
{
UNUSED_ALWAYS( arg );
UNUSED_ALWAYS( param );
HRESULT hr = E_FAIL;

//
// Determine if the given notification is for the
// start query button.
//
if ( arg->lVal == TASKPAD_LOCALQUERY )
{
CComPtr<IConsole> spConsole = pConsole;
int nResult;

//
// Display a message box to demonstrate the
// handling of the taskpad notification.
//
spConsole->MessageBox( L"Local query
started",
L"Health
Taskpad",
MB_ICONINFORMATION |
MB_OK,
&nResult );

hr = S_OK;
}

return( hr );
}

The function ensures that the task ID, as indicated by the arg parameter, is the ID we're looking for (this is most useful when multiple tasks with the MMC_ACTION_ID are defined). A message box is then displayed to the user to indicate the Snap-In has received the notification.

Adding Taskpad Resources

It is now necessary to add the necessary resources and functions to allow your user to select a menu item that toggles between a Taskpad and its default web page display.

First, you need to add a new menu item allowing the Taskpad toggle:

  1. Select the Resources tab from Visual Studio's class view.
  2. Expand the Resources and then Menu resource folders.
  3. Double-click on the IDR_ENROLL_MENU menu resource. The menu editor appears.
  4. Add a menu item to the View menu with an ID of ID_VIEW_TASKPAD and a caption of "View Web Resources".

Once the menu has been added, you must add a member to CHealthNode that indicates whether the node is in Taskpad mode or not:

    bool m_fTaskpad;

This member is utilized in GetResultViewType(...), outlined earlier, to determine whether an internal web page resource or MMC's internal Taskpad template should be used. Add a command handler to toggle between Taskpad and normal mode:

    STDMETHOD( OnTaskpad )(bool&
bHandled, CSnapInObjectRootBase* pObj)
{
UNUSED_ALWAYS( bHandled );
CComPtr<IConsole> spConsole;

//
// Switch the current view mode to a taskpad
view.
//
m_fTaskpad = !m_fTaskpad;

//
// Reselect the node to cause
GetResultViewType() to get
// called once again.
//
if ( pObj->m_nType == 1 )
spConsole =
((CBenefits*)pObj)->m_spConsole;
else
spConsole =
((CBenefitsComponent*)pObj)->m_spConsole;

spConsole->SelectScopeItem(
m_scopeDataItem.ID );

return( S_OK );
}

The m_fTaskpad member is toggled, the console is obtained, and IConsole::SelectScopeItem(...) is called to trigger a selection on the node. This will cause GetResultViewType(...) to be called once again. Because you have modified the state of m_fTaskpad, the appropriate web page or Taskpad template will be returned.

The remaining function is added to reflect the current mode of display by adding or removing a checkmark by the ID_VIEW_TASKPAD menu item:

    void UpdateMenuState(UINT id, LPTSTR
pBuf, UINT *flags)
{
UNUSED_ALWAYS( pBuf );

//
// Check the menuitem if we are currently in
"taskpad"
// mode.
//
if ( id == ID_VIEW_TASKPAD )
*flags |= m_fTaskpad ?
MFS_CHECKED : MFS_UNCHECKED;
}

Since the menu state is always determined whenever the menu is displayed, you simply check the current state of m_fTaskpad and set the appropriate menu style. Refer to the SDK for additional menu styles and states.


Step x: Enhancing the Result Pane

Customizing Columns

Sorting Results


Step x: Extending the Namespace

A Snap-In is required to support the IComponent and IComponentData interfaces. The ATL Snap-In Wizard creates CBenefitsComponent and CBenefits to handle these interfaces, which in turn utilize the IComponentImpl<> and IComponentDataImpl<> templates provided by ATLSnap.h.

class CBenefitsComponent : public CComObjectRootEx<CComSingleThreadModel>,
    public CSnapInObjectRoot<2, CBenefits>,
    public IExtendPropertySheetImpl<CBenefitsComponent>,
    public IExtendContextMenuImpl<CBenefitsComponent>,
    public IExtendControlbarImpl<CBenefitsComponent>,
    public IPersistStreamInit,
    public IComponentImpl<CBenefitsComponent>
{
...
}

class CBenefits : public CComObjectRootEx<CComSingleThreadModel>,
public CSnapInObjectRoot<1, CBenefits>,
    public IComponentDataImpl<CBenefits, CBenefitsComponent>,
    public IExtendPropertySheetImpl<CBenefits>,
    public IExtendContextMenuImpl<CBenefits>,
    public IPersistStreamInit,
    public CComCoClass<CBenefits, &CLSID_Benefits>
{
...
}

The IComponentImpl<> and IComponentDataImpl<> templates normalize Snap-In support by routing calls to your Snap-In to a third, important class. This class, CBenefitsData, contains default implementations of some of the most important functionality of your Snap-In. In most cases, it is this class in which most or your modifications are made.

Must be derived from CSnapInItem.

How SCOPEITEMDATA supplies info.

TBD. This is an architectual overview that will be finished last based on overall content covered.


Inserting a File Into Your Project

To insert an .H file or .CPP file, you should:

  1. On the Project menu, select Add To Project... and then the New... menu. The New dialog appears.
  2. Select the appropriate file type from the Files tab. When inserting an .H file, select C/C++ Header File. When inserting a .CPP file, select C++ Source File.
  3. Enter the appropriate file name into the File Name edit control.
  4. Ensure the location is correct. By default, DevStudio suggests the same location as in which your project is located. In most cases, this default is preferred.

Select the OK button. The new file is added to your project and is made available from within DevStudio's FileView.


ATL Taskpad Template

#ifndef _ATL_TASKPAD_H_
#define _ATL_TASKPAD_H_

#include "SnapHelp.h"

//
// Derive from this class if you'd like to use the
// IExtendTaskpadImpl implementation in your snap-in.
//
class CTaskpadItem
{
public:
//
// Should be overridden by client.
//
STDMETHOD( TaskNotify )( IConsole*
pConsole, VARIANT* arg, VARIANT* param )
{
UNUSED_ALWAYS( arg );
UNUSED_ALWAYS( param );
return( E_NOTIMPL );
}

//
// Should be overridden by client.
//
STDMETHOD( EnumTasks )( LPOLESTR
szTaskGroup, IEnumTASK** ppEnumTASK )
{
UNUSED_ALWAYS( szTaskGroup );
UNUSED_ALWAYS( ppEnumTASK );
return( E_NOTIMPL );
}

protected:
//
// Given a destination and source task list, this copies the
// strings using CoTaskMemAlloc, and also adds module file path
// information as appropriate.
//
int CoTasksDup( MMC_TASK*
pDestTasks, MMC_TASK* pSrcTasks, int nNumTasks )
{
USES_CONVERSION;
_ASSERTE( pDestTasks != NULL );
_ASSERTE( pSrcTasks != NULL );
_ASSERTE( nNumTasks > 0 );
int nCopied = 0;
TCHAR szImagesPath[ _MAX_PATH * 2 ];
TCHAR szBuf[ _MAX_PATH * 2 ];

try
{
//
// Get the path of our
module.
//
_tcscpy( szImagesPath,
_T( "res://" ) );
if ( GetModuleFileName(
_Module.GetModuleInstance(), szImagesPath + _tcslen( szImagesPath ), MAX_PATH ) == 0 )
throw;

//
// Append another
seperator.
//
_tcscat( szImagesPath,
_T( "/" ) );

//
// Initialize the
destination tasks.
//
memset( pDestTasks, 0,
sizeof( MMC_TASK ) * nNumTasks );

//
// Loop through and
copy each appropriate task.
//
for ( int i = 0; i <
nNumTasks; i++ )
{
//
// Copy the display object.
//
switch( pSrcTasks[ i ].sDisplayObject.eDisplayType )
{
case MMC_TASK_DISPLAY_TYPE_SYMBOL:
pDestTasks[ i ].sDisplayObject.uSymbol.szFontFamilyName = CoTaskDupString( pSrcTasks[ i
].sDisplayObject.uSymbol.szFontFamilyName );
pDestTasks[ i ].sDisplayObject.uSymbol.szURLtoEOT = CoTaskDupString( pSrcTasks[ i
].sDisplayObject.uSymbol.szURLtoEOT );
pDestTasks[ i ].sDisplayObject.uSymbol.szSymbolString = CoTaskDupString( pSrcTasks[ i
].sDisplayObject.uSymbol.szSymbolString );
break;

default:
_tcscpy( szBuf, szImagesPath );
_tcscat( szBuf, pSrcTasks[ i ].sDisplayObject.uBitmap.szMouseOverBitmap );
pDestTasks[ i ].sDisplayObject.uBitmap.szMouseOverBitmap = CoTaskDupString( szBuf );
_tcscpy( szBuf, szImagesPath );
_tcscat( szBuf, pSrcTasks[ i ].sDisplayObject.uBitmap.szMouseOffBitmap );
pDestTasks[ i ].sDisplayObject.uBitmap.szMouseOffBitmap = CoTaskDupString( szBuf );
break;
}

//
// Copy the display type.
//
pDestTasks[ i ].sDisplayObject.eDisplayType = MMC_TASK_DISPLAY_TYPE_BITMAP;

//
// Copy the help string.
//
pDestTasks[ i ].szHelpString = CoTaskDupString( pSrcTasks[ i ].szHelpString );

//
// Handle the button text.
//
pDestTasks[ i ].szText = CoTaskDupString( pSrcTasks[ i ].szText );

//
// Handle the action type.
//
pDestTasks[ i ].eActionType = pSrcTasks[ i ].eActionType;

//
// Based on the action type, handle the appropriate union member.
//
switch( pDestTasks[ i ].eActionType )
{
case MMC_ACTION_ID:
pDestTasks[ i ].nCommandID = pSrcTasks[ i ].nCommandID;
break;
case MMC_ACTION_LINK:
pDestTasks[ i ].szActionURL = CoTaskDupString( pSrcTasks[ i ].szActionURL );
break;
case MMC_ACTION_SCRIPT:
pDestTasks[ i ].szScript = CoTaskDupString( pSrcTasks[ i ].szScript );
break;
}

//
// Increment our successful copy.
//
nCopied++;
}
}
catch(...)
{
//
// Likely thrown by the
cotaskdup() allocations.
//
}

return( nCopied );
}
};

template <class T>
class ATL_NO_VTABLE IExtendTaskPadImpl : public
IExtendTaskPad
{
public:
STDMETHOD( TaskNotify )(
LPDATAOBJECT pdo, VARIANT* arg, VARIANT* param)
{
HRESULT hr = E_POINTER;
T* pT = static_cast<T*>(this);
CSnapInItem* pItem;
DATA_OBJECT_TYPES type;

//
// Retrieve the data class from the passed in
object.
//
hr = pT->m_pComponentData->GetDataClass(
pdo, &pItem, &type );
if (SUCCEEDED(hr))
{
CTaskpadItem*
pTaskpadItem = dynamic_cast< CTaskpadItem* >( pItem );
if ( pTaskpadItem )
{
//
// We're guaranteed that the passed in object will be one
// of ours since we should have derived from it.
//
hr = pTaskpadItem->TaskNotify( pT->m_spConsole, arg, param );
}
}

return( hr );
}

STDMETHOD( EnumTasks )( IDataObject
* pdo, LPOLESTR szTaskGroup, IEnumTASK** ppEnumTASK )
{
HRESULT hr = E_POINTER;
T* pT = static_cast<T*>(this);
CSnapInItem* pItem;
DATA_OBJECT_TYPES type;

//
// Retrieve the data class from the passed in
object.
//
hr = pT->m_pComponentData->GetDataClass(
pdo, &pItem, &type );
if (SUCCEEDED(hr))
{
CTaskpadItem*
pTaskpadItem = dynamic_cast< CTaskpadItem* >( pItem );
if ( pTaskpadItem )
{
//
// We're guaranteed that the passed in object will be one
// of ours since we should have derived from it.
//
hr = pTaskpadItem->EnumTasks( szTaskGroup, ppEnumTASK );
}
}

return( hr );
}

STDMETHOD( GetTitle )( LPOLESTR
pszGroup, LPOLESTR * pszTitle )
{
UNUSED_ALWAYS( pszGroup );
USES_CONVERSION;
HRESULT hr = E_FAIL;
T* pT = static_cast<T*>(this);

try
{
//
// Allocate memory for
the title.
//
*pszTitle = (LPOLESTR)
CoTaskMemAlloc( ( wcslen( pT->m_pszTitle ) + 1 ) * sizeof( OLECHAR ) );
if ( pszTitle == NULL )
throw;

//
// Copy the title.
//
wcscpy( *pszTitle,
pT->m_pszTitle );
hr = S_OK;
}
catch( ... )
{
}

return( hr );
}

STDMETHOD( GetBackground )(
LPOLESTR pszGroup, MMC_TASK_DISPLAY_OBJECT * pTDO )
{
UNUSED_ALWAYS( pszGroup );
USES_CONVERSION;
HRESULT hr = E_FAIL;
T* pT = static_cast<T*>(this);
TCHAR szModulePath[ _MAX_PATH ];
OLECHAR szBackgroundPath[ _MAX_PATH ];

try
{
//
// In the taskpad case,
the module path of MMC.EXE should be
// obtained. Use the
template contained therein.
//
if ( GetModuleFileName(
_Module.GetModuleInstance(), szModulePath, _MAX_PATH ) == 0 )
throw;

//
// Append the necessary
decorations for correct access.
//
wcscpy(
szBackgroundPath, L"res://" );
wcscat(
szBackgroundPath, T2W( szModulePath ) );
wcscat(
szBackgroundPath, L"/" );
wcscat(
szBackgroundPath, pT->m_pszBackgroundPath );

pTDO->eDisplayType =
MMC_TASK_DISPLAY_TYPE_BITMAP;
pTDO->uBitmap.szMouseOverBitmap = CoTaskDupString( szBackgroundPath );
if (
pTDO->uBitmap.szMouseOverBitmap == NULL )
throw;
pTDO->uBitmap.szMouseOffBitmap = NULL;

hr = S_OK;
}
catch( ... )
{
}

return( hr );
}

STDMETHOD(
GetDescriptiveText )( LPOLESTR pszGroup, LPOLESTR * pszDescriptiveText )
{
UNUSED_ALWAYS( pszGroup );
UNUSED_ALWAYS( pszDescriptiveText );
return( E_NOTIMPL );
}

STDMETHOD( GetListPadInfo )(
LPOLESTR pszGroup, MMC_LISTPAD_INFO * lpListPadInfo )
{
UNUSED_ALWAYS( pszGroup );
UNUSED_ALWAYS( lpListPadInfo );
return( E_NOTIMPL );
}
};

#endif


CBenefitsData Template

Located in Benefits.h:

template< class T >
class CBenefitsData : public CSnapInItemImpl< T >
{
public:
static const GUID* m_NODETYPE;
static const TCHAR* m_SZNODETYPE;
static const TCHAR* m_SZDISPLAY_NAME;
static const CLSID* m_SNAPIN_CLASSID;

CBenefitsData( CEmployee* pEmployee )
{
//
// Assign the given employee to our internal
containment.
// This employee will be assumed valid for the
lifetime of
// this object since persistence is maintained
by our parent
// node.
//
m_pEmployee = pEmployee;

//
// Always initialize our display name with the
static declared.
//
m_bstrDisplayName = m_SZDISPLAY_NAME;

//
// Image indexes may need to be modified
depending on the images specific to
// the snapin.
//
memset(&m_scopeDataItem, 0,
sizeof(SCOPEDATAITEM));
m_scopeDataItem.mask = SDI_STR | SDI_IMAGE |
SDI_OPENIMAGE | SDI_PARAM;
m_scopeDataItem.displayname = MMC_CALLBACK;
m_scopeDataItem.nImage = 0;
         // May need modification
m_scopeDataItem.nOpenImage = 0;
     // May need modification
m_scopeDataItem.lParam = (LPARAM) this;
memset(&m_resultDataItem, 0,
sizeof(RESULTDATAITEM));
m_resultDataItem.mask = RDI_STR | RDI_IMAGE |
RDI_PARAM;
m_resultDataItem.str = MMC_CALLBACK;
m_resultDataItem.nImage = 0;
        // May need modification
m_resultDataItem.lParam = (LPARAM) this;
}

~CBenefitsData()
{
}

STDMETHOD(GetScopePaneInfo)(SCOPEDATAITEM *pScopeDataItem)
{
if (pScopeDataItem->mask & SDI_STR)
pScopeDataItem->displayname
= m_bstrDisplayName;
if (pScopeDataItem->mask & SDI_IMAGE)
pScopeDataItem->nImage
= m_scopeDataItem.nImage;
if (pScopeDataItem->mask &
SDI_OPENIMAGE)
pScopeDataItem->nOpenImage
= m_scopeDataItem.nOpenImage;
if (pScopeDataItem->mask & SDI_PARAM)
pScopeDataItem->lParam
= m_scopeDataItem.lParam;
if (pScopeDataItem->mask & SDI_STATE )
pScopeDataItem->nState
= m_scopeDataItem.nState;

//
// SDI_CHILDREN should be overridden by its
derived classes.
//

return S_OK;
}

STDMETHOD(GetResultPaneInfo)(RESULTDATAITEM *pResultDataItem)
{
if (pResultDataItem->bScopeItem)
{
if
(pResultDataItem->mask & RDI_STR)
{
pResultDataItem->str
= GetResultPaneColInfo(pResultDataItem->nCol);
}
if
(pResultDataItem->mask & RDI_IMAGE)
{
pResultDataItem->nImage
= m_scopeDataItem.nImage;
}
if
(pResultDataItem->mask & RDI_PARAM)
{
pResultDataItem->lParam
= m_scopeDataItem.lParam;
}

return S_OK;
}

if (pResultDataItem->mask & RDI_STR)
{
pResultDataItem->str
= GetResultPaneColInfo(pResultDataItem->nCol);
}
if (pResultDataItem->mask & RDI_IMAGE)
{
pResultDataItem->nImage
= m_resultDataItem.nImage;
}
if (pResultDataItem->mask & RDI_PARAM)
{
pResultDataItem->lParam
= m_resultDataItem.lParam;
}
if (pResultDataItem->mask & RDI_INDEX)
{
pResultDataItem->nIndex
= m_resultDataItem.nIndex;
}

return S_OK;
}

//
// Overridden to provide result icons.
//
STDMETHOD( OnAddImages )( MMC_NOTIFY_TYPE event,
long arg,
long param,
IConsole* pConsole,
DATA_OBJECT_TYPES type
)
{
UNUSED_ALWAYS( event );
UNUSED_ALWAYS( param );
UNUSED_ALWAYS( pConsole );
UNUSED_ALWAYS( type );

// Add Images
IImageList* pImageList = (IImageList*) arg;
HRESULT hr = E_FAIL;

// Load bitmaps associated with the scope pane
// and add them to the image list
// Loads the default bitmaps generated by the
wizard
// Change as required
HBITMAP hBitmap16 = LoadBitmap(
_Module.GetResourceInstance(), MAKEINTRESOURCE( IDB_BENEFITS_16 ) );
if (hBitmap16 != NULL)
{
HBITMAP hBitmap32 =
LoadBitmap( _Module.GetResourceInstance(), MAKEINTRESOURCE( IDB_BENEFITS_32 ) );
if (hBitmap32 != NULL)
{
hr
= pImageList->ImageListSetStrip( (long*)hBitmap16,
(long*)
hBitmap32, 0, RGB( 0, 128, 128 ) );
if
( FAILED( hr ) )
ATLTRACE(
_T( "IImageList::ImageListSetStrip failed\n" ) );
}
}

return( hr );
}

virtual LPOLESTR GetResultPaneColInfo(int nCol)
{
if (nCol == 0)
{
T* pT =
static_cast<T*>(this);
return(
pT->m_bstrDisplayName );
}

// TODO : Return the text for other columns
return OLESTR("Generic Description");
}

//
// Helper function to extract the appropriate console from
// a base object type.
//
STDMETHOD( GetConsole )( CSnapInObjectRootBase* pObj, IConsole**
ppConsole )
{
HRESULT hr = E_FAIL;

if ( pObj->m_nType == 1 )
{
//
// This is the id of
the data object.
//
*ppConsole = (
(CBenefits*) pObj )->m_spConsole;
(*ppConsole)->AddRef();
hr = S_OK;
}
else if ( pObj->m_nType == 2 )
{
//
// This is the id of
the component object.
//
*ppConsole = (
(CBenefitsComponent*) pObj )->m_spConsole;
(*ppConsole)->AddRef();
hr = S_OK;
}

return( hr );
}

//
// Called to determine if the clipboard data can be obtained and
// if it has a node type that matches the given GUID.
//
STDMETHOD( IsClipboardDataType )( LPDATAOBJECT pDataObject, GUID inGuid
)
{
HRESULT hr = S_FALSE;

if ( pDataObject == NULL )
return( E_POINTER );

STGMEDIUM stgmedium = { TYMED_HGLOBAL, NULL };
FORMATETC formatetc = {
CSnapInItem::m_CCF_NODETYPE,
NULL,
DVASPECT_CONTENT,
-1,
TYMED_HGLOBAL
};

//
// Allocate memory to received the GUID.
//
stgmedium.hGlobal = GlobalAlloc( 0, sizeof(
GUID ) );
if ( stgmedium.hGlobal == NULL )
return( E_OUTOFMEMORY
);

//
// Retrieve the GUID of the paste object.
//
hr = pDataObject->GetDataHere(
&formatetc, &stgmedium );
if( FAILED( hr ) )
{
GlobalFree(stgmedium.hGlobal);
return( hr );
}

//
// Make a local copy of the GUID.
//
GUID guid;
memcpy( &guid, stgmedium.hGlobal, sizeof(
GUID ) );
GlobalFree( stgmedium.hGlobal );

//
// Check to see if the node is of the
appropriate type.
//
if ( IsEqualGUID( guid, inGuid ) )
hr = S_OK;
else
hr = S_FALSE;

return( hr );
}

//
// Command handler for "OnImport" functionality. For the
current
// sample, display a simple message box.
//
STDMETHOD( OnImport )(bool& bHandled, CSnapInObjectRootBase* pObj)
{
UNUSED_ALWAYS( bHandled );
USES_CONVERSION;
int nResult;
CComPtr<IConsole> spConsole;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( T2OLE( _T( "Data
successfully imported" ) ),
T2OLE( _T(
"Benefits" ) ),
MB_ICONINFORMATION |
MB_OK,
&nResult );

return( S_OK );
};

//
// Command handler for "OnExport" functionality. For the
current
// sample, display a simple message box.
//
STDMETHOD( OnExport )(bool& bHandled, CSnapInObjectRootBase* pObj)
{
UNUSED_ALWAYS( bHandled );
USES_CONVERSION;
int nResult;
CComPtr<IConsole> spConsole;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( T2OLE( _T( "Data
successfully exported" ) ),
T2OLE( _T(
"Benefits" ) ),
MB_ICONINFORMATION |
MB_OK,
&nResult );

return( S_OK );
};

protected:
//
// Container for the employee information.
//
CEmployee* m_pEmployee;
};


CChildrenBenefitsData Template

Located in Benefits.h:

template< class T >
class CChildrenBenefitsData : public CBenefitsData< T >
{
public:
//
// Call the benefits data with no employee. All of our
// containment nodes are not passed in an employee.
//
CChildrenBenefitsData< T >( CEmployee* pEmployee = NULL ) :
CBenefitsData< T >( pEmployee )
{
};

//
// Overridden to automatically clean-up any child nodes.
//
virtual ~CChildrenBenefitsData()
{
//
// Free any added nodes.
//
for ( int i = 0; i < m_Nodes.GetSize(); i++
)
{
CSnapInItem* pNode;

pNode = m_Nodes[ i ];
_ASSERTE( pNode != NULL
);
delete pNode;
}
}

//
// Overridden to automatically expand any child nodes.
//
STDMETHOD(
OnShow )( MMC_NOTIFY_TYPE event,
long arg,
long param,
IConsole* pConsole,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( event );
UNUSED_ALWAYS( param );
UNUSED_ALWAYS( type );
HRESULT hr = E_NOTIMPL;
ATLTRACE2(atlTraceSnapin, 0,
_T("CChildNodeImpl::OnExpand\n"));

//
// Only add the items if we're being selected.
//
if ( arg == TRUE )
{
CComQIPtr<IResultData,&IID_IResultData>
spResultData( pConsole );

//
// Loop through and add
each subnode.
//
for ( int i = 0; i <
m_Nodes.GetSize(); i++ )
{
CSnapInItem*
pNode;
RESULTDATAITEM*
pResultData;

pNode
= m_Nodes[ i ];
_ASSERTE(
pNode != NULL );

//
//
Get the scope pane info for the node and set
//
relative id.
//
pNode->GetResultData(
&pResultData );
_ASSERTE(
pResultData != NULL );

//
//
Add the item to the scope list using the newly
//
populated scope data item.
//
hr
= spResultData->InsertItem( pResultData );
_ASSERTE(
SUCCEEDED( hr ) );
}
}

return( hr );
};

//
// Overridden to automatically expand any child nodes.
//
STDMETHOD(
OnExpand )( MMC_NOTIFY_TYPE event,
long arg,
long param,
IConsole* pConsole,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( event );
UNUSED_ALWAYS( arg );
UNUSED_ALWAYS( type );
ATLTRACE2(atlTraceSnapin, 0,
_T("CChildNodeImpl::OnExpand\n"));

CComQIPtr<IConsoleNameSpace,
&IID_IConsoleNameSpace> pNameSpace( pConsole );
HRESULT hr = E_NOTIMPL;

//
// Loop through and add each subnode.
//
for ( int i = 0; i < m_Nodes.GetSize(); i++
)
{
CSnapInItem* pNode;
SCOPEDATAITEM*
pScopeData;

pNode = m_Nodes[ i ];
_ASSERTE( pNode != NULL
);

//
// Get the scope pane
info for the node and set
// relative id.
//
pNode->GetScopeData(
&pScopeData );
_ASSERTE( pScopeData !=
NULL );
pScopeData->relativeID
= param;

//
// Add the item to the
scope list using the newly
// populated scope data
item.
//
hr =
pNameSpace->InsertItem( pScopeData );
_ASSERTE( SUCCEEDED( hr
) );
}

return( hr );
};

//
// Used as containment for all child nodes.
//
CSimpleArray<CSnapInItem*> m_Nodes;
};


CKeyNode Class

Located in CKeyNode.h:

class CBuildingNode;
class CKeyNode : public CChildrenBenefitsData< CKeyNode >
{
public:
SNAPINMENUID( IDR_ACCESS_MENU )
BEGIN_SNAPINCOMMAND_MAP( CKeyNode, FALSE )
SNAPINCOMMAND_ENTRY(ID_NEW_BUILDINGACCESS,
OnGrantAccess)
END_SNAPINCOMMAND_MAP()
BEGIN_SNAPINTOOLBARID_MAP( CKeyNode )
SNAPINTOOLBARID_ENTRY( IDR_ACCESSTOOLBAR )
END_SNAPINTOOLBARID_MAP()

CKeyNode( CEmployee* pCurEmployee );
virtual ~CKeyNode();

//
// Overridden to call the base class implementation.
//
STDMETHOD(Notify)(
MMC_NOTIFY_TYPE event,
long arg,
long param,
IComponentData* pComponentData,
IComponent* pComponent,
DATA_OBJECT_TYPES type)
{
ATLTRACE2( atlTraceSnapin, 0, _T(
"CNotifyImpl::Notify\n" ) );

// Add code to handle the different
notifications.
// Handle MMCN_SHOW and MMCN_EXPAND to
enumerate children items.
// In response to MMCN_SHOW you have to
enumerate both the scope
// and result pane items.
// For MMCN_EXPAND you only need to enumerate
the scope items
// Use IConsoleNameSpace::InsertItem to insert
scope pane items
// Use IResultData::InsertItem to insert result
pane item.
HRESULT hr = E_NOTIMPL;

_ASSERTE( pComponentData != NULL || pComponent
!= NULL );

CComPtr<IConsole> spConsole;
CComQIPtr<IHeaderCtrl,
&IID_IHeaderCtrl> spHeader;
if (pComponentData != NULL)
spConsole =
((CBenefits*)pComponentData)->m_spConsole;
else
{
spConsole =
((CBenefitsComponent*)pComponent)->m_spConsole;
spHeader = spConsole;
}

switch (event)
{
case MMCN_SELECT:
//
// Call our select
handler.
//
hr = OnSelect(
spConsole );
break;

case MMCN_SHOW:
//
// Only setup colums if
we're displaying the result pane.
//
if ( arg == TRUE )
hr
= OnShowColumn( spHeader );
hr = OnShow( event,
arg, param, spConsole, type );
break;

case MMCN_EXPAND:
//
// We do not expand as
normal, since the building nodes are
// only displayed in
the result pane.
//
break;

case MMCN_ADD_IMAGES:
hr = OnAddImages(
event, arg, param, spConsole, type );
break;

case MMCN_QUERY_PASTE:
//
// Called to determine
if the current object can be pasted
// or not into this
context.
//
hr = OnQueryPaste(
(LPDATAOBJECT) arg );
break;

case MMCN_PASTE:
//
// Called by MMC when
the item should be pasted.
//
hr = OnPaste(
spConsole, (LPDATAOBJECT) arg, (LPDATAOBJECT*) param );
break;
}

return hr;
}

//
// Ensures that the appropriate verbs are displayed.
//
STDMETHOD( OnSelect )( IConsole* pConsole )
{
HRESULT hr;
CComPtr<IConsoleVerb> spConsoleVerb;

//
// Enable the delete verb.
//
hr = pConsole->QueryConsoleVerb(
&spConsoleVerb );
_ASSERTE( SUCCEEDED( hr ) );

//
// Enable the copy and paste verbs.
//
hr = spConsoleVerb->SetVerbState(
MMC_VERB_PASTE, ENABLED, TRUE );
_ASSERTE( SUCCEEDED( hr ) );

return( hr );
}

//
// Called by the console to determine if we can paste the
// specified node.
//
STDMETHOD( OnQueryPaste )( LPDATAOBJECT pItem );

//
// Called by MMC when the item should be pasted.
//
STDMETHOD( OnPaste )( IConsole* pConsole, LPDATAOBJECT pItem,
LPDATAOBJECT* pItemCut );

//
// Overridden to provide strings for various columns.
//
LPOLESTR GetResultPaneColInfo(int nCol);

//
// Overridden to add new columns to the results
// display.
//
STDMETHOD( OnShowColumn )( IHeaderCtrl* pHeader );

//
// Command handler for "Grant Acess" functionality.
//
STDMETHOD( OnGrantAccess )( bool& bHandled, CSnapInObjectRootBase*
pObj );

//
// Called by one of our children nodes to inform us that
// they should be deleted. This occurs when the user selects
// a delete action on the building. This function should not
// only delete the building, but also handle the refresh of
// the result display.
//
STDMETHOD( OnDeleteBuilding )( IConsole* pConsole, CBuildingNode*
pChildNode );
};

Located in CKeyNode.cpp:

static const GUID CKeyNodeGUID_NODETYPE =
{ 0xec362ef3, 0xd94d, 0x11d1, { 0x84, 0x74, 0x0, 0x10, 0x4b, 0x21, 0x1b, 0xe5 } };
const GUID* CKeyNode::m_NODETYPE = &CKeyNodeGUID_NODETYPE;
const TCHAR* CKeyNode::m_SZNODETYPE = _T("EC362EF3D94D-11D1-8474-00104B211BE5");
const TCHAR* CKeyNode::m_SZDISPLAY_NAME = _T("Card Key Permissions");
const CLSID* CKeyNode::m_SNAPIN_CLASSID = &CLSID_Benefits;

//
// Used for the key node example.
//
extern BUILDINGDATA g_Buildings[ 3 ];

//
// The following constructor initialiazes its base-class members with
// hard-coded values for display purposes. Since these are static nodes,
// hard-coded values can be used for the following values.
//
CKeyNode::CKeyNode( CEmployee* pCurEmployee ) : CChildrenBenefitsData<CKeyNode>(
pCurEmployee )
{
USES_CONVERSION;

m_scopeDataItem.nOpenImage = m_scopeDataItem.nImage = 2;
m_scopeDataItem.cChildren = 0;    // Not necessary
unless modified.

//
// Populate building nodes based on this employees permissions.
//
for ( int i = 0; i < sizeof( g_Buildings ) / sizeof( BUILDINGDATA );
i++ )
{
//
// Only add an item if the given employee has
access to the
// building.
//
if ( g_Buildings[ i ].dwId &
pCurEmployee->m_Access.dwAccess )
{
CSnapInItem* pItem;

pItem = new
CBuildingNode( this, W2BSTR( g_Buildings[ i ].pstrName ), W2BSTR( g_Buildings[ i
].pstrLocation ) );
m_Nodes.Add( pItem );
}
}
}

CKeyNode::~CKeyNode()
{

}

//
// Overridden to provide strings for various columns.
//
LPOLESTR CKeyNode::GetResultPaneColInfo(int nCol)
{
CComBSTR szText;

// The following switch statement dispatches to the
// appropriate column index and loads the necessary
// string.
switch ( nCol )
{
case 0:
szText = m_bstrDisplayName;
break;
case 1:
szText.LoadString(
_Module.GetResourceInstance(), IDS_KEY_DESC );
break;
default:
ATLTRACE( "An invalid column index was
passed to GetResultPaneColInfo()\n" );
}

return( szText.Copy() );
}

//
// Overridden to add new columns to the results
// display.
//
STDMETHODIMP CKeyNode::OnShowColumn( IHeaderCtrl* pHeader )
{
USES_CONVERSION;
HRESULT hr = E_FAIL;
CComPtr<IHeaderCtrl> spHeader( pHeader );

// Add two columns: one with the name of the object and one with
// the description of the node. Use the value of 100 pixels as the
size.
hr = spHeader->InsertColumn( 0, T2OLE( _T( "Building" ) ),
LVCFMT_LEFT, 200 );
_ASSERTE( SUCCEEDED( hr ) );

// Add the second column. Use the value of 200 pixels as the size.
hr = spHeader->InsertColumn( 1, T2OLE( _T( "Location" ) ),
LVCFMT_LEFT, 350 );
_ASSERTE( SUCCEEDED( hr ) );

return( hr );
}

//
// Command handler for "Grant Access" functionality.
//
STDMETHODIMP CKeyNode::OnGrantAccess( bool& bHandled, CSnapInObjectRootBase* pObj )
{
UNUSED_ALWAYS( bHandled );
UNUSED_ALWAYS( pObj );

#ifdef _BENEFITS_DIALOGS
CBuildingAccessDialog dlg;

dlg.SetEmployee( m_pEmployee );
dlg.DoModal();
#else
CComPtr<IConsole> spConsole;
int nResult;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( L"Access granted",
L"Benefits",
MB_ICONINFORMATION | MB_OK,
&nResult );
#endif

return( S_OK );
}

//
// Called by the console to determine if we can paste the
// specified node.
//
STDMETHODIMP CKeyNode::OnQueryPaste( LPDATAOBJECT pDataObject )
{
HRESULT hr;

//
// Determine if the type of object being pasted is the right
// type.
//
hr = IsClipboardDataType( pDataObject, CBuildingNodeGUID_NODETYPE );
if ( SUCCEEDED( hr ) )
{
CBuildingNode* pItem;
DATA_OBJECT_TYPES Type;

//
// Loop through all of currently contained
nodes and
// determine if we already contain the
specified building
// by comparing building names.
//
hr = CSnapInItem::GetDataClass( pDataObject,
(CSnapInItem**) &pItem, &Type );
if ( SUCCEEDED( hr ) )
{
for ( int i = 0; i <
m_Nodes.GetSize(); i++ )
{
CBuildingNode*
pTemp;
CComBSTR
bstrTemp;

//
//
Retrieve the node from our internal list.
//
pTemp
= dynamic_cast<CBuildingNode*>( m_Nodes[ i ] );
_ASSERTE(
pTemp != NULL );

//
//
If the names are equal, indicate failure
//
and break out.
//
if
( wcscmp( pItem->m_bstrDisplayName, pTemp->m_bstrDisplayName ) == 0 )
{
hr
= S_FALSE;
break;
}
}
}
}

return( hr );
}

//
// Called by MMC when the item should be pasted.
//
STDMETHODIMP CKeyNode::OnPaste( IConsole* pConsole, LPDATAOBJECT pDataObject,
LPDATAOBJECT* ppDataObject )
{
HRESULT hr;

//
// Ensure the data is of the correct type.
//
hr = IsClipboardDataType( pDataObject, CBuildingNodeGUID_NODETYPE );
if ( SUCCEEDED( hr ) )
{
try
{
CBuildingNode* pItem;
DATA_OBJECT_TYPES Type;

//
// Retrieve the passed
in item.
//
hr =
CSnapInItem::GetDataClass( pDataObject, (CSnapInItem**) &pItem, &Type );
if ( FAILED( hr ) )
throw;

//
// Allocate a new
building node. The constructor
// copies the values
from the input node.
//
CSnapInItem* pNewNode =
new CBuildingNode( *pItem );
if ( pNewNode == NULL )
throw;

//
// Add the node to the
end of our internal array.
//
m_Nodes.Add( pNewNode
);

//
// Reselect ourselves
to cause a refresh.
//
pConsole->SelectScopeItem(
m_scopeDataItem.ID );

//
// Put the given data
object into the returned dataobject
// so that MMC may
complete its cut tasks.
//
*ppDataObject =
pDataObject;

hr = S_OK;
}
catch( ... )
{
//
// Assume all failures
are total.
//
hr = E_FAIL;
}
}

return( hr );
}

//
// Called by one of our children nodes to inform us that
// they should be deleted. This occurs when the user selects
// a delete action on the building. This function should not
// only delete the building, but also handle the refresh of
// the result display.
//
STDMETHODIMP CKeyNode::OnDeleteBuilding( IConsole* pConsole, CBuildingNode* pChildNode )
{
_ASSERTE( pConsole != NULL );
_ASSERTE( pChildNode != NULL );
HRESULT hr = E_FAIL;

//
// First, loop through all of our contained members and
// remove it from the contained list.
//
for ( int i = 0; i < m_Nodes.GetSize(); i++ )
{
if ( m_Nodes[ i ] == pChildNode )
{
//
// We have found a
match. Remove it from the
// contained list.
//
m_Nodes.RemoveAt( i );

//
// Reselect ourselves
to cause a refresh.
//
pConsole->SelectScopeItem(
m_scopeDataItem.ID );

//
// Since there should
only be one match, break out
// of the find process.
Indicate success.
//
hr = S_OK;
break;
}
}

return( hr );
}


CHealthNode Class

Located in BenNodes.h:

class CHealthNode : public CBenefitsData< CHealthNode
>,
public CTaskpadItem
{
public:
SNAPINMENUID( IDR_ENROLL_MENU )
BEGIN_SNAPINCOMMAND_MAP( CHealthNode, FALSE )
SNAPINCOMMAND_ENTRY(ID_TASK_ENROLL, OnEnroll)
SNAPINCOMMAND_ENTRY(ID_TOP_IMPORT, OnImport)
SNAPINCOMMAND_ENTRY(ID_TOP_EXPORT, OnExport)
SNAPINCOMMAND_ENTRY(ID_VIEW_TASKPAD, OnTaskpad)
END_SNAPINCOMMAND_MAP()
BEGIN_SNAPINTOOLBARID_MAP( CHealthNode )
SNAPINTOOLBARID_ENTRY( IDR_ENROLLTOOLBAR )
END_SNAPINTOOLBARID_MAP()

CHealthNode( CEmployee* pCurEmployee );
virtual ~CHealthNode();

//
// Overridden to display web page as results.
//
STDMETHOD( GetResultViewType )( LPOLESTR *ppViewType, long *pViewOptions );

//
// Overridden to provide strings for various columns.
//
LPOLESTR GetResultPaneColInfo(int nCol);

//
// Command handler for "Enroll" functionality.
//
STDMETHOD( OnEnroll )(bool& bHandled, CSnapInObjectRootBase* pObj);

//
// Restores any state, especially in the case of using a
// taskpad, when the back and forward buttons are used by
// the user for navigation.
//
STDMETHOD( OnRestoreView )( MMC_RESTORE_VIEW* pRestoreView, BOOL*
pfHandled );

//
// Command handler for "OnTaskpad" functionality.
//
STDMETHOD( OnTaskpad )(bool& bHandled, CSnapInObjectRootBase* pObj)
{
UNUSED_ALWAYS( bHandled );
CComPtr<IConsole> spConsole;

//
// Switch the current view mode to a taskpad
view.
//
m_fTaskpad = !m_fTaskpad;

//
// Reselect the node to cause
GetResultViewType() to get
// called once again.
//
if ( pObj->m_nType == 1 )
spConsole =
((CBenefits*)pObj)->m_spConsole;
else
spConsole =
((CBenefitsComponent*)pObj)->m_spConsole;

spConsole->SelectScopeItem(
m_scopeDataItem.ID );

return( S_OK );
}

//
// Overridden to set the checkmark state of the taskpad menuitem.
//
void UpdateMenuState(UINT id, LPTSTR pBuf, UINT *flags)
{
UNUSED_ALWAYS( pBuf );

//
// Check the menuitem if we are currently in
"taskpad"
// mode.
//
if ( id == ID_VIEW_TASKPAD )
*flags |= m_fTaskpad ?
MFS_CHECKED : MFS_UNCHECKED;
}

//
// Overridden to enable the delete verb.
//
STDMETHOD(Notify)( MMC_NOTIFY_TYPE event,
long arg,
long param,
IComponentData* pComponentData,
IComponent* pComponent,
DATA_OBJECT_TYPES type)
{
HRESULT hr = E_NOTIMPL;

switch (event)
{
case MMCN_SELECT:
hr = S_OK;
break;

case MMCN_RESTORE_VIEW:
//
// Called when the
history list is used to navigate by
// the user. Since we
have a taskpad display, it's up
// to us to restore
that view, as appropriate.
//
hr = OnRestoreView(
(MMC_RESTORE_VIEW*) arg, (BOOL*) param );
break;

case MMCN_SHOW:
//
// Always respond to
the show call so that the
// console knows this
has been processed and can
// properly seat
toolbars, etc.
//
hr = S_OK;
break;

default:
//
// Always default to
calling the base class implementation.
//
hr = CBenefitsData<
CHealthNode >::Notify( event,
arg,
param,
pComponentData,
pComponent,
type
);
}

return hr;
}

//
// Called when one of the tasks is clicked.
//
STDMETHOD( TaskNotify )( IConsole* pConsole, VARIANT* arg, VARIANT*
param );

//
// Returns an enumerator for all of these tasks.
//
STDMETHOD( EnumTasks )( LPOLESTR szTaskGroup, IEnumTASK** ppEnumTASK );

protected:
//
// Indicates whether we should be displayed in a taskpad view.
//
bool m_fTaskpad;
};

Located in BenNodes.cpp:

static const GUID CHealthNodeGUID_NODETYPE =
{ 0xec362ef1, 0xd94d, 0x11d1, { 0x84, 0x74, 0x0, 0x10, 0x4b, 0x21, 0x1b, 0xe5 } };
const GUID* CHealthNode::m_NODETYPE = &CHealthNodeGUID_NODETYPE;
const TCHAR* CHealthNode::m_SZNODETYPE =
_T("EC362EF1D94D-11D1-8474-00104B211BE5");
const TCHAR* CHealthNode::m_SZDISPLAY_NAME = _T("Health & Dental Plan");
const CLSID* CHealthNode::m_SNAPIN_CLASSID = &CLSID_Benefits;

//
// Hard coded tasks to be associated with the health node.
//
MMC_TASK g_HealthTasks[ 3 ] =
{
{ MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\WebPage.gif",
L"img\\WebPage.gif", L"Microsoft", L"General Microsoft
resources", MMC_ACTION_LINK, (long) L"http://www.microsoft.com" },
{ MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\WebPage.gif",
L"img\\WebPage.gif", L"Microsoft Management Site", L"More MMC
oriented resources", MMC_ACTION_LINK, (long)
L"http://www.microsoft.com/management" },
{ MMC_TASK_DISPLAY_TYPE_VANILLA_GIF, L"img\\Query.gif",
L"img\\Query.gif", L"Local Query", L"Start query on local
database", MMC_ACTION_ID, TASKPAD_LOCALQUERY },
};

//
// The following constructor initialiazes its base-class members with
// hard-coded values for display purposes. Since these are static nodes,
// hard-coded values can be used for the following values.
//
CHealthNode::CHealthNode( CEmployee* pCurEmployee ) : CBenefitsData<CHealthNode> (
pCurEmployee )
{
m_scopeDataItem.nOpenImage = m_scopeDataItem.nImage = 1;
m_scopeDataItem.cChildren = 0;    // Not necessary
unless modified.

m_fTaskpad = FALSE;
}

CHealthNode::~CHealthNode()
{

}

//
// Specifies that the results should display a web page as its results. In
// addition, the view options should be set so that standard lists, which
// won't be applicable to this node, should not be available to the user.
//
STDMETHODIMP CHealthNode::GetResultViewType( LPOLESTR* ppViewType, long* pViewOptions )
{
USES_CONVERSION;
TCHAR szPath[ _MAX_PATH ];
TCHAR szModulePath[ _MAX_PATH ];

//
// Set the view options to no preferences.
//
*pViewOptions = MMC_VIEW_OPTIONS_NONE;

if ( m_fTaskpad )
{
//
// In the taskpad case, the module path of
MMC.EXE should be
// obtained. Use the template contained
therein.
//
GetModuleFileName( NULL, szModulePath,
_MAX_PATH );

//
// Append the necessary decorations for correct
access.
//
_tcscpy( szPath, _T( "res://" ) );
_tcscat( szPath, szModulePath );
_tcscat( szPath, _T( "/default.htm" )
);
}
else
{
//
// Use the HTML page that is embedded as a
resource of
// this module for display purposes.
//
GetModuleFileName( _Module.GetModuleInstance(),
szModulePath, _MAX_PATH );

//
// Append the necessary decorations for correct
access.
//
_tcscpy( szPath, _T( "res://" ) );
_tcscat( szPath, szModulePath );
_tcscat( szPath, _T( "/health.htm" )
);
}

//
// CoTaskMemAlloc(...) must be used since the MMC client frees the
space using
// CoTaskMemFree(...). Include enough space for NULL.
//
*ppViewType = (LPOLESTR) CoTaskMemAlloc( ( _tcslen( szPath ) + 1 ) *
sizeof( OLECHAR ) );
_ASSERTE( *ppViewType != NULL );
ocscpy( *ppViewType, T2OLE( szPath ) );

return( S_OK );
}

//
// Overridden to provide strings for various columns.
//
LPOLESTR CHealthNode::GetResultPaneColInfo(int nCol)
{
USES_CONVERSION;
CComBSTR szText;

// The following switch statement dispatches to the
// appropriate column index and loads the necessary
// string.
switch ( nCol )
{
case 0:
szText = m_bstrDisplayName;
break;
case 1:
szText.LoadString(
_Module.GetResourceInstance(), IDS_HEALTH_DESC );
break;
default:
ATLTRACE( "An invalid column index was
passed to GetResultPaneColInfo()\n" );
}

return( szText.Copy() );
}

//
// Command handler for "Enroll" functionality.
//
STDMETHODIMP CHealthNode::OnEnroll( bool& bHandled, CSnapInObjectRootBase* pObj )
{
UNUSED_ALWAYS( bHandled );
UNUSED_ALWAYS( pObj );

#ifdef _BENEFITS_DIALOGS
CHealthEnrollDialog dlg;

dlg.SetEmployee( m_pEmployee );
dlg.DoModal();
#else
CComPtr<IConsole> spConsole;
int nResult;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( L"Enrolled",
L"Benefits",
MB_ICONINFORMATION | MB_OK,
&nResult );
#endif

return( S_OK );
}

//
// Restores any state, especially in the case of using a
// taskpad, when the back and forward buttons are used by
// the user for navigation.
//
STDMETHODIMP CHealthNode::OnRestoreView( MMC_RESTORE_VIEW* pRestoreView, BOOL* pfHandled )
{
_ASSERTE( pRestoreView->dwSize == sizeof( MMC_RESTORE_VIEW ) );
*pfHandled = TRUE;
return( S_OK );
}

//
// Called when one of the tasks is clicked.
//
STDMETHODIMP CHealthNode::TaskNotify( IConsole* pConsole, VARIANT* arg, VARIANT* param )
{
UNUSED_ALWAYS( arg );
UNUSED_ALWAYS( param );
HRESULT hr = E_FAIL;

//
// Determine if the given notification is for the
// start query button.
//
if ( arg->lVal == TASKPAD_LOCALQUERY )
{
CComPtr<IConsole> spConsole = pConsole;
int nResult;

//
// Display a message box to demonstrate the
// handling of the taskpad notification.
//
spConsole->MessageBox( L"Local query
started",
L"Health
Taskpad",
MB_ICONINFORMATION |
MB_OK,
&nResult );

hr = S_OK;
}

return( hr );
}

//
// Returns an enumerator for all of these tasks.
//
STDMETHODIMP CHealthNode::EnumTasks( LPOLESTR szTaskGroup, IEnumTASK** ppEnumTASK )
{
UNUSED_ALWAYS( szTaskGroup );
MMC_TASK CoTasks[ sizeof( g_HealthTasks ) / sizeof( MMC_TASK ) ];
typedef CComObject< CComEnum< IEnumTASK, &IID_IEnumTASK,
MMC_TASK, _Copy<MMC_TASK> > > enumvar;
enumvar* p = new enumvar;

//
// Copy the local tasks to our temporary task structures. This
// performs the CoTaskMemAlloc for the strings, etc. It also
// maps image type resources to the local module name.
//
if ( CoTasksDup( CoTasks, g_HealthTasks, sizeof( g_HealthTasks ) /
sizeof( MMC_TASK ) ) )
{
p->Init( &CoTasks[ 0 ], &CoTasks[
sizeof( g_HealthTasks ) / sizeof( MMC_TASK ) ], NULL, AtlFlagCopy);
return( p->QueryInterface( IID_IEnumTASK,
(void**) ppEnumTASK ) );
}

return( E_FAIL );
}


CRetirementNode Class

Located in BenNodes.h:

class CRetirementNode : public CBenefitsData<
CRetirementNode >
{
public:
SNAPINMENUID( IDR_UPDATE_MENU )
BEGIN_SNAPINCOMMAND_MAP( CRetirementNode, FALSE )
SNAPINCOMMAND_ENTRY(ID_TASK_ENROLL, OnEnroll)
SNAPINCOMMAND_ENTRY(ID_TOP_UPDATE, OnUpdate)
END_SNAPINCOMMAND_MAP()
BEGIN_SNAPINTOOLBARID_MAP( CRetirementNode )
END_SNAPINTOOLBARID_MAP()

CRetirementNode( CEmployee* pCurEmployee );
virtual ~CRetirementNode();

//
// Overridden to display web page as results.
//
STDMETHOD( GetResultViewType )( LPOLESTR *ppViewType, long
*pViewOptions );

//
// Overridden to provide strings for various columns.
//
LPOLESTR GetResultPaneColInfo(int nCol);

//
// Command handler for "Enroll" functionality.
//
STDMETHOD( OnEnroll )(bool& bHandled, CSnapInObjectRootBase* pObj);

//
// Command handler for "Update" functionality.
//
STDMETHOD( OnUpdate )(bool& bHandled, CSnapInObjectRootBase* pObj);

//
// Overridden to cache the OCX pointer.
//
STDMETHOD(Notify)(
MMC_NOTIFY_TYPE event,
long arg,
long param,
IComponentData* pComponentData,
IComponent* pComponent,
DATA_OBJECT_TYPES type)
{
HRESULT hr = E_NOTIMPL;

switch (event)
{
case MMCN_SHOW:
//
// Always respond to
the show call so that the
// console knows this
has been processed and can
// properly seat
toolbars, etc.
//
hr = S_OK;
break;

case MMCN_INITOCX:
//
// The IUnknown of the
OCX is passed in. Do
// any other
initialization of the OCX here.
//
m_spControl =
(IUnknown*) param;
hr = S_OK;
break;

default:
//
// Always default to
calling the base class implementation.
//
hr =
CBenefitsData<CRetirementNode>::Notify( event,
arg,
param,
pComponentData,
pComponent,
type
);
}

return hr;
}

protected:
//
// Definition generated by compiler. IDispatch implementation.
//
_SampleControlPtr m_spControl;
};

Located in BenNodes.cpp:

static const GUID CRetirementNodeGUID_NODETYPE =
{ 0xec362ef2, 0xd94d, 0x11d1, { 0x84, 0x74, 0x0, 0x10, 0x4b, 0x21, 0x1b, 0xe5 } };
const GUID* CRetirementNode::m_NODETYPE = &CRetirementNodeGUID_NODETYPE;
const TCHAR* CRetirementNode::m_SZNODETYPE =
_T("EC362EF2D94D-11D1-8474-00104B211BE5");
const TCHAR* CRetirementNode::m_SZDISPLAY_NAME = _T("401K Plan");
const CLSID* CRetirementNode::m_SNAPIN_CLASSID = &CLSID_Benefits;

//
// The following constructor initialiazes its base-class members with
// hard-coded values for display purposes. Since these are static nodes,
// hard-coded values can be used for the following values.
//
CRetirementNode::CRetirementNode( CEmployee* pCurEmployee ) : CBenefitsData<
CRetirementNode > ( pCurEmployee )
{
m_scopeDataItem.nOpenImage = m_scopeDataItem.nImage = 0;
m_scopeDataItem.cChildren = 0;    // Not necessary
unless modified.
}

CRetirementNode::~CRetirementNode()
{

}

//
// Specifies that the results should display a web page as its results. In
// addition, the view options should be set so that standard lists, which
// won't be applicable to this node, should not be available to the user.
//
STDMETHODIMP CRetirementNode::GetResultViewType( LPOLESTR* ppViewType, long* pViewOptions
)
{
USES_CONVERSION;

//
// For this example to work, the sample control must be installed.
//
TCHAR* pszControl = _T(
"{FE148827-3093-11D2-8494-00104B211BE5}" );

// CoTaskMemAlloc(...) must be used since the MMC client frees the
space using
// CoTaskMemFree(...). Include enough space for NULL.
//
*ppViewType = (LPOLESTR) CoTaskMemAlloc( ( _tcslen( pszControl ) + 1 )
* sizeof( OLECHAR ) );
_ASSERTE( *ppViewType != NULL );
ocscpy( *ppViewType, T2OLE( pszControl ) );

//
// Set the view options so that no lists are displayed.
//
*pViewOptions = MMC_VIEW_OPTIONS_NOLISTVIEWS;

return( S_OK );
}

//
// Overridden to provide strings for various columns.
//
LPOLESTR CRetirementNode::GetResultPaneColInfo(int nCol)
{
CComBSTR szText;

// The following switch statement dispatches to the
// appropriate column index and loads the necessary
// string.
switch ( nCol )
{
case 0:
szText = m_bstrDisplayName;
break;
case 1:
szText.LoadString(
_Module.GetResourceInstance(), IDS_RETIREMENT_DESC );
break;
default:
ATLTRACE( "An invalid column index was
passed to GetResultPaneColInfo()\n" );
}

return( szText.Copy() );
}

//
// Command handler for "Enroll" functionality.
//
STDMETHODIMP CRetirementNode::OnEnroll( bool& bHandled, CSnapInObjectRootBase* pObj )
{
UNUSED_ALWAYS( bHandled );
UNUSED_ALWAYS( pObj );

#ifdef _BENEFITS_DIALOGS
CRetirementEnrollDialog dlg;

dlg.SetEmployee( m_pEmployee );
dlg.DoModal();
#else
CComPtr<IConsole> spConsole;
int nResult;

//
// Retrieve the appropriate console.
//
GetConsole( pObj, &spConsole );
spConsole->MessageBox( L"Enrolled",
L"Benefits",
MB_ICONINFORMATION | MB_OK,
&nResult );
#endif

return( S_OK );
}

//
// Command handler for "Update" functionality. Demonstrates calling a
// displayed OCX's method.
//
STDMETHODIMP CRetirementNode::OnUpdate( bool& bHandled, CSnapInObjectRootBase* pObj )
{
UNUSED_ALWAYS( bHandled );
UNUSED_ALWAYS( pObj );
HRESULT hr = E_FAIL;

if ( m_spControl )
{
//
// This should trigger the OCX to refresh its
historical information.
//
hr = m_spControl->Refresh();
}

return( hr );
}


CRootNode Class

Located in RootNode.h:

class CRootNode : public CChildrenBenefitsData<
CRootNode >
{
public:
BEGIN_SNAPINTOOLBARID_MAP( CRootNode )
END_SNAPINTOOLBARID_MAP()

CRootNode();

//
// Creates the benefits subnodes for the scope pane.
//
BOOL InitializeSubNodes();

//
// Overridden to provide employee name for root node.
//
STDMETHOD( FillData )( CLIPFORMAT cf, LPSTREAM pStream );

//
// Overridden to add new columns to the results
// display.
//
STDMETHOD( OnShowColumn )( IHeaderCtrl* pHeader );

//
// Handles creation of our property pages.
//
STDMETHOD( CreatePropertyPages )(LPPROPERTYSHEETCALLBACK lpProvider,
long handle,
IUnknown* pUnk,
DATA_OBJECT_TYPES type);

//
// Determines if pages should be displayed. This has been
// modified to ensure that we're called by the snap-in manager
// when it's first inserted.
//
STDMETHOD( QueryPagesFor )(DATA_OBJECT_TYPES type)
{
if ( type == CCT_SCOPE || type == CCT_RESULT ||
type == CCT_SNAPIN_MANAGER )
return S_OK;
return S_FALSE;
}

//
// Ensures that the appropriate verbs are displayed.
//
STDMETHOD( OnSelect )( IConsole* pConsole );

//
// Overridden to call the base class implementation.
//
STDMETHOD(Notify)(
MMC_NOTIFY_TYPE event,
long arg,
long param,
IComponentData* pComponentData,
IComponent* pComponent,
DATA_OBJECT_TYPES type)
{
// Add code to handle the different
notifications.
// Handle MMCN_SHOW and MMCN_EXPAND to
enumerate children items.
// For MMCN_EXPAND you only need to enumerate
the scope items
// Use IConsoleNameSpace::InsertItem to insert
scope pane items
// Use IResultData::InsertItem to insert result
pane item.
HRESULT hr = E_NOTIMPL;

_ASSERTE(pComponentData != NULL || pComponent
!= NULL);

CComPtr<IConsole> spConsole;
CComQIPtr<IHeaderCtrl,
&IID_IHeaderCtrl> spHeader;
if (pComponentData != NULL)
spConsole =
((CBenefits*)pComponentData)->m_spConsole;
else
{
spConsole =
((CBenefitsComponent*)pComponent)->m_spConsole;
spHeader = spConsole;
}

switch (event)
{
case MMCN_SELECT:
hr = OnSelect(
spConsole );
break;

case MMCN_SHOW:
// Only setup colums if
we're displaying the result pane.
if ( arg == TRUE )
hr = OnShowColumn( spHeader );
break;

case MMCN_EXPAND:
//
// Since the insert
item is never called, we don't have a valid
// HSCOPEITEM as you
would in sub-nodes. The Expand message is
// intercepted and
stored for use later.
//
m_scopeDataItem.ID =
param;
hr = OnExpand( event,
arg, param, spConsole, type );
break;

case MMCN_ADD_IMAGES:
hr = OnAddImages(
event, arg, param, spConsole, type );
break;
}

return hr;
}

//
// Uses the dirty flag to determine whether or not this node
// needs to be persisted.
//
STDMETHOD(IsDirty)()
{
return ( m_fDirty ? S_OK : S_FALSE );
}

//
// Loads the employee information from the stream.
//
STDMETHOD(Load)(LPSTREAM pStm)
{
DWORD dwRead;

pStm->Read( &m_Employee, sizeof(
m_Employee ), &dwRead );
_ASSERTE( dwRead == sizeof( m_Employee ) );

return( S_OK );
}

//
// Stores the employee information to the stream and clears
// our dirty flag.
//
STDMETHOD(Save)(LPSTREAM pStm, BOOL fClearDirty)
{
DWORD dwWritten;

pStm->Write( &m_Employee, sizeof(
m_Employee ), &dwWritten );
_ASSERTE( dwWritten == sizeof( m_Employee ) );

//
// Clear the dirty flag.
//
if ( fClearDirty )
m_fDirty = FALSE;

return( S_OK );
}

//
// Returns the size of the employee structure.
//
STDMETHOD(GetSizeMax)(ULARGE_INTEGER FAR* pcbSize )
{
pcbSize->LowPart = sizeof( m_Employee );
return( S_OK );
}

//
// Received when a property has changed. This function
// modifies the employee's display text. At a later date,
// it may post this message to its sub-nodes.
//
STDMETHOD( OnPropertyChange )( IConsole* pConsole );

protected:
//
// Simply function to create the display name from the
// employee data.
//
int CreateDisplayName( TCHAR* szBuf );

//
// Called to set the dirty flag for persistence.
//
void SetModified( bool fDirty = true )
{
m_fDirty = fDirty;
}

//
// Contains the the employees entire datastore for this
// sample.
//
CEmployee m_Employee;

//
// Flag set to indicate whether the datastore is "dirty".
//
bool m_fDirty;
};

Located in CRootNode.cpp:

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
static const GUID CBenefitsGUID_NODETYPE =
{ 0xe0573e71, 0xd325, 0x11d1, { 0x84, 0x6c, 0x0, 0x10, 0x4b, 0x21, 0x1b, 0xe5 } };
const GUID* CRootNode::m_NODETYPE = &CBenefitsGUID_NODETYPE;
const TCHAR* CRootNode::m_SZNODETYPE =
_T("E0573E71-D325-11D1-846C-00104B211BE5");
const TCHAR* CRootNode::m_SZDISPLAY_NAME = _T("Benefits");
const CLSID* CRootNode::m_SNAPIN_CLASSID = &CLSID_Benefits;

//
// Pass NULL in as the employee since this contains the valid
// employee. The pointer to the employee is leftover baggage
// from using the CBenefitsData() template.
//
CRootNode::CRootNode() : CChildrenBenefitsData< CRootNode >()
{
m_scopeDataItem.nOpenImage = 5;
m_scopeDataItem.nImage = 4;

//
// Always clear our dirty flag.
//
m_fDirty = false;
}

//
// Creates the benefits subnodes for the scope pane.
//
BOOL CRootNode::InitializeSubNodes()
{
CSnapInItem* pNode;

//
// Allocate sub nodes and add them to our internal list.
//
pNode = new CHealthNode( &m_Employee );
if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
return( FALSE );

pNode = new CRetirementNode( &m_Employee );
if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
return( FALSE );

pNode = new CKeyNode( &m_Employee );
if ( pNode == NULL || m_Nodes.Add( pNode ) == FALSE )
return( FALSE );

return( TRUE );
}

//
// Overridden to provide employee name for root node.
//
STDMETHODIMP CRootNode::FillData( CLIPFORMAT cf, LPSTREAM pStream )
{
HRESULT hr = DV_E_CLIPFORMAT;
ULONG uWritten;

//
// We need to write out our own member since GetDisplayName() does
// not give us an opportunity override its static implementation by
// ATL.
//
if (cf == m_CCF_NODETYPE)
{
hr = pStream->Write( GetNodeType(),
sizeof(GUID), &uWritten);
return hr;
}

if (cf == m_CCF_SZNODETYPE)
{
hr = pStream->Write( GetSZNodeType(),
(lstrlen((LPCTSTR) GetSZNodeType()) + 1 )* sizeof(TCHAR), &uWritten);
return hr;
}

if (cf == m_CCF_DISPLAY_NAME)
{
USES_CONVERSION;
TCHAR szDisplayName[ 256 ];
LPWSTR pwszName;

// Create a full display name.
CreateDisplayName( szDisplayName );
pwszName = T2W( szDisplayName );
hr = pStream->Write( pwszName, wcslen(
pwszName ) * sizeof( WCHAR ), &uWritten);
return hr;
}

if (cf == m_CCF_SNAPIN_CLASSID)
{
hr = pStream->Write( GetSnapInCLSID(),
sizeof(GUID), &uWritten);
return hr;
}

return hr;
}

//
// Overridden to add new columns to the results
// display.
//
STDMETHODIMP CRootNode::OnShowColumn( IHeaderCtrl* pHeader )
{
USES_CONVERSION;
HRESULT hr = E_FAIL;
CComPtr<IHeaderCtrl> spHeader( pHeader );

//
// Add two columns: one with the name of the object and one with
// the description of the node. Use the value of 200 pixels as the
size.
//
hr = spHeader->InsertColumn( 0, T2OLE( _T( "Benefit" ) ),
LVCFMT_LEFT, 200 );
_ASSERTE( SUCCEEDED( hr ) );

//
// Add the second column. Use the value of 350 pixels as the size.
//
hr = spHeader->InsertColumn( 1, T2OLE( _T( "Description" )
), LVCFMT_LEFT, 350 );
_ASSERTE( SUCCEEDED( hr ) );

return( hr );
}

STDMETHODIMP CRootNode::CreatePropertyPages(LPPROPERTYSHEETCALLBACK lpProvider,
long handle,
IUnknown* pUnk,
DATA_OBJECT_TYPES type)
{
UNUSED_ALWAYS( pUnk );
HRESULT hr = E_UNEXPECTED;

if ( type == CCT_SCOPE || type == CCT_RESULT || type ==
CCT_SNAPIN_MANAGER )
{
bool fStartup;

//
// Set the start-up flag based on the type of
pages to be
// created.
//
fStartup = type == CCT_SNAPIN_MANAGER ? true :
false;

//
// Allocate the new page. The second parameter
of the constructor
// indicates whether or not this is the
start-up wizard. The dialog
// handler will update the UI appropriately.
//
CEmployeeNamePage* pNamePage = new
CEmployeeNamePage( handle, fStartup, false, _T( "Employee Name" ) );
CEmployeeAddressPage* pAddressPage = new
CEmployeeAddressPage( handle, fStartup, false, _T( "Employee Address" ) );

//
// Set the page's employee.
//
pNamePage->m_pEmployee = &m_Employee;
pAddressPage->m_pEmployee = &m_Employee;

lpProvider->AddPage( pNamePage->Create()
);
lpProvider->AddPage(
pAddressPage->Create() );

//
// The second parameter to the property page
class constructor
// should be true for only one page.
//
hr = S_OK;
}

return( hr );
}

//
// Ensures that the appropriate verbs are displayed.
//
STDMETHODIMP CRootNode::OnSelect( IConsole* pConsole )
{
//
// Since we display property pages, make sure that the property page
// verb is enabled.
//
CComPtr<IConsoleVerb> spConsoleVerb;
HRESULT hr = pConsole->QueryConsoleVerb( &spConsoleVerb );

//
// Enable the properties verb.
//
hr = spConsoleVerb->SetVerbState( MMC_VERB_PROPERTIES, ENABLED, TRUE
);
_ASSERTE( SUCCEEDED( hr ) );

return( hr );
}

//
// Received when a property has changed. This function
// modifies the employee's display text. At a later date,
// it may post this message to its sub-nodes.
//
STDMETHODIMP CRootNode::OnPropertyChange( IConsole* pConsole )
{
HRESULT hr;
SCOPEDATAITEM* pScopeData;
CComQIPtr<IConsoleNameSpace,&IID_IConsoleNameSpace>
spNamespace( pConsole );
TCHAR szNameBuf[ 256 ];

//
// For demonstration purposes, always set the modified flag. This
// could be done more intelligently for real purposes.
//
SetModified();

//
// Always assume that the name changed. Recreate the display name
// since this will be called for after SetItem() is called.
//
CreateDisplayName( szNameBuf );
m_bstrDisplayName = szNameBuf;

//
// Fill out the scope item structure and set the item.
// This will cause MMC to call us for the new display
// text.
//
hr = GetScopeData( &pScopeData );

//
// Make sure that callback is specified.
//
hr = spNamespace->SetItem( pScopeData );

return( hr );
}

//
// Simply function to create the display name from the
// employee data.
//
int CRootNode::CreateDisplayName( TCHAR* szBuf )
{
USES_CONVERSION;

//
// Create a full display name.
//
_tcscpy( szBuf, W2T( m_Employee.m_szLastName ) );
_tcscat( szBuf, _T( ", " ) );
_tcscat( szBuf, W2T( m_Employee.m_szFirstName ) );

return( _tcslen( szBuf ) );
}