Chapter 26

Creating an ActiveX Container Application


An entire encyclopedia could be written on the programming of ActiveX applications. Obviously, it's not possible to fit an encyclopedia between the covers of this book. For that reason, Part IV of the book, including this chapter and the following three chapters will give you an introduction to ActiveX programming. In this chapter, you look at the basic process required to create an ActiveX container application, which is an application that can hold ActiveX objects in its documents.

Creating the Basic ActiveX Container Application

Thanks to AppWizard's amazing power, you can create a basic container application in only a couple of steps. The skeleton application created by AppWizard features all the basic functionality of an ActiveX container application, including the capability to embed, link, and edit ActiveX objects. Later in this chapter, in the sections entitled "Embedding an Object" and "Linking an Object," you'll see how to use the container application. But first you must create the application, which you can do by completing the following steps.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this version of the ActiveXCont1 application is located in the Chap26\ActiveXCont1, Part 1 folder at this book's Web site.

1. Start a new AppWizard project workspace called ActiveXCont1, as shown in Figure 26.1.


Fig. 26.1

Here's how you start ActiveXCont1.

2. Give the new project the following settings in the AppWizard dialog boxes. The New Project Information dialog box should then look like Figure 26.2.

Step 1: Single document

Step 2: Default settings

Step 3: Select Container and Yes, Please for OLE compound files. Leave other options as is.

Step 4: Default settings

Step 5: Default settings

Step 6: Default settings


Fig. 26.2

These are the AppWizard settings for the ActiveXCont project.

You've now created the basic ActiveXCont1 application. To compile the program, select Developer Studio's Build, Build command. Then, select Build, Execute to run the program. When you do, you see the window shown in Figure 26.3. Thanks to AppWizard and MFC, the program is already a fairly powerful container application that can link or embed data supplied by an ActiveX server application.


FIG. 26.3

The new ActiveXCont1 application's main window looks like this.

Embedding an Object

To see ActiveX in action, select the application's Edit, Insert New Object command. When you do, the Insert Object dialog box appears (see Figure 26.4), from which you can select the type of object you want to insert and determine whether the object should be linked or embedded. The Create N ew option causes ActiveX to run the server application associated with the type of object you choose. You can then create a new object and embed it into ActiveXCont1's currently open document.


FIG. 26.4

The Insert Object dialog box enables you to insert objects into a currently open document.

For example, leave the Create New option selected, and double-click the Bitmap Image in the Object Type box. ActiveX then requests the server to run so that you can create your new bitmap image. If you haven't changed the server associated with bitmap files in your system, Microsoft Paint will run and merge its menus and toolbars with those of the ActiveXCont1 container application, as shown in Figure 26.5. You can now use the new menus and tools to create the new bitmap image, all without ever having to leave the ActiveXCont1 application.


FIG. 26.5

Microsoft Paint merges its menus and tools into the ActiveXCont1 application.

When a server application is capable of merging its menus and toolbars with the container application, it supports in-place editing. In-place editing means that you can edit the inserted object without having to leave the container application.

Normally, when you're finished creating the new bitmap image, you can click somewhere else in the document, and ActiveX removes the server application's menus and tools from the container application's window. This capability, however, has not yet been added to the ActiveXCont1 application. You'll take care of that missing piece of the puzzle later in the chapter, in the section entitled "Enabling Mouse Selection of Objects" For now, you can close the server application by pressing Esc. When you do, you'll see the ActiveXCont1 application with the new bitmap object embedded in its open document (see Figure 26.6).


FIG. 26.6

The new bitmap image is embedded into the currently open document.

Because you created the bitmap object from scratch from inside the ActiveXCont1 application, the bitmap doesn't exist as a separate file on disk. Instead, it's an integral part of the document in which it's embedded. To see that this is true, select the application's File, Save command, and save the document under the file name Test.axc. Then, use Windows Explorer to examine the contents of the ActiveXCont1 project folder. You'll find the Test.axc file that you just saved, but you won't find a bitmap file. Because Test.axc contains the bitmap file, the bitmap image cannot be linked with other applications' documents. The bitmap is a separate entity that exists only in the Test.axc document.

Linking an Object

When you insert an object into a document, you can choose whether to create a new object, as you did in the previous section, or to insert an object that already exists on disk. A new object can only be embedded into the document, because the object doesn't exist as a disk file. Obviously, other applications can't link to a data object that doesn't exist separately on the disk.

Objects in their own files such as bitmap files with the .BMP file extension can be either linked or embedded into documents. To try this out, follow these steps:

1. Copy the Aztec.bmp file from the Chap26\ActiveXCont1 folder on this book's Web site to your ActiveXCont1 project folder. (Make sure you also turn off the file's read-only attribute.)

2. Select File, New from ActiveXCont1's menu bar. A new document appears in the window.

3. Select Edit, Insert New Object, and the Insert Object dialog box appears again.

4. Select the Create from File option. The dialog box changes to include a Browse button that you can select to find the file that you want to link or embed (see Figure 26.7).

5. Use the browse feature to locate and select the Aztec.bmp file you copied to the project's folder.


FIG. 26.7

When you want to insert a file, the Insert Object dialog box changes to give you file browsing capabilities.

If you were to select the Insert Object dialog box's OK button at this point, the selected bitmap would be embedded into the document. However, you can link the bitmap by first selecting the Link option in the dialog box. When you do, click the dialog box's OK button, and ActiveX inserts the bitmap into the document by linking to the bitmap's file, as shown in Figure 26.8. Save the document containing the newly linked object under the name Test2.axc, and then close the document window by selecting File, New.


FIG. 26.8

Here's the bitmap linked (rather than embedded) into the ActiveX1 document.

You might remember that when a linked object is edited, all documents that contain a link to that object are automatically updated. To prove this, bring up Windows Explorer, and double-click the Aztec.bmp to call up the program you use to edit bitmaps. Now, change the bitmap any way you like, and save the bitmap back to disk.

To see how basic linking works, reload the Test2.ax1 file. Then, select the Edit, Links command. The Links dialog box, which displays the bitmap’s link to the document, appears (see Figure 26.9). Click the Update Now button to update the linked image. The document now shows the changes you made to the bitmap.


FIG. 26.9

The Links dialog box enables the user to manipulate an item's link to the document.

If you edit an item from within the container application, the updating mechanism works automatically. For example, you could have edited the Aztec.bmp file by selecting the Edit, Linked Bitmap Image Object, Edit command. In that case, when you closed the bitmap editor, the changes would appear automatically in the container application's document.

Understanding the ActiveXCont1 Skeleton Application

As you might have guessed, to implement the ActiveX functionality supported by the application, there's a lot going on under the hood of ActiveXCont1. In this section, you'll examine the pertinent parts of the AppWizard-generated code that's important to ActiveX.

As you look through MFC's ActiveX code, you'll notice that the word OLE comes up a lot. This is because MFC's classes and member functions were named before OLE was changed to ActiveX.

Exploring the CActiveXCont1App Class

To understand the source code, the first place to look is in InitInstance(), where the application performs its initialization at start-up. That function, which is defined as part of the CActiveXCont1App class, performs its normal initialization, as well as some special initialization for ActiveX. To initialize the ActiveX libraries, InitInstance() calls AfxOleInit(), as shown in Listing 26.1:

Listing 26.1 lst26_01.cpp Calling AfxOleInit()

// Initialize OLE libraries
if (!AfxOleInit())
{
    AfxMessageBox(IDP_OLE_INIT_FAILED);
    return FALSE;
}

The InitInstance() function also calls the AfxEnableControlContainer() global function, which enables the application to act as a container for ActiveX controls. That function call looks like this:

AfxEnableControlContainer();

Mixed in amongst the standard MFC function calls for setting up the application's document and view, InitInstance() calls the CDocTemplate object's SetContainerInfo() member function to give the application the menu and other resources it needs to handle an embedded object when said object is being edited in-place. That function call looks like this:

pDocTemplate->SetContainerInfo(IDR_CNTR_INPLACE);

If you look at the ActiveXCont1 application's menu resources, you'll see that there are two menus defined, with the IDs IDR_CNTR_INPLACE and IDR_MAINFRAME. Those menus are combined to provide the container with normal and ActiveX commands on the menu bar.

Exploring the CActiveXCont1CntrItem Class

Like everything else in MFC, an embedded or linked object is represented by an instance of a class. MFC provides the COleClientItem class to represent these types of objects. In the ActiveXCont1 application's source code, the CActiveXCont1CntrItem class is derived from COleClientItem and so represents embedded objects for this specific application. Table 26.1 lists the functions provided by AppWizard for the CActiveXCont1CntrItem class, along with their descriptions:

Table 26.1 Member functions of the CActiveXCont1CntrItem

Class Function Description
OnActivate() Called by MFC when the object is activated in-place, but before the server's user interface (menus and tools) have been merged with the container application's interface. The AppWizard-generated version of this function gets a pointer to the activated item, checking to be sure that the pointer isn't NULL and that the pointer isn't equal to the this pointer. The function then calls the base class version of OnActivate().
OnChange() Called by MFC whenever the user changes the embedded object, including editing, saving, or closing the object. The AppWizard version of this function simply calls the base class's OnChange() and then updates all views.
OnChangeItemPosition() Called by MFC when the server requests a change to the in-place window's position. The AppWizard-generated version of this function simply calls the base class's version.
OnDeactivateUI() Called by MFC when an object is deactivated so that the container application can restore its menus and tools. The AppWizard-generated version of this function performs most of its work by calling the base class version.
OnGetItemPosition() Called by MFC to get the location of the object. The AppWizard-generated version of the function specifies a hard-coded position rectangle of 10, 10, 210, 210.
Serialize() Called by MFC to store the object. The AppWizard-generated version does little more than call the base class version of the function.

Exploring the CActiveXCont1View Class

When the user selects the Insert New Object command, MFC calls the view class's OnInsertObject() member function. OnInsertObject(), shown in Listing 26.2, first displays the Insert Object dialog box to gather information about the new object from the user. The function then displays the hourglass cursor and creates an instance of the CActiveXCont1CntrItem class, which represents the object about to be inserted into the document. If this new object is not a file, OnInsertObject() starts the appropriate server application to enable the user to edit the new object. Because a new object is always in a selected state, the function sets the m_pSelection data member, which holds a pointer to the currently selected object, to the new object's pointer. Finally, the function updates all views and turns off the hourglass cursor.

The program line pItem = new CActiveXCont1CntrItem(pDoc) only creates the container item it doesn't initialize it. The info needed to initialize the container item from the Insert Object dialog box is contained in the COleInsertDialog dialog box. That's why the function that initializes the container item class is dlg.CreateItem(), a member of COleInsertDialog, not a member of CActiveXCont1View or CActiveXCont1CntrItem.

Listing 26.2 lst26_02.cpp Responding to the Insert New Object Command

void CActiveXCont1View::OnInsertObject()
{
    // Invoke the standard Insert Object dialog box
    //  to obtain information
    //  for new CActiveXCont1CntrItem object.
    COleInsertDialog dlg;
    if (dlg.DoModal() != IDOK)
        return;
    BeginWaitCursor();
    CActiveXCont1CntrItem* pItem = NULL;
    TRY
    {
        // Create new item connected to this document.
        CActiveXCont1Doc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);
        pItem = new CActiveXCont1CntrItem(pDoc);
        ASSERT_VALID(pItem);
        // Initialize the item from the dialog data.
        if (!dlg.CreateItem(pItem))
            AfxThrowMemoryException();  // any exception will do
        ASSERT_VALID(pItem);
        // If item created from class list (not from file) then launch
        //  the server to edit the item.
        if (dlg.GetSelectionType() == COleInsertDialog::createNewItem)
            pItem->DoVerb(OLEIVERB_SHOW, this);
        ASSERT_VALID(pItem);
        // As an arbitrary user interface design,
        //  this sets the selection
        //  to the last item inserted.
        // TODO: reimplement selection as appropriate
        //  for your application
        m_pSelection = pItem;   // set selection to last inserted item
        pDoc->UpdateAllViews(NULL);
    }
    CATCH(CException, e)
    {
        if (pItem != NULL)
        {
            ASSERT_VALID(pItem);
            pItem->Delete();
        }
        AfxMessageBox(IDP_FAILED_TO_CREATE);
    }
    END_CATCH
    EndWaitCursor();
}

The IsSelected() member function (Listing 26.3) has an easy task determine whether the given object is currently selected. The function only has to compare the given pointer to the m_pSelection data member, which holds a pointer to the currently selected item. If no item is selected, m_pSelection is NULL.

Listing 26.3 lst26_03.cpp The IsSelected() Member Function

BOOL CActiveXCont1View::IsSelected(const CObject* pDocItem) const
{
   // The implementation below is adequate if your selection consists of
   //  only CActiveXCont1CntrItem objects.  To handle different selection
   //  mechanisms, the implementation here should be replaced.
   // TODO: implement this function that tests for a
      //   selected OLE client item
   return pDocItem == m_pSelection;
}

Whenever the user resizes a document window when an object is being edited in-place, the server application needs to be informed of the change. This happens in the view class's OnSize() member function (Listing 26.4). The AppWizard-generated version of this function first calls the base class's OnSize(). It then gets a pointer to the currently active item. If the pointer is NULL, there is no active object. If the pointer is not NULL, the function calls SetItemRects() on behalf of the object, which sets the object's new bounding rectangle.

Listing 26.4 lst26_04.cpp The OnSize() Member Function

void CActiveX1View::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL)
        pActiveItem->SetItemRects();
}

When a window containing an object that's being edited in-place receives the focus, the object being edited needs to take over the focus. This task is handled in the OnSetFocus() member function (Listing 26.5). This function first gets a pointer to the object being activated in-place. If the returned pointer is not NULL and the object's server has activated its user interface (merged its menus and tools), OnSetFocus() gets a pointer to the in-place window and gives the window the focus. Before returning, OnSetFocus() calls the base class's version of OnSetFocus().

Listing 26.5 lst26_05.cpp The OnSetFocus() Member Function

void CActiveX1View::OnSetFocus(CWnd* pOldWnd)
{
    COleClientItem* pActiveItem =
        GetDocument()->GetInPlaceActiveItem(this);
    if (pActiveItem != NULL &&
        pActiveItem->GetItemState() == COleClientItem::activeUIState)
    {
        // need to set focus to this item if it is in the same view
        CWnd* pWnd = pActiveItem->GetInPlaceWindow();
        if (pWnd != NULL)
        {
            pWnd->SetFocus();   // don't call the base class
            return;
        }
    }
    CView::OnSetFocus(pOldWnd);
}

The last member function of interest in the view class is OnDraw() (Listing 26.6), which is responsible for drawing, not only the document's native data (data that's created by the application itself, not by ActiveX objects), but also any embedded or linked objects present in the document. As the comments in the function indicate, the AppWizard-generated code in OnDraw() is only temporary and must be replaced with your own code. The default function draws a single linked or embedded object by calling the object's Draw() member function.

Listing 26.6 lst26_06.cpp The OnDraw() Member Function

void CActiveXCont1View::OnDraw(CDC* pDC)
{
    CActiveXCont1Doc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
    // TODO: also draw all OLE items in the document
    // Draw the selection at an arbitrary position.
    // This code should be
    //  removed once your real drawing code is implemented.
    // This position
    //  corresponds exactly to the rectangle returned by
    //  CActiveXCont1CntrItem,
    //  to give the effect of in-place editing.
    // TODO: remove this code when final draw code is complete.
    if (m_pSelection == NULL)
    {
        POSITION pos = pDoc->GetStartPosition();
        m_pSelection =
            (CActiveXCont1CntrItem*)pDoc->GetNextClientItem(pos);
    }
    if (m_pSelection != NULL)
        m_pSelection->Draw(pDC, CRect(10, 10, 210, 210));
}

Exploring the CActiveXCont1Doc Class

The application's document class also contains some ActiveX code. First, the document class is derived from COleDocument, like this:

class CActiveXCont1Doc : public COleDocument

As you might have guessed, documents of the COleDocument class can contain ActiveX objects.

The document class also implements the message map for ActiveX items on the application's Edit menu. Listing 26.7 shows how this message map appears in the AppWizard-generated code.

Listing 26.7 lst26_07.cpp The Document Class's Message Map

BEGIN_MESSAGE_MAP(CActiveXCont1Doc, COleDocument)
    //{{AFX_MSG_MAP(CActiveXCont1Doc)
        // NOTE - the ClassWizard will add and remove
        //  mapping macros here.
        //    DO NOT EDIT what you see in these blocks
        //    of generated code!
    //}}AFX_MSG_MAP
    // Enable default OLE container implementation
    ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE,
        COleDocument::OnUpdatePasteMenu)
    ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE_LINK,
        COleDocument::OnUpdatePasteLinkMenu)
    ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_CONVERT,
        COleDocument::OnUpdateObjectVerbMenu)
    ON_COMMAND(ID_OLE_EDIT_CONVERT,
        COleDocument::OnEditConvert)
    ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS,
        COleDocument::OnUpdateEditLinksMenu)
    ON_COMMAND(ID_OLE_EDIT_LINKS, COleDocument::OnEditLinks)
    ON_UPDATE_COMMAND_UI(ID_OLE_VERB_FIRST,
        COleDocument::OnUpdateObjectVerbMenu)
END_MESSAGE_MAP()

Finally, the document class's constructor is responsible for enabling compound documents, as shown in Listing 26.8. Simply put, compound documents are documents that contain, not only data native to the application, but also linked or embedded ActiveX objects. Obviously, such a document requires a special type of storage. This special type of storage is called structured storage.

Listing 26.8 lst26_08.cpp The Document Class's Constructor

CActiveXCont1Doc::CActiveXCont1Doc()
{
    // Use OLE compound files
    EnableCompoundFile();
    // TODO: add one-time construction code here
}

Now that you have a general idea of what goes on inside the basic AppWizard-generated container application, it's time to extend the application to include some other important ActiveX features. In the next section, you'll learn how to enable the user to select and activate an ActiveX object with a mouse click.

Enabling Mouse Selection of Objects

In a standard container application, the user can select a linked or embedded object by clicking it with the mouse. Moreover, a double-click should activate the object for editing. Currently, the ActiveXCont1 application is unable to perform either of these functions. Perform the following steps to add the code needed to enable mouse selection of ActiveX objects.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this version of the ActiveXCont1 application is located in the Chap26\ActiveXCont1, Part 2 folder at this book's Web site.

1. Load the CntrItem.h file, and add the following line to the class's public Attributes section, right after the public keyword:

CRect m_itemRect;

This CRect object will hold the currently selected ActiveX object's size and position.

2. Load the CntrItem.cpp file, and add the following line to the item class's constructor:

m_itemRect.SetRect(20, 20, 150, 150);

This line initializes the ActiveX object's starting position and size.

3. In the OnGetItemPosition() function, replace the line rPosition.SetRect(10, 10, 210, 210) with the following:

rPosition = m_itemRect;

This line sets rPosition to the ActiveX object's current rectangle.

4. In the OnChangeItemPosition() function, add the lines shown in listing 26.9 right after the TODO: update any cache you may have of the item's rectangle/extent comment:

Listing 26.9 lst26_09.cpp Line for the OnChangeItemPosition() Function

    // Get a pointer to the document.
    CActiveXCont1Doc* pDoc = GetDocument();
    // Redraw all views of this document.
    pDoc->UpdateAllViews(NULL);
    // Save the item's new position.
    m_itemRect = rectPos; 
    
    // Tell MFC that the document needs to be saved.
    pDoc->SetModifiedFlag();

These lines update all views of the document, save the ActiveX object's new position in its m_itemRect data member, and tell the document it's been modified (and so needs to be saved).

5. Add the following line to the serialize() function, right after the TODO: add storing code here comment:

ar << m_itemRect;

This line ensures that the ActiveX object's current position is saved when the document containing the object is saved.

6. Add the following line to the serialize() function, right after the TODO: add loading code here comment:

ar >> m_itemRect;

This line loads the ActiveX object's last position when the document containing the object is opened.

7. Use ClassWizard to add the OnLButtonDown() member function to the CActiveXCont1View class, as shown in Figure 26.10.


FIG. 26.10

The ClassWizard window should look like this after you've added OnLButtonDown().

8. Click the Edit Code button, and add the lines shown in Listing 26.10 to the OnLButtonDown() function, right after the TODO: Add your message handler code here and/or call default comment. You'll study these lines later in the chapter, in the section entitled "Exploring the OnLButtonDown() Function."

Listing 26.10 lst26_10.cpp Code for the OnLButtonDown() Function

    // Get the item located at the mouse click.
    CActiveXCont1CntrItem* pHitItem = GetHitItem(point);
    // Set the clicked item as selected.
    SetSelectedItem(pHitItem);
    
    // If an item was selected...
    if (pHitItem != NULL)
    {
        // Create and initialize a rect tracker.
        CRectTracker rectTracker;
        InitTracker(&rectTracker, pHitItem);
        // Update the window's client area.
        UpdateWindow();
        // Enable the user to manipulate the item's rectangle.
        if (rectTracker.Track(this, point))
        {
            // Redraw the window's display.
            Invalidate();
            // Record item's new position.
            pHitItem->m_itemRect = rectTracker.m_rect;
            // Tell MFC that the document needs to be saved.
            CActiveXCont1Doc* pDoc = GetDocument();
            pDoc->SetModifiedFlag();
        }
    }

9. Use ClassWizard to add the OnLButtonDblClk() member function to the CActiveXCont1View class, as shown in Figure 26.11.


FIG. 26.11

Here, ClassWizard is adding the OnLButtonDblClk() message-response function.

10. Click the Edit Code button, and add the lines shown in Listing 26.11 to the OnLButtonDblClk() function, right after the TODO: Add your message handler code here and/or call default comment. You'll study these lines later in the chapter, in the section "Exploring the OnLButtonDblClk() Function."

Listing 26.11 lst26_11.cpp Code for the OnLButtonDblClk() Function

    // Select the clicked item.
    OnLButtonDown(nFlags, point);
    
    // If an item was selected...
    if (m_pSelection != NULL)
    {
        // Get the state of the Ctrl key.
        SHORT keyState = GetKeyState(VK_CONTROL);
        LONG verb;
        // If the Ctrl key was pressed,
        // the user wants to open the item.
        if (keyState < 0)
            verb = OLEIVERB_OPEN;
        // Else the user wants to perform the
        // item's primary command.
        else
            verb = OLEIVERB_PRIMARY;
        // Perform the selected action.
        m_pSelection->DoVerb(verb, this);
    }

11. Use ClassWizard to add the OnSetCursor() member function to the CActiveXCont1View class, as shown in Figure 26.12.


FIG. 26.12

Here, ClassWizard is adding the OnSetCursor() message response function.

12. Click the Edit Code button, and add the lines shown in Listing 26.12 to the OnSetCursor() function, right after the TODO: Add your message handler code here and/or call default comment. You'll study these lines later in the chapter, in the section entitled "Exploring the OnSetCursor() Function."

Listing 26.12 lst26_12.cpp Code for the OnSetCursor() Function

    // If there's a selected item in this window...
    if ((pWnd == this) && (m_pSelection != NULL))
    {
        // Create and initialize a rect tracker.
        CRectTracker rectTracker;
        InitTracker(&rectTracker, m_pSelection);
        // If the cursor is over an item's rectangle,
        // enable the tracker to set the mouse cursor.
        BOOL trackerSetCursor =
            rectTracker.SetCursor(this, nHitTest);
        // If the tracker set the cursor,
        // return from the function.
        if (trackerSetCursor)
            return TRUE;
    }
    // If the tracker didn't set the cursor,
    // let the window do it.

13. Add the functions shown in Listing 26.13 to the end of the ActiveXCont1View.cpp file.

These new functions initialize a CRectTracker object, determine whether the user clicked an embedded or linked item, and get a pointer to the selected item. You'll study these functions in detail later in this chapter, in the sections "Exploring the InitTracker() Function," "Exploring the GetHitItem() Function," and "Exploring the SetSelectedItem() Function."

Listing 26.13 lst26_13.cpp New Functions for the View Class

void CActiveXCont1View::InitTracker(CRectTracker* pRectTracker,
    CActiveXCont1CntrItem* pItem)
{
    // Get the size of the item.
    pRectTracker->m_rect = pItem->m_itemRect;
    
    // If the item is selected, draw the resize handles.
    if (pItem == m_pSelection)
        pRectTracker->m_nStyle |= CRectTracker::resizeInside;
    
    // If the item is linked, surround the
    // item with a dotted line.
    if (pItem->GetType() == OT_LINK)
        pRectTracker->m_nStyle |= CRectTracker::dottedLine;
    // Else if the item is embedded, surround
    // the item with a solid line.
    else
        pRectTracker->m_nStyle |= CRectTracker::solidLine;
    
    // If the item is being in-place edited, draw
    // a cross-hatch pattern over the item.
    UINT itemState = pItem->GetItemState();
    if ((itemState == COleClientItem::openState) ||
            (itemState == COleClientItem::activeUIState))
        pRectTracker->m_nStyle |= CRectTracker::hatchInside;
}
CActiveXCont1CntrItem* CActiveXCont1View::GetHitItem(CPoint point)
{
    CActiveXCont1Doc* pDoc = GetDocument();
    CActiveXCont1CntrItem* pHitItem = NULL;
    // Get the position of the first item.
    POSITION position = pDoc->GetStartPosition();
    // Loop through all the items.
    while (position != NULL)
    {
        // Get a pointer to the next item.
        CActiveXCont1CntrItem* pItem =
            (CActiveXCont1CntrItem*)pDoc->GetNextItem(position);
        // If the current item contains the clicked point,
        // set the pointer to the hit item.
        BOOL hit = pItem->m_itemRect.PtInRect(point);
        if (hit)
            pHitItem = pItem;
    }
    return pHitItem;
}
void CActiveXCont1View::SetSelectedItem(CActiveXCont1CntrItem* pItem)
{
    // Get a pointer to the document.
    CActiveXCont1Doc* pDoc = GetDocument();
    // If user clicked on an empty part of the window,
    // close the currently active in-place item.
    if (pItem == NULL || m_pSelection != pItem)
    {
        // Get a pointer to the item that's
        // currently being edited in-place.
        COleClientItem* pActiveItem =
            pDoc->GetInPlaceActiveItem(this);
        // Close the in-place item.
        if (pActiveItem != NULL && pActiveItem != pItem)
            pActiveItem->Close();
    }
    // Set the currently selected item.
    m_pSelection = pItem; 
    // Redraw the window.
    Invalidate();
}

14. Add the function declarations shown in Listing 26.14 to the ActiveXCont1View.h header file. Place the lines in the Implementation section, right after the protected keyword.

Listing 26.14 lst26_14.cpp New Function Declarations

    void InitTracker(CRectTracker* pRectTracker,
        CActiveXCont1CntrItem* pItem);
    CActiveXCont1CntrItem* GetHitItem(CPoint point);
    void SetSelectedItem(CActiveXCont1CntrItem* pItem);

15. In the OnDraw() function, replace all the lines following the TODO: remove this code when final draw code is complete comment with the lines shown in Listing 26.15. You'll examine these lines in "Examining the OnDraw() Function."

Listing 26.15 lst26_15.cpp Lines for the OnDraw() Function

    // Get the position of the first item.
    POSITION position = pDoc->GetStartPosition();
    
    // Loop through all items.
    while (position != NULL)
    {
        // Get a pointer to the next item.
        CActiveXCont1CntrItem* pItem =
            (CActiveXCont1CntrItem*)pDoc->GetNextItem(position);
        // Draw the item.
        pItem->Draw(pDC, pItem->m_itemRect);
        
        // Draw the appropriate tracker rectangle on the item.
        CRectTracker rectTracker;
        InitTracker(&rectTracker, pItem);
        rectTracker.Draw(pDC);
    }

You've now created part 2 of the ActiveXCont1 application. To compile the program, select Developer Studio's Build, Build command. Then, select Build, Execute to run the program.

Selecting ActiveX Objects

When you run the new version of ActiveXCont1, you see the application's main window. Go ahead and link a copy of the Aztec.bmp file into the application's currently open document. When you do, the application's window looks something like Figure 26.13. As you can see, because the bitmap was just added to the document, it is the currently selected item. This means that the bitmap item is surrounded by a selection rectangle that includes handles for moving and resizing the item.


FIG. 26.13

Here's ActiveXCont1's window after linking the Aztec.bmp file.

To move the bitmap, just drag it with the mouse pointer. Notice how the mouse pointer changes when it's over the item. To resize the item, use the mouse pointer to manipulate the item's sizing handles. Again, when the mouse pointer is over one of the handles, its form changes to indicate the operation you're about to perform on the item. Figure 26.14 shows the bitmap item after it's been moved and resized.


FIG. 26.14

You can use your mouse to move and resize the bitmap item.

To deselect the bitmap object, just click inside the window. When you do, the sizing handles disappear. The dotted outline, however, remains. The dotted outline indicates that the item is linked. Had you embedded the item, it would have a solid outline. Reselecting the bitmap object is just a matter of clicking it with the mouse. To edit the bitmap with its server application, double-click the item. Because the item is linked, the server application runs in its own window, as shown in Figure 26.15.


FIG. 26.15

In most cases, Microsoft Paint is the server application for bitmaps.

Understanding Part 2 of ActiveXCont1

Although AppWizard created a fairly powerful skeleton program for you, when you added mouse item-selection, you had quite a bit of work to do. In this section, you'll explore the code lines that implement this important ActiveX feature.

Exploring Changes to the ActiveXCont1CntrItem Class

As you already know, in the ActiveXCont1 application, the ActiveXCont1CntrItem class represents ActiveX objects that are linked or embedded into a document. In the original version of the application, these objects were positioned at a default location and given a default size. In the new version of the application, you've taken over control of an item's position and size. First, you created a data member that you can use to store the item's current location and size:

CRect m_itemRect;

In the ActiveXCont1CntrItem class's constructor, the program initializes the m_itemRect data member like this:

m_itemRect.SetRect(20, 20, 150, 150);

To change the initial size of embedded or linked items, you only need to change the values to which the CRect object is initialized.

When MFC needs to know the position of an item, it calls the item's OnGetItemPosition() member function, whose single parameter is a reference to a CRect object named rPosition. All your program needs to do is set this CRect object to the rectangle object holding the item's current size and position, like this:

rPosition = m_itemRect;

When the user decides to move the item, MFC calls the item's OnChangeItemPosition() member function, giving your program a chance to redraw all views displaying the item and to record the new position into the m_itemRect data member. If you have only a single view of the item, you can probably get away without redrawing the views, but it's always a good idea to follow MFC's rules. To redraw the views, you first get a pointer to the document object and then call the UpdateAllViews() member function through that pointer, like this:

CActiveXCont1Doc* pDoc = GetDocument();
pDoc->UpdateAllViews(NULL);

Storing the item's new position is as easy as copying the rectPos parameter to the m_itemRect data member:

m_itemRect = rectPos;

You'll also want to tell MFC that the document has been modified. Then, if the user tries to exit the application before saving the document, MFC displays a suitable warning. Calling the document object's SetModifiedFlag() member function takes care of this problem:

pDoc->SetModifiedFlag();

Finally, you want the CActiveXCont1CntrItem class to save the location of its items when it saves its document. You do this by adding lines to the class's Serialize() member function. This line saves the value of the CRect object:

ar << m_itemRect;

This line, on the other hand, reads the CRect object back in from the file:

ar >> m_itemRect;

Exploring the OnLButtonDown() Function

When the user wants to select or deselect an item, he clicks the item or the window with his mouse. This means that the OnLButtonDown() message-response function, which MFC calls in response to mouse clicks, is the perfect place to implement mouse selection of ActiveX objects. In the CActiveXCont1View class, the OnLButtonDown() function first determines whether an ActiveX item has been clicked:

CActiveXCont1CntrItem* pHitItem = GetHitItem(point);

GetHitItem() is a local function that you added to the program. You'll examine that function in detail soon, in the section entitled "Exploring the GetHitItem() Function." For now, just know that the function returns a pointer to the item at the location of the mouse click or returns NULL if no item was clicked. The location of the mouse click is conveniently passed to OnLButtonDown() in the point parameter.

After getting a pointer to the clicked item (if any), the program calls SetSelectedItem() to determine whether the mouse click was meant to select or deselect an item:

SetSelectedItem(pHitItem);

Again, SetSelectedItem() is a local function that you added to the program. You'll examine this function in detail in the section entitled "Exploring the SetSelectedItem() Function."

Next, OnLButtonDown() checks whether an item was actually selected:

if (pHitItem != NULL)

If so, the program creates and initializes a CRectTracker object:

CRectTracker rectTracker;
InitTracker(&rectTracker, pHitItem);

A CRectTracker object is responsible for displaying a selection rectangle around an item, including displaying the item's selection handles. This process is handled in the InitTracker() function, which is a member function you added to the class. You'll examine this function in the section called "Exploring the InitTracker() Function." All you need to know now is that InitTracker() sets up the CRectTracker object as appropriate for the type of item that's been selected.

After setting up the CRectTracker object, the program calls the CRectTracker's Track() member function, which enables the user to manipulate the item:

if (rectTracker.Track(this, point))

The Track() function returns TRUE if the user completes the item manipulation. In this case, the program must redraw the item in its new location or size and store the item's new rectangle:

Invalidate();
pHitItem->m_itemRect = rectTracker.m_rect;

Finally, the program notifies MFC that the document has been changed and needs to be saved:

CActiveXCont1Doc* pDoc = GetDocument();
pDoc->SetModifiedFlag();

Exploring the OnLButtonDblClk() Function

When the user wants to edit an item, she double-clicks it. So, item-editing program lines should be placed in the view class's OnLButtonDblClk() member function, which MFC calls when the user double-clicks in a window.

In the CAvtiveXCont1View class, the first thing OnLButtonDblClk() does is call OnLButtonDown() to handle the item-selection process:

OnLButtonDown(nFlags, point);

The function then checks that an item has been selected:

if (m_pSelection != NULL)

If so, the program gets the state of the keyboard's Ctrl key:

SHORT keyState = GetKeyState(VK_CONTROL);

If the Ctrl key were pressed (keyState < 0) during the double-click, the user wants to open the selected item. The predefined verb (as ActiveX item commands are called) for this action is OLEIVERB_OPEN:

if (keyState < 0)
    verb = OLEIVERB_OPEN;

If the Ctrl key weren't pressed, the user is requesting that the item's primary command, which is represented by the predefined OLEIVERB_PRIMARY value, be executed:

else
    verb = OLEIVERB_PRIMARY;

To execute the selected item verb, the program calls the selected item's DoVerb() member function, like this:

m_pSelection->DoVerb(verb, this);

Exploring the OnSetCursor() Function

Whenever the user places the mouse pointer over a selected linked or embedded item, the pointer should change to show the action that the user can currently perform with the mouse. This happens in the view class's OnSetCursor() function. This function first checks whether there's a selected item in the window:

if ((pWnd == this) && (m_pSelection != NULL))

If there's no selected item, OnSetCursor() just handles the mouse cursor the normal way by calling the base class's OnSetCursor(). If there is a selected item, the function creates and initializes a CRectTracker object for the currently selected item:

CRectTracker rectTracker;
InitTracker(&rectTracker, m_pSelection);

The function then calls the CRectTracker object's SetCursor() member function to set the mouse pointer to the appropriate shape for the part of the tracker rectangle the pointer is over:

BOOL trackerSetCursor =
    rectTracker.SetCursor(this, nHitTest);

The tracker's SetCursor() function returns TRUE if it handles the pointer and FALSE if it doesn't. The function uses the returned value, stored in trackerSetCursor, to determine whether to return the following from the function:

if (trackerSetCursor)
    return TRUE;

Exploring the OnDraw() Function

As you know from many previous MFC programs, the OnDraw() function is responsible for drawing a view window's display. This is true for ActiveX applications, as well, except that ActiveX containers must draw both native data and any linked or embedded items. Because the current document can contain more than one linked or embedded item, the OnDraw() function must locate and draw all the items. To do this, the function first gets the position of the first item by calling the document object's GetStartPosition() member function:

POSITION position = pDoc->GetStartPosition();

If this function returns NULL, there is no first object to find. However, if it returns the position of an object, OnDraw() begins a while loop that'll iterate through all the items in the document:

while (position != NULL)

Inside the loop, the program gets a pointer to the item located at the position stored in the position variable:

CActiveXCont1CntrItem* pItem =
    (CActiveXCont1CntrItem*)pDoc->GetNextItem(position);

OnDraw() can then draw the item by calling the item's Draw() function:

pItem->Draw(pDC, pItem->m_itemRect);

This function requires two parameters, which are a pointer to the device context and the CRect object, holding the item's size and position.

Finally, because an item drawn might be selected, the appropriate CRectTracker rectangle must be drawn. OnDraw() handles this important task like this:

CRectTracker rectTracker;
InitTracker(&rectTracker, pItem);
rectTracker.Draw(pDC);

Although not done in ActiveXCont1, any native data (data that was created by the container application, such as the text is a word processor's document), should also be drawn in OnDraw(), just as it normally would be drawn in a non-ActiveX application.

Exploring the InitTracker() Function

Before a CRectTracker object can be used, it must be initialized. How this is done depends upon the type of item with which the tracker is being used. In the ActiveXCont1 application, the InitTracker() function takes care of these details. First, InitTracker() sets the size of the tracker rectangle to that of the item, like this:

pRectTracker->m_rect = pItem->m_itemRect;

Then, if the item is selected, the tracker must display resize handles, which is done by adding the CRectTracker::resizeInside style to the tracker's m_nStyle style data member:

if (pItem == m_pSelection)
    pRectTracker->m_nStyle |= CRectTracker::resizeInside;

If the item is linked into the document, it must have a dotted outline, which is represented by the tracker style CRectTracker::dottedLine:

if (pItem->GetType() == OT_LINK)
    pRectTracker->m_nStyle |= CRectTracker::dottedLine;

If the item is embedded rather than linked, it needs a solid outline:

else
    pRectTracker->m_nStyle |= CRectTracker::solidLine;

A little trickier is the process of determining whether or not the object is in its open and active state. In such a case, the item needs to be overlayed with a hatch pattern. To do this, InitTracker() first retrieves the item's state by calling the item's GetItemState() member function:

UINT itemState = pItem->GetItemState();

The program can then compare the state to the flags that indicate whether the item is active. If it is, the CRectTracker::hatchInside style is added to the tracker rectangle:

if ((itemState == COleClientItem::openState) ||
        (itemState == COleClientItem::activeUIState))
    pRectTracker->m_nStyle |= CRectTracker::hatchInside;

When the tracker rectangle is drawn, the styles that were set by InitTracker() will control the tracker rectangle's appearance.

Exploring the GetHitItem() Function

When the user clicks in the application's window, the application must determine whether an embedded or linked item has been selected. This means comparing the coordinates of the mouse click with the coordinates of each item in the document. This task is handled by the GetHitItem() function. The function first gets a pointer to the document object and sets pHitItem, which will hold a pointer to the selected item (if any), to NULL:

CActiveXCont1Doc* pDoc = GetDocument();
CActiveXCont1CntrItem* pHitItem = NULL;

Next, the function gets the position of the first linked or embedded item:

POSITION position = pDoc->GetStartPosition();

Much like OnDraw(), GetHitItem() uses a while loop to cycle through all the items in the document. Inside this loop, the function first gets a pointer to the item at the current position:

CActiveXCont1CntrItem* pItem =
    (CActiveXCont1CntrItem*)pDoc->GetNextItem(position);

The program then checks whether the clicked point is contained in the item's rectangle:

BOOL hit = pItem->m_itemRect.PtInRect(point);

If PtInRect() returns TRUE, the function has located the item to be selected:

if (hit)
    pHitItem = pItem;

Finally, pHitItem (which can be NULL, indicating that no item was clicked) is returned from the function:

return pHitItem;

Exploring the SetSelectedItem() Function

When the user clicks in the application's window, the program must determine whether the user is selecting a new item or deselecting an already selected item. In some cases, the user might be doing both, such as when he clicks on a new item when another item is already activated in-place. The selection and deselection tasks are handled in the SetSelectedItem() function. First, this function gets a pointer to the document object:

CActiveXCont1Doc* pDoc = GetDocument();

The function then checks whether the user clicked on an empty part of the window (pItem == NULL) or is trying to select a new item when a previous item is still activated (m_pSelection != pItem):

if (pItem == NULL || m_pSelection != pItem)

If neither of these cases is true, the function simply sets m_pSelection to the newly selected item:

m_pSelection = pItem;

However, if the one of the two previous tests is true, there's a previously activated item that needs to be deactivated. Getting a pointer to the currently active item is a simple matter of calling the document object's GetInPlaceActiveItem() member function:

COleClientItem* pActiveItem =
    pDoc->GetInPlaceActiveItem(this);

Finally, if there is an active item, and that item isn't the item being currently selected, the program closes the active item by calling the item's Close() member function:

if (pActiveItem != NULL && pActiveItem != pItem)
    pActiveItem->Close();

The last step is to redraw the document's display:

Invalidate();Deleting ActiveX Objects

Currrently, as soon as you place a linked or embedded item into an ActiveXCont1 document, you're stuck with it. Short of starting a new document, there's no way to get rid of unwanted items. As you'll see in the following steps, enabling the user to delete items from an ActiveX document is easy to do. Just perform the following steps to add this capability to the ActiveXCont1 application.

ON THE WEB

http://www.quecorp.com/semfc The complete source code and executable file for this version of the ActiveXCont1 application is located in the Chap26\ActiveXCont1, Part 3 folder at this book's Web site.

1. Use Developer Studio's menu editor to add a Delete menu item to the application's Edit menu (found in the IDR_MAINFRAME menu), as shown in Figure 26.16. Give the new menu item the ID_EDIT_CLEAR menu ID.


FIG. 26.16

Give the new Delete menu the ID_EDIT_CLEAR menu ID.

2. Drag the new menu item so that it's positioned just below the Paste Special item, as shown in Figure 26.17.


FIG. 26.17

The Delete menu should be positioned as shown here.

3. Bring up ClassWizard by pressing Ctrl-W. When the ClassWizard window appears, select CActiveXCont1View in the Class Name box.

4. Use ClassWizard to add to the view class both COMMAND and UPDATE_COMMAND_UI message-response functions for the D elete menu item, accepting the suggested function names, as shown in Figure 26.18.


FIG. 26.18

To handle the new Delete menu item, you need COMMAND and UPDATE_COMMAND_UI functions.

5. Click the Edit Code button, and add the lines shown in Listing 26.16 to the OnEditClear() function. You'll examine these lines later in this chapter, in the section entitled "Exploring the OnEditClear() Function."

Listing 26.16 lst26_16.cpp Lines for the OnEditClear() Function

    if (m_pSelection != NULL)
    {
        // Delete the selected item.
        m_pSelection->Delete();
        m_pSelection = NULL;
        // Update all view windows associated
        // with this document.
        CActiveXCont1Doc* pDoc = GetDocument();
        pDoc->UpdateAllViews(NULL);
    }

6. Add the lines shown in Listing 26.17 to the OnUpdateEditClear() function.

These lines simply enable or disable the Delete menu item, depending on whether an embedded or linked item is currently selected.

Listing 26.17 lst16_17.cpp Lines for the OnUpdateEditClear() Function

    BOOL enable = TRUE;
    if (m_pSelection == NULL)
        enable = FALSE;
    pCmdUI->Enable(enable);

7. Bring up the application's IDR_MAINFRAME accelerator table, as shown in Figure 26.19.


FIG. 26.19

To add hotkeys to menu items, you must first display the appropriate accelerator table.

8. Add an accelerator key for the Delete command, as shown in Figure 26.20. Be sure to select the ID_EDIT_CLEAR and VK_DELETE IDs and to turn off all options in the Modifiers box.


FIG. 26.20

The VK_DELETE virtual-key ID is associated with the keyboard's Delete key.

You've now created part 3 of the ActiveXCont1 application. To compile the program, choose Developer Studio's Build, Build command. Then, select Build, Execute to run the program.

Using the Third Version of ActiveXCont1

Testing the ActiveXCont1 application's new capabilities won't take more than two minutes. When you run the program, link or embed a new item. Then, select the new item and press your keyboard's Delete key. Presto! The item is deleted from the document. You'll get the same results if you use the application's E dit, Delete command instead of pressing the Delete key.

Exploring the OnEditClear() Function

At this point, you should easily understand most of the new program lines and functions that you just added to the ActiveXCont1 application. However, the OnEditClear() function might require a little explanation.

When the user selects an embedded or linked item and clicks the Edit, Delete command or presses the Delete key, OnEditClear() springs into action. The function first checks whether an item is selected:

if (m_pSelection != NULL)

If so, deleting the item is as simple as calling the item's Delete() member function and setting the item's pointer to NULL:

m_pSelection->Delete();
m_pSelection = NULL;

The final task is to update all views that might be connected to the document that contained the deleted item:

CActiveXCont1Doc* pDoc = GetDocument();
pDoc->UpdateAllViews(NULL);

If no item is selected, OnEditClear() does nothing but return.