- Table of Contents
- Copyright
- About the Authors
- About the Contributors
- Acknowledgments
- Tell Us What You Think!
- Introduction
- How to Use This Book
- What You Need to Use This Book
- What's New in Visual C++ 6.0
- Contacting the Main Author
- Part I: Introduction
- Chapter 1. The Visual C++ 6.0 Environment
- Part II: MFC Programming
- Chapter 2. MFC Class Library Overview
- Chapter 3. MFC Message Handling Mechanism
- Chapter 4. The Document View Architecture
- Chapter 5. Creating and Using Dialog Boxes
- Chapter 6. Working with Device Contexts and GDI Objects
- Chapter 7. Creating and Using Property Sheets
- Chapter 8. Working with the File System
- Chapter 9. Using Serialization with File and Archive Objects
- Part III: Internet Programming with MFC
- Chapter 10. MFC and the Internet Server API (ISAPI)
- Chapter 11. The WinInet API
- Chapter 12. MFC HTML Support
- Part IV: Advanced Programming Topics
- Chapter 13. Using the Standard C++ Library
- Chapter 14. Error Detection and Exception Handling Techniques
- Chapter 15. Debugging and Profiling Strategies
- Chapter 16. Multithreading
- Chapter 17. Using Scripting and Other Tools to Automate the Visual C++ IDE
- Part V: Database Programming
- Chapter 18. Creating Custom AppWizards
- Chapter 19. Database Overview
- Chapter 20. ODBC Programming
- Chapter 21. MFC Database Classes
- Chapter 22. Using OLE DB
- Chapter 23. Programming with ADO
- Part VI: MFC Support for COM and ActiveX
- Chapter 24. Overview of COM and Active Technologies
- Chapter 25. Active Documents
- Chapter 26. Active Containers
- Just What Is an Active Document Container?
- Some Details About Active Document Containers
- The COM Interfaces
- Building an Active Document Container
- Summary
- Chapter 27. Active Servers
- Chapter 28. ActiveX Controls
- Part VII: Using the Active Template Library
- Chapter 29. ATL Architecture
- Chapter 30. Creating COM Objects Using ATL
- Chapter 31. Creating ActiveX Controls Using ATL
- Chapter 32. Using ATL to Create MTS and COM+ Components
- Part VIII: Finishing Touches
- Chapter 33. Adding Windows Help
- Part IX: Appendix
Building an Active Document Container
You can build an active document container in several ways. The most educational method is to use the native COM interfaces. Building a container this way takes longer but would greatly increase your understanding of the intricacies of COM. In this chapter, however, you will not build a container using this method.
Another way to build an active document container is to use the new Active Template Library (ATL). This library is an alternative approach to using the MFC Library. These libraries are fundamentally different from each other. ATL is based on the idea of a template. Templates are ways of implementing code without knowing what kind of data you are working with.
Suppose that you have a class that adds two integers together and another class that adds two floating-point numbers. The code for both these classes would be remarkably similar—perhaps something like c = a + b. The only difference between the two implementations is the type of data used in the calculation. This is the perfect situation for a template class. The code for the template looks the same (c = a + b), but the types of a, b, and c are not resolved until the template class is declared in the code.
If you are interested in learning more about templates, take a closer look at some of the MFC collection classes, the Standard Template Library, or ATL. The key benefit to this approach is the small size of programs built with ATL.
MFC is based on the idea of a hierarchical class library. Programmers are encouraged to build layers of classes that can inherit behaviors from their parent classes. This approach tends to make it very easy to implement functionality, because in most cases, you can just inherit it. Hierarchical classes tend to grow large, however, both in terms of the size of programs generated and the number of classes to learn and understand. The programming language SmallTalk is built on the idea of inheriting functionality and overriding the behaviors you want to change. The key characteristic of a SmallTalk project is that it takes a person new to SmallTalk a long time to learn the SmallTalk hierarchy. But someone who knows SmallTalk can implement new functionality quickly. SmallTalk programs also tend to be large. MFC programs share these characteristics.
For the purpose of this chapter, you will use MFC to build a container program. You will do this because you can inherit a great deal of active document container behavior without writing any code.
Active Document Container Support in MFC
In earlier versions of Visual C++ and MFC, OLE container support was provided through the COleClientItem class. This class still is used when containers want to support embedded OLE objects without offering support for containing active documents.
With the release of Visual C++ 6, the COleDocObjectItem class represents a contained active document object. Beginning with Visual C++ 6, COleDocument stores COleDocObjectItem instances as well as COleClientItem instances.
The COleDocObjectItem class is derived from COleClientItem and provides the following additional functions:
- GetActiveView does not return a pointer to an MFC CView instance. It returns a pointer to the currently active IOleDocumentView interface exposed by an embedded object. If no view is active, NULL is returned. Take care when using this function: It violates a basic COM development rule and fails to increment the reference count on the returned interface pointer. If you intend to use this pointer, you must increment the reference count manually by calling AddRef. Additionally, you must never call Release through the interface pointer unless you have called AddRef first.
- OnPreparePrinting is called by the MFC framework before the active document object is printed.
- OnPrint is called by the MFC framework when the active document object is to be printed.
- GetPageCount provides page count information about the document, including the initial page number and the total number of pages in the document.
- ExecCommand is a wrapper around the IOleCommandTarget::Exec method that enables commands to be routed to the document.
If you are using MFC AppWizard to create a container application, COleDocObjectItem or COleClientItem is used as a base class in your project. A class derived from COleDocObjectItem is added automatically to your project if you select the Active Document Container check box in AppWizard Step 3. If you don't select the Active document checkbox, COleClientItem is used as a base class.
The Pocket Project
As an example of an application that acts as an active document container, the CD-ROM that accompanies this book includes Pocket, an MFC-based application that is similar to the Office Binder application. Pocket also is similar to the MFCBind example program included with MSDN, with the following changes:
- Icons for embedded documents are displayed in a list view control.
- Printing is not supported.
- The MFCBind example has a number of partially implemented features, which tend to clutter the example.
The source code presented in this chapter highlights the portions of the Pocket project that deal with active documents. The entire Pocket project, including all of the source code, is included on the CD-ROM.
Creating the Pocket Project
The Pocket container application enables you to embed a number of active document objects into a single file. The Pocket application is similar in appearance to the MFCBind and Microsoft Office Binder applications. The client area is divided by a splitter bar. A list view on the left side of the client area contains a list of documents in the container. The view on the right side of the splitter displays one of the activated documents.
Each active document in the container is represented by an instance of CPocketCntrItem, which is derived from COleDocObjectItem. Each instance of CPocketCntrItem is represented by an icon in the list view. To activate a document, you click on the icon associated with the document you want to activate.
Using MFC AppWizard to Create the Project
The Pocket application was built using MFC AppWizard. The project is a single- document interface (SDI) application and uses the default settings for all options—except for the compound document support options on AppWizard Step 3. The following options were selected for the Pocket project:
- Container Compound Document Support. This selection causes AppWizard to create a project that supports embedding OLE documents.
- Active Document Container Selected. Enabling this check box causes AppWizard to create a container that supports active documents; specifically, AppWizard uses COleDocObjectItem instead of COleClientItem.
All other options were set to their default values.
The initial Pocket project created by the wizard consisted of the CPP and H files shown in Table 26.11.
Table 26.11. Generated Files and Their Descriptions
| CPP/Header Name | Purpose |
| CntrItem | These files contain the CPocketCntrItem class. This class is derived from COleDocObjectItem and supplies most of the active document support for the container. |
| MainFrm | These files contain the class derived from CFrameWnd. |
| These files contain the class derived from CWinApp. | |
| PocketDoc | These files contain the class derived from COleDocument. |
| PocketView | These files contain the class derived from CView. |
| StdAfx | Precompiled header support. |
In addition to the files created by AppWizard, the Pocket project includes the ItemListView.h and ItemListView.cpp files. These files implement the CItemListView class, which is derived from CListView and displays the documents currently embedded in the container.
The CItemListView class was added to the project using the Insert Class dialog box. To open the Insert Class dialog box, choose New Class from the Insert menu. The values from Table 26.12 were used with the Insert Class dialog box to create the CItemListView class.
Table 26.12. Generated Files and Their Descriptions
| Parameter | Value |
| Class type | MFC Class |
| Name | CItemListView |
| Base class | CListView |
After using the Insert Class dialog box with the parameters from Table 26.12, the ItemListView.cpp and ItemListview.h files are added automatically to the Pocket project.
Adding a Splitter to the Pocket Project
The Pocket application is an SDI application, with the main view divided vertically by a splitter. The CMainFrame class has a member variable named m_wndSplitter, which is an instance of CSplitterWnd. The code shown in Listing 26.1 initializes the splitter window and creates the two contained views.
Example 26.1. Creating a Splitter Window That Contains the Views
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs,
CCreateContext* pContext)
{
ASSERT(RUNTIME_CLASS(CPocketView)==pContext->m_pNewViewClass);
// Create splitter window, with list view on the left and main
// view on the right. The width is set to a default width that
// can be adjusted by the user.
int cxListWidth = ::GetSystemMetrics(SM_CXICON) * 2;
CRect rc;
GetClientRect(rc);
CSize sizView(rc.Width() - cxListWidth, 50);
CSize sizList(cxListWidth, 0);
// Create splitter
if(m_wndSplitter.CreateStatic(this, 1, 2) == FALSE)
{
TRACE0("Could not create splitter\n");
return FALSE;
}
// Create main view
BOOL fCreated = m_wndSplitter.CreateView(0, // row
1, // column
pContext->m_pNewViewClass,
sizView,
pContext);
if(fCreated == FALSE)
{
TRACE0("Could not create main view\n");
return FALSE;
}
// Create list view
fCreated = m_wndSplitter.CreateView(0,
0,
RUNTIME_CLASS(CItemListView),
sizList,
pContext);
if(fCreated == FALSE)
{
TRACE0("could not create list view\n");
return FALSE;
}
// Make the main view active
CView* pView;
// Downcast the CWnd* to a CView*
pView = reinterpret_cast<CView*>(m_wndSplitter.GetPane(0, 1));
SetActiveView(pView);
return TRUE;
}
The OnCreateClient function shown in Listing 26.1 is called by the MFC framework to create the client area of an MFC application. In the version implemented in the Pocket project, a splitter window is created, and then two views are inserted into the splitter panes. The first view created inside a splitter pane is the main view for the project. This view, CPocketView, is the view class that is associated with CPocketDoc, the document class used in Pocket. Next, an instance of CItemListView is created inside the other splitter pane. If both views are created successfully, the CPocketView instance is set as the active view.
Although the Pocket application includes two view classes, the CPocketDoc class is associated only with one view class—CPocketView. The CMainFrame class is responsible for caching pointers to both view classes so that other parts of the application have access to both views. Listing 26.2 shows the CMainFrame functions that provide access to pointers to each view.
Example 26.2. The CMainFrame Class Provides Access to View Pointers in the Pocket Application
CItemListView* CMainFrame::GetItemListView()
{
return static_cast<CItemListView*>(m_wndSplitter.GetPane(0, 0));
}
CPocketView* CMainFrame::GetPocketView()
{
return static_cast<CPocketView*>(m_wndSplitter.GetPane(0, 1));
}
Creating New Active Documents
You can embed new active documents into a Pocket document by choosing Add from Pocket's Section menu. Choosing this menu item displays the Insert Object dialog box, as shown in Figure 26.5.
Figure 26.5 The Insert Object dialog box.
After you select the type of active document object to be embedded, a document of the selected type opens in the main view, and an icon representing the new item is added to the list view.
The CPocketDoc class is responsible for handling the insertion of a new active document. Listing 26.3 shows the code used for this processing.
Example 26.3. Adding a New Active Document in the Pocket Project
void CPocketDoc::OnSectionAdd()
{
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
ASSERT_POINTER(pFrame, CMainFrame);
CPocketView* pView = (CPocketView*)pFrame->GetPocketView();
ASSERT_POINTER(pView, CPocketView);
CPocketCntrItem* pItem = NULL;
pView->BeginWaitCursor();
// Use the standard OLE Insert Object dialog box.
COleInsertDialog dlg;
if (dlg.DoModal(COleInsertDialog::DocObjectsOnly) != IDOK)
{
pView->EndWaitCursor();
return;
}
try{
// Create a new instance of the container item to track
// the new object, then pass the new instance to the OLE
// insert dialog, which will create the object.
pItem = new CPocketCntrItem(this);
ASSERT_VALID(pItem);
if (!dlg.CreateItem(pItem))
{
delete pItem;
pView->EndWaitCursor();
return;
}
ASSERT_VALID(pItem);
// If there's already an active item, deactivate it before
// making the new item active.
COleClientItem* pActiveItem = GetInPlaceActiveItem(pView);
if(pActiveItem)
pActiveItem->Deactivate();
pItem->Activate(OLEIVERB_SHOW, pView);
ASSERT_VALID(pItem);
// Set the selection to the most recently inserted item
pView->m_pSelection = (CPocketCntrItem*)pItem;
UpdateAllViews(NULL);
}
catch(CException* pe)
{
if(pItem != NULL)
{
ASSERT_VALID(pItem);
pItem->Delete();
CMainFrame *pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
pFrame->GetItemListView()->RemoveItem(pItem);
}
AfxMessageBox(IDP_FAILED_TO_CREATE);
// Not using the MFC exception macros, so the exception
// must be cleaned up manually.
pe->Delete();
}
pView->EndWaitCursor();
}
In Listing 26.3, the OnSectionAdd function begins by using the COleInsertDialog class to collect the type of OLE object to be inserted. The list of available objects is restricted to active documents by passing the COleInsertDialog::DocObjectsOnly value as a parameter when calling DoModal.
If you select an item to be embedded, an instance of CPocketCntrItem is created. As discussed earlier in this chapter, the CPocketCntrItem class is derived from COleDocObjectItem. Every active document object that is embedded into a Pocket document is represented by one instance of CPocketCntrItem. After the new instance of CPocketCntrItem is initialized, it's activated in the main view. Before the new item is activated, any currently activated item is deactivated.
When the instance of CPocketCntrItem is created, the MFC framework calls an undocumented function named FinishCreate. This function, shown in Listing 26.4, notifies you of the results after the framework and the operating system attempt to create the new item. If the SCODE indicates a failure, the item could not be inserted. If the SCODE indicates success, the item was inserted, and the item is added to the list view.
Example 26.4. Implementing the Undocumented FinishCreate Function
BOOL CPocketCntrItem::FinishCreate(SCODE sc)
{
// Undocumented virtual function, see MFCBind example.
// If we receive a failed result, the item couldn't be
// created. Otherwise, we delegate to the base class, and
// call our own processing function.
ASSERT(SUCCEEDED(sc));
BOOL fReturn = FALSE;
if(SUCCEEDED(sc))
{
fReturn = COleDocObjectItem::FinishCreate(sc);
if(fReturn)
AddItemToListView();
}
else
{
AfxMessageBox(_T("Could not create new section"));
}
return fReturn;
}
The FinishCreate function in Listing 26.4 calls AddItemToListView to handle the work required to place an icon and description for the new item in the list view. Listing 26.5 shows the AddItemToListView function.
Example 26.5. Adding a New Item into Pocket
void CPocketCntrItem::AddItemToListView()
{
// Uses the ATL/MFC string conversion macros.
USES_CONVERSION;
ASSERT_POINTER(m_lpObject, IOleObject);
if (!m_lpObject)
return;
// Request the short form of the class type
CString strType;
GetUserType(USERCLASSTYPE_SHORT, strType);
// Get the Class ID, and store it as strClassId
CLSID clsid;
GetClassID(&clsid);
LPOLESTR lpOleStr = NULL;
StringFromCLSID(clsid, &lpOleStr);
CString strClassId(OLE2T(lpOleStr));
CoTaskMemFree(lpOleStr);
// Call base class function to pull icon from the registry.
HICON hIcon = GetIconFromRegistry();
if(hIcon != NULL)
{
// Walk through the framework, and get the pointer to
// the main frame window.
CWnd* pWndMain = AfxGetApp()->m_pMainWnd;
ASSERT_POINTER(pWndMain, CWnd);
CMainFrame* pFrame = DYNAMIC_DOWNCAST(CMainFrame, pWndMain);
ASSERT_POINTER(pFrame, CMainFrame);
// Get the pointer to the list view.
CItemListView *pListView = pFrame->GetItemListView();
if(pListView == NULL)
return;
pListView->AddItem(this,
hIcon,
strType,
strClassId);
}
}
The AddItemToListView function collects information about the new active document item, including
- The short version of the OLE class type. This name is used as a label to identify the item in the list view.
- A CString containing the CLSID for the new item. Converting this information into a CString requires several steps, including a call to CoTaskMemFree to release the memory allocated for the original string.
- A handle to the icon associated with the OLE class. The icon is retrieved by calling GetIconFromRegistry, a function that is part of the COleClientItem base class.
The preceding items, as well as a pointer to this instance of CPocketCntrItem, are passed to the list view's AddItem function, which Listing 26.6 shows.
Example 26.6. Adding a New Item to the List View
BOOL CItemListView::AddItem(COleClientItem* pItem,
HICON hIcon, CString strType,
CString strClassId)
{
ASSERT_POINTER(pItem, COleClientItem);
ASSERT(hIcon);
ASSERT(strType.IsEmpty() == FALSE);
ASSERT(strClassId.IsEmpty() == FALSE);
CPocketItem* pListItem = new CPocketItem(pItem,
hIcon,
strType,
strClassId);
if(!pListItem) return FALSE;
CListCtrl& ctrl = GetListCtrl();
int nItemCount = ctrl.GetItemCount();
int ndxImage = m_imgList.Add(hIcon);
ASSERT(ndxImage != -1);
UINT fuMask = LVIF_TEXT|LVIF_PARAM|LVIF_IMAGE;
int ndxNewItem = ctrl.InsertItem(fuMask,
nItemCount,
strType,
0,
0,
ndxImage,
(long)pListItem);
ASSERT(ndxNewItem == nItemCount);
return TRUE;
}
The AddItem function in Listing 26.6 creates an instance of a CPocketItem structure that serves as a wrapper for the parameter data. The icon associated with the new item is added to the list view's image list, and then the new item is added to the list view control. The type name is used as a label for the item, and the CPocketItem pointer is stored in the list view for later use.
Switching Between Active Documents
You can switch between documents by clicking on an icon in the list view. The currently active document is replaced by the document represented by the icon you selected. The code responsible for handling this work is the OnClick function in the CItemListView class, which is provided in Listing 26.7.
Example 26.7. Selecting an Active Document
void CItemListView::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;
ASSERT_POINTER(pFrame, CMainFrame);
COleDocument *pDoc = (COleDocument*)pFrame->GetActiveDocument();
ASSERT_POINTER(pDoc, COleDocument);
CPocketView *pView = (CPocketView*)pFrame->GetPocketView();
ASSERT_POINTER(pView, CPocketView);
// This pointer may be NULL if no item is currently
// activated in-place.
COleClientItem *pActiveItem = NULL;
pActiveItem = pDoc->GetInPlaceActiveItem(pView);
ASSERT_NULL_OR_POINTER(pActiveItem, COleClientItem);
CListCtrl& ctrl = GetListCtrl();
int nSelected = ctrl.GetNextItem(-1, LVNI_SELECTED);
if(nSelected == -1) return;
DWORD dwData = ctrl.GetItemData(nSelected);
CPocketItem* pItem = (CPocketItem*)dwData;
ASSERT_POINTER(pItem, CPocketItem);
if(pItem->m_pOleItem == pActiveItem)
{
CWnd* pWnd = pActiveItem->GetInPlaceWindow();
ASSERT_NULL_OR_POINTER(pWnd, CWnd);
if(pWnd)
pWnd->SetFocus();
pView->m_pSelection = pActiveItem;
return;
}
else if(pActiveItem)
{
pActiveItem->Deactivate();
}
pActiveItem = pItem->m_pOleItem;
pActiveItem->Activate(OLEIVERB_SHOW, pView);
CWnd* pWnd = pActiveItem->GetInPlaceWindow()->SetFocus();
ASSERT_NULL_OR_POINTER(pWnd, CWnd);
if(pWnd)
pWnd->SetFocus();
pView->m_pSelection = pActiveItem;
*pResult = 0;
}
The OnClick function begins by retrieving pointers to the application's frame, document, and view classes. Next, the CPocketItem pointer that is currently active is compared to the CPocketItem pointer for the item selected by the user. If the pointers are equal, the user has reselected the same item, and input focus is returned to the main view. If the items differ, the currently active item (if any) is deactivated, and the newly selected item is activated.
Destroying an Active Document
To delete an item from a Pocket document, choose Remove from the Section menu, and the currently selected item is removed from the document. The OnSectionRemove function in the CPocketDoc class is responsible for carrying out this work (see Listing 26.8).
Example 26.8. Handling the Section Remove Menu Command
void CPocketDoc::OnSectionRemove()
{
CMainFrame* pFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;
ASSERT_VALID(pFrame);
CPocketView* pView = (CPocketView*)pFrame->GetPocketView();
ASSERT_VALID(pView);
COleClientItem* pItem = GetPrimarySelectedItem(pView);
if(pItem)
{
CItemListView* pItemView = pFrame->GetItemListView();
ASSERT_VALID(pItemView);
pItem->Delete();
pItemView->RemoveItem(pItem);
}
else
{
AfxMessageBox(_T("Select a section to be deleted."));
}
}
The OnSectionRemove function in Listing 26.8 begins by retrieving a COleClientItem pointer that represents the currently active item in the main view. The Delete function is called through this pointer, which causes the underlying OLE object and the associated C++ instance to be released. Next, the item is removed from the list view by calling the CItemListView::RemoveItem function, which Listing 26.9 shows.
Example 26.9. Removing an Item from Pocket
BOOL CItemListView::RemoveItem(COleClientItem *pRemove)
{
BOOL fReturn = FALSE;
CMainFrame *pFrame = (CMainFrame*)AfxGetApp()->m_pMainWnd;
ASSERT_POINTER(pFrame, CMainFrame);
CPocketView *pView = (CPocketView*)pFrame->GetPocketView();
ASSERT_POINTER(pView, CPocketView);
CPocketItem* pItem = NULL;
CListCtrl& ctrl = GetListCtrl();
int nItemCount = ctrl.GetItemCount();
for(int n = 0; n < nItemCount; n++)
{
DWORD dwData = ctrl.GetItemData(n);
CPocketItem* pItem = (CPocketItem*)dwData;
ASSERT_POINTER(pItem, CPocketItem);
if(pItem->m_pOleItem == pRemove)
{
ctrl.DeleteItem(n);
break;
}
}
if(ctrl.GetItemCount() == 0)
return TRUE;
// Activate one of the remaining items - we'll just pick the
// first item in the list.
DWORD dwData = ctrl.GetItemData(0);
pItem = (CPocketItem*)dwData;
ASSERT_POINTER(pItem, CPocketItem);
ASSERT_POINTER(pItem->m_pOleItem, COleClientItem);
pItem->m_pOleItem->Activate(OLEIVERB_SHOW, pView);
CWnd* pWnd = pItem->m_pOleItem->GetInPlaceWindow();
ASSERT_NULL_OR_POINTER(pWnd, CWnd);
if(pWnd)
pWnd->SetFocus();
pView->m_pSelection = pItem->m_pOleItem;
ctrl.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
VERIFY(ctrl.Arrange(LVA_DEFAULT));
return TRUE;
}
The RemoveItem function has two sections. In the first section, the item to be removed is searched for, and if found, it is removed from the list view. In the second part of the function, another item from the list view is selected and made active.
Using the Pocket Application
To add a new item to a Pocket document, choose Add from the Section menu, and choose one of the displayed active document types. To remove an item, choose Remove from the Section menu. Figure 26.6 shows the Pocket container with several embedded documents.
Figure 26.6 The Pocket application with several embedded documents.
Summary | Next Section

Account Sign In
View your cart