The Visual Programmer Puts ActiveX Document Objects Through Their Paces

Joshua Trupin

Joshua Trupin is a software developer specializing in C/C++ and Visual Basic apps for Windows. He can be reached at 75120.657@compuserve.com or geeknet@ix.netcom.com.

Click to open or copy the MFCX project files.

Click to open or copy the SCRIBX project files.

Click to open or copy the SHELLEXP project files.

OLE has come a long way over the past few years, especially with regard to embedding technologies. At first, the best you could do was display an icon representing a server within a container and activate it sepaately by double-clicking on it. The next stage, a bit better, involved the static display of a server's contents within a rectangle. More recently, we've advanced to in-place editing of those embedded servers and to OLE control/ActiveX control interfaces.

ActiveXÔ documents, also known as OLE Document Objects or doc objects, are the next logical step in this progression. If you're familiar with the MFC document/view design, you know that it specifies how programs create frames, views, and documents. Within the frame, the view window acts as a virtual display of the data contained in associated documents. Currently, an OLE 2 container/server interaction takes place in a rectangle granted to the server within the container's document. A doc object transaction takes place one level higher at the container application's frame level. A doc object server takes over the entire client area of the container application, can adjust the frame's menus, and appears as if it is the application. To put it another way, OLE documents describe a method for programs to write out data in a storage-independent way. ActiveX documents, on the other hand, are mechanisms that make an object container-independent and able to run in any frame.

If you have MicrosoftÒ Office for WindowsÒ 95, you can see how this works right now. Doc objects were originally designed for use with the Office Binder application (see Figure 1), which lets you collect disparate Office-based documents. As you click on the document icon, Office Binder adjusts the entire view area on the right, including toolbars and menus. To the user, it looks like working in a single application, since the frame itself never changes or goes away.

Figure 1 The Office Binder

The doc objects specification was first designed as a proprietary group of interfaces, used only in the context of the Microsoft Office Binder. (ISVs could sign up for the Office-compatible program, as outlined on the MSDN CD, to get specifications allowing them to Office-enable their own programs.) As the benefits of extending this concept became clear, the OLE doc objects specification was officially rolled out last December as part of Microsoft's ActiveX Technologies (formerly code-named Sweeper) client-side extensions. Microsoft Internet Explorer 3.0 will feature ActiveX document support, as will the Shell Explorer to ship with Nashville. ActiveX documents will soon permeate the entire Windows operating system line, so it's important that you become familiar with their workings and implementation. In the pages ahead I'll describe how to convert an existing MFC-based OLE server application into a doc object server, as well as how to provide doc object container support in your Visual BasicÒ 4.0 application. (My implementation of the MFC-based Document Object server is based on an early beta of MFC 4.2. MFC 4.2 should be available soon after this article appears in print.) Although Visual Basic 4.0 can't create ActiveX Document objects, Visual Basic 5.0, expected out at the end of this year, will provide this new feature (among other stuff too cool to mention here).

ActiveX Document OLE Interfaces

If you want your application to be an ActiveX document, able to work within an ActiveX doc object container, you must do four things:

That slightly glazed-over look in your eyes can only mean one thing—you thought you were going to be able to avoid OLE for just a bit longer. A chart or two might make these specifications slightly less daunting. Figure 2 shows a slightly more readable form of the information in the list above, including the newly defined ActiveX document interfaces. If you look at this chart and feel like you're drowning in interfaces, don't worry. In the land of MFC, you don't have to implement everything by hand.

Figure 2 ActiveX Document Interfaces

As OLE has evolved, the document/view system used in a standard MFC application has been seamlessly integrated with coordinating OLE interfaces. This is all done quietly within the base class definitions, so you never even know about it unless you browse through the header files. If you compare the basic output of MFC 1.0's AppWizard and today's version, the program might look about the same. However, it will support a broad array of OLE embedding functions, all of which you get for free.

Not only do you get stuff without asking for it, you may also discover that your application already has a dreaded interface implemented, buried somewhere in the depths of the MFC base classes. This makes it clear why you should install MFC source code along with the Microsoft Developer's Studio, even though it takes up some more disk space.

Take a closer look at Figure 2. On the left, you'll see what you have to implement to become an ActiveX document container. We don't need to know about any of that right now, since we're concentrating on writing embeddable applications, not frames. You'll get all the stuff on the left when you open up Internet Explorer 3.0 or Microsoft Office Binder (or the Visual Basic sample later in this article). It's the stuff on the right, the server specification, that interests us.

Let's concentrate on everyone's favorite sample, the MFC Scribble application. When you implement a standard document/view MFC application like Scribble, your document class gets an appropriate name like CScribbleDoc. This class is derived from COleServerDoc, which is several layers away from the simple base class CDocument. As it turns out, these layers contain a lot of OLE functionality. Most of the interfaces you need to implement an ActiveX document server are already encapsulated within your CScribbleDoc (see Figure 3). In fact, without touching a line of code, six of the ten server-side interfaces already exist—you only have to implement the four new ones (IOleDocument, IOleDocumentView, IPrint, and IOleCommandTarget).

Figure 3 Where the Interfaces Are

Obviously, each of these four remaining interfaces is important for an ActiveX document server's health. Before we look at how you can add these interfaces to an existing project, we need to investigate just what they do. These four interfaces are all defined in DOCOBJ.H; a simplified version (based on the DOCOBJ.IDL interface definition language file) is shown in Figure 4.

Figure 4 Simplified DOCOBJ.H


 //----------------------------------------------------------------------------
// OLE Document Object interfaces
// Copyright 1995 - 1996 Microsoft Corporation. All Rights Reserved.
//----------------------------------------------------------------------------

interface IOleDocument;
interface IOleDocumentSite;
interface IOleDocumentView;
interface IEnumOleDocumentViews;
interface IContinueCallback;
interface IPrint;
interface IOleCommandTarget;

interface IOleDocument : IUnknown
{
    typedef [unique] IOleDocument *LPOLEDOCUMENT;
    
    typedef enum {
        DOCMISC_CANCREATEMULTIPLEVIEWS    = 1,
        DOCMISC_SUPPORTCOMPLEXRECTANGLES  = 2,
        DOCMISC_CANTOPENEDIT              = 4, // fails the 
                                               // IOleDocumentView::Open method
        DOCMISC_NOFILESUPPORT             = 8, // does not support read/
                                               // writing to a file    
    } DOCMISC;
     

    HRESULT CreateView(
        [in, unique] IOleInPlaceSite *pIPSite,
        [in, unique] IStream *pstm,
        [in] DWORD dwReserved,
        [out] IOleDocumentView **ppView);

    HRESULT    GetDocMiscStatus(
        [out] DWORD *pdwStatus);

    HRESULT EnumViews(
        [out] IEnumOleDocumentViews **ppEnum,
        [out] IOleDocumentView **ppView);
}

//---------------------------------------------------------------------------

interface IOleDocumentSite : IUnknown
{
    typedef [unique] IOleDocumentSite *LPOLEDOCUMENTSITE;

    HRESULT ActivateMe(
            [in] IOleDocumentView *pViewToActivate);
}

//----------------------------------------------------------------------------

interface IOleDocumentView : IUnknown
{
    typedef [unique] IOleDocumentView *LPOLEDOCUMENTVIEW;

    HRESULT SetInPlaceSite(
           [in, unique] IOleInPlaceSite *pIPSite);

    HRESULT GetInPlaceSite(
           [out] IOleInPlaceSite **ppIPSite);

    HRESULT GetDocument(
        [out] IUnknown **ppunk);

    [input_sync] 
    HRESULT SetRect(
        [in] LPRECT prcView);

    HRESULT GetRect(
        [out] LPRECT prcView);

    [input_sync] 
    HRESULT SetRectComplex(
        [in, unique] LPRECT prcView,
        [in, unique] LPRECT prcHScroll,
        [in, unique] LPRECT prcVScroll,
        [in, unique] LPRECT prcSizeBox);

    HRESULT Show(
        [in] BOOL fShow);

    HRESULT UIActivate(
        [in] BOOL fUIActivate);

    HRESULT Open(void);

    HRESULT CloseView(DWORD dwReserved);

    HRESULT SaveViewState(
        [in] LPSTREAM pstm);

    HRESULT ApplyViewState(
        [in] LPSTREAM pstm);

    HRESULT Clone(
           [in] IOleInPlaceSite *pIPSiteNew,
        [out] IOleDocumentView **ppViewNew);
}

//----------------------------------------------------------------------------

interface IEnumOleDocumentViews : IUnknown
{ 

    typedef [unique] IEnumOleDocumentViews *LPENUMOLEDOCUMENTVIEWS;

    [local]
    HRESULT __stdcall Next(
        [in] ULONG cViews,
        [out] IOleDocumentView **rgpView,
        [out] ULONG *pcFetched);

    [call_as(Next)]
    HRESULT __stdcall RemoteNext(
        [in] ULONG cViews,
        [out, size_is(cViews), length_is(*pcFetched)]
        IOleDocumentView **rgpView,
        [out] ULONG *pcFetched);
                  
    HRESULT Skip(
           [in] ULONG cViews);
        
    HRESULT Reset();

    HRESULT Clone(
           [out] IEnumOleDocumentViews **ppEnum);
}

//----------------------------------------------------------------------------

interface IContinueCallback : IUnknown
{
    typedef [unique] IContinueCallback *LPCONTINUECALLBACK;

    HRESULT FContinue();

    HRESULT FContinuePrinting(
        [in] LONG nCntPrinted,
        [in] LONG nCurPage,
        [in, unique] wchar_t * pwszPrintStatus);
}

//----------------------------------------------------------------------------

interface IPrint : IUnknown
{
    typedef [unique] IPrint *LPPRINT;

    typedef enum
    {
        PRINTFLAG_MAYBOTHERUSER           = 1,
        PRINTFLAG_PROMPTUSER              = 2,
        PRINTFLAG_USERMAYCHANGEPRINTER    = 4,
        PRINTFLAG_RECOMPOSETODEVICE       = 8,
        PRINTFLAG_DONTACTUALLYPRINT       = 16,
        PRINTFLAG_FORCEPROPERTIES         = 32,
        PRINTFLAG_PRINTTOFILE             = 64

    } PRINTFLAG;

    typedef struct  tagPAGERANGE
    {
        LONG nFromPage;
        LONG nToPage;
    } PAGERANGE;

    typedef struct  tagPAGESET
    {
        ULONG    cbStruct;
        BOOL    fOddPages;
        BOOL    fEvenPages;
        ULONG    cPageRange;
        [size_is(cPageRange)]
        PAGERANGE rgPages[];
    } PAGESET;

    cpp_quote("#define PAGESET_TOLASTPAGE    ((WORD)(-1L))")

    HRESULT SetInitialPageNum(
        [in] LONG nFirstPage);

    HRESULT    GetPageInfo(
        [out] LONG *pnFirstPage,
        [out] LONG *pcPages);

    [local]
    HRESULT __stdcall Print(
        [in] DWORD grfFlags,
        [in, out] DVTARGETDEVICE **pptd,
        [in, out] PAGESET ** ppPageSet,
        [in, out, unique] STGMEDIUM * pstgmOptions, 
        [in] IContinueCallback *pcallback,
        [in]  LONG nFirstPage,
        [out] LONG *pcPagesPrinted,
        [out] LONG *pnLastPage);

    [call_as(Print)]
    HRESULT __stdcall RemotePrint(
        [in] DWORD grfFlags,
        [in, out] DVTARGETDEVICE **pptd,
        [in, out] PAGESET ** pppageset,
        [in, out, unique] RemSTGMEDIUM * pstgmOptions, 
        [in] IContinueCallback * pcallback,
        [in]  LONG nFirstPage,
        [out] LONG * pcPagesPrinted,
        [out] LONG * pnLastPage);
}

//----------------------------------------------------------------------------

interface IOleCommandTarget : IUnknown
{
    typedef [unique] IOleCommandTarget *LPOLECOMMANDTARGET;

    typedef enum 
    {
        OLECMDF_SUPPORTED      = 0x00000001,
        OLECMDF_ENABLED        = 0x00000002,
        OLECMDF_LATCHED        = 0x00000004,
        OLECMDF_NINCHED        = 0x00000008,
    } OLECMDF;

    
    typedef struct _tagOLECMD {
        ULONG    cmdID;
        DWORD    cmdf;
    } OLECMD;

    typedef struct _tagOLECMDTEXT{
        DWORD cmdtextf;    
        ULONG cwActual;
        ULONG cwBuf;    /* size in wide chars of the buffer for text */
        [size_is(cwBuf)]
        wchar_t rgwz[]; /* Array into which callee writes the text */
    } OLECMDTEXT;

    typedef enum 
    {
        OLECMDTEXTF_NONE                = 0,
        OLECMDTEXTF_NAME                = 1,
        OLECMDTEXTF_STATUS              = 2,
    } OLECMDTEXTF;

    typedef enum 
    {
        OLECMDEXECOPT_DODEFAULT         = 0,
        OLECMDEXECOPT_PROMPTUSER        = 1,
        OLECMDEXECOPT_DONTPROMPTUSER    = 2,
        OLECMDEXECOPT_SHOWHELP          = 3
    } OLECMDEXECOPT;

    typedef enum {
        OLECMDID_OPEN                   = 1,
        OLECMDID_NEW                    = 2,
        OLECMDID_SAVE                   = 3,
        OLECMDID_SAVEAS                 = 4,
        OLECMDID_SAVECOPYAS             = 5,
        OLECMDID_PRINT                  = 6,
        OLECMDID_PRINTPREVIEW           = 7,
        OLECMDID_PAGESETUP              = 8,
        OLECMDID_SPELL                  = 9,
        OLECMDID_PROPERTIES             = 10,
        OLECMDID_CUT                    = 11,
        OLECMDID_COPY                   = 12,
        OLECMDID_PASTE                  = 13,
        OLECMDID_PASTESPECIAL           = 14,
        OLECMDID_UNDO                   = 15,
        OLECMDID_REDO                   = 16,
        OLECMDID_SELECTALL              = 17,
        OLECMDID_CLEARSELECTION         = 18,
        OLECMDID_ZOOM                   = 19,
        OLECMDID_GETZOOMRANGE           = 20
    } OLECMDID;

  //----------------------------------------------------------------------------
  // error codes

  #define OLECMDERR_E_FIRST           (OLE_E_LAST+1)
  #define OLECMDERR_E_NOTSUPPORTED    (OLECMDERR_E_FIRST)
  #define OLECMDERR_E_DISABLED        (OLECMDERR_E_FIRST+1)
  #define OLECMDERR_E_NOHELP          (OLECMDERR_E_FIRST+2)
  #define OLECMDERR_E_CANCELED        (OLECMDERR_E_FIRST+3)
  #define OLECMDERR_E_UNKNOWNGROUP    (OLECMDERR_E_FIRST+4)

    [input_sync] 
    HRESULT QueryStatus(
        [in, unique] const GUID *pguidCmdGroup,
        [in] ULONG cCmds,
        [size_is(cCmds)]
        [in, out] OLECMD prgCmds[],
        [in, out, unique] OLECMDTEXT *pCmdText);


    HRESULT Exec(
        [in, unique] const GUID *pguidCmdGroup,
        [in] DWORD nCmdID,
        [in] DWORD nCmdexecopt,
        [in, unique] VARIANTARG *pvaIn,
        [in, out, unique] VARIANTARG *pvaOut);
}

IOleDocument

By implementing the IOleDocument interface, an object indicates that it can act as an ActiveX document. An ActiveX document container uses a server's IOleDocument to create new server views, enumerate these views, and retrieve MiscStatus bits relevant to the ActiveX document. (What we call a view is really an IOleDocumentView, an interface I'll discuss shortly.) IOleDocument implementations therefore need three functions: CreateView, EnumViews, and GetDocMiscStatus. CreateView creates and allows (optional) initialization of a new view. GetDocMiscStatus returns the value of the object's MiscStatus bits. These are also saved in the doc objects registry key of this object. EnumViews returns an enumeration of the document's views, or a pointer to its single view. IEnumOleDocumentViews, the returned enumeration, is a standard enumerator interface typed for IOleDocumentView. (An enumerator interface is defined with the operators Next, Skip, Reset, and Clone, and allows its user to walk through a list of document view objects.)

IOleDocumentView

The IOleDocumentView interface refers to a single instance of an ActiveX document view, a concept closely related to an MFC view. The interface provides everything needed for a container to perform site management—sizing, activating, and resetting an ActiveX document's bounding area. To do this, IOleDocumentView implements in-place site functions, as well as others like GetRect, Show, UIActivate, and Clone (see Figure 5). When implemented within an MFC-based document/view application, information sent to the IOleDocumentView interface by the host will be reflected back to the MFC view class, allowing it to adjust itself accurately. For example, IOleDocumentView is notified when you resize the container.

Figure 5 IOleDocumentView Functions

SetInPlaceSite

Associates a view site object (provided by the container) with this view.

GetInPlaceSite

Returns a pointer to this view's associated site.

GetDocument

Returns a pointer to this view's associated document.

SetRect

Sets the viewpoint coordinates for this view.

GetRect

Retrieves the viewpoint coordinates for this view.

SetRectComplex

Lets the container specify not only the simple view rectangle but also the area occupied by the view's scrollbars and sizing box. (Complex rectangles don't have to be supported by a view; the container can find this out through the IOleDocument::GetMiscStatus function, which will include the DOCMISC_SUPPORTCOMPLEXRECTANGLES flag on return if this support exists.)

Show

Indicates whether to show or hide this view.

UIActivate

Indicates whether to UI activate or deactivate this view.

Open

Asks to display the view in a separate window. (This ability can be turned off with the DOCMISC_CANTOPENEDIT MiscStatus bit.)

CloseView

Asks this view to shut itself down.

SaveViewState

Writes the view's state information to an IStream.

ApplyViewState

Asks a view to restore its state to a previously saved setting in an IStream.

Clone

Asks this view to duplicate itself with the same view context, but in a different viewport.


IOleCommandTarget

IOleCommandTarget is an optional interface that lets ActiveX document container and servers send each other commands, without resorting to tricks such as assigning fixed menu IDs. Instead, this mechanism lets a caller query it to find out which commands it supports (from a standard enumerated list) as well as letting an object execute all supported commands. Two functions are provided: QueryStatus returns an object's support for a particular command; Exec asks an object to execute a supported command, indicated by ID.

A caller passes in a list of OLECMD structures, each of which contains a command ID and a place to put informational flags on return. The implementor fills in each structure with one or more OR'd flags from the list shown in Figure 6. An OLECMDTEXT list works the same way, receiving a localized name and status string for each command requested. This allows a hosting program to change its status bar when a command is invoked.

Figure 6 OLECMD Flags

Flag

Description

OLECMDF_SUPPORTED

The command is supported by this object.

OLECMDF_ENABLED

The command is available and enabled.

OLECMDF_LATCHED

The command is an on-off toggle and is currently on.

OLECMDF_NINCHED

The command is an on-off toggle but the state cannot be determined because the attribute of this command is found in both on and off states in the relevant selection. This state corresponds to an "indeterminate" state of a 3-state checkbox, for example.


Using the Exec function, you can call a specific command by ID (for instance, OLECMDID_OPEN is equivalent to File Open), and provide incoming and outgoing arguments (although these are usually NULL). There are several options defined within the OLECMDEXECOPT enumeration that indicate how the object should execute this command (see Figure 7).

Figure 7 OLECMDEXECOPT Options

Flag

Description

OLECMDEXECOPT_PROMPTUSER

Execute the command after taking user input.

OLECMDEXECOPT_DONTPROMPTUSER

Execute the command without prompting the user (for example, clicking on the Print toolbar button causes the document to be immediately printed without requir-ing user input).

OLECMDEXECOPT_DODEFAULT

Caller is not sure whether the user should be prompted or not.

OLECMDEXECOPT_SHOWHELP

Object should show help for the corre-sponding command and not execute.


A number of common commands have already been defined in the IOleCommandTarget interface for use with the Exec function. These mostly deal with the File and Edit menus (see Figure 8).

Figure 8 IOleCommandTarget Commands for Exec

Identifier

Description

OLECMDID_OPEN

File Open

OLECMDID_NEW

File New

OLECMDID_SAVE

File Save

OLECMDID_SAVEAS

File Save As

OLECMDID_SAVECOPYAS

File Save Copy As

OLECMDID_PRINT

File Print

OLECMDID_PRINTPREVIEW

File Print Preview

OLECMDID_PAGESETUP

File Page Setup

OLECMDID_SPELL

Tools Spelling

OLECMDID_PROPERTIES

File Properties

OLECMDID_CUT

Edit Cut

OLECMDID_COPY

Edit Copy

OLECMDID_PASTE

Edit Paste

OLECMDID_PASTESPECIAL

Edit Paste Special

OLECMDID_UNDO

Edit Undo

OLECMDID_REDO

Edit Redo

OLECMDID_SELECTALL

Edit Select All

OLECMDID_CLEARSELECTION

Edit Clear

OLECMDID_ZOOM

Query zoom value / display zoom dialog / set zoom value

OLECMDID_GETZOOMRANGE

Retrieves zoom range applicable to View Zoom


IPrint

Like IOleCommandTarget, IPrint is an optional addition that lets an object support programmatic printing. The three functions provided by IPrint tell the server object to print (Print), retrieve print-related information (GetPageInfo), and set the initial page number of a print job (SetInitialPageNum). When the IPrint::Print function is called, it takes as input any of a group of flags indicating what defaults are set for the print job (see Figure 9).

Figure 9 IPrint::Print Flags

Value

Description

PRINTFLAG_MAYBOTHERUSER*

Specifies that user interaction is permitted. If not set, the printing process must not require any user input.

PRINTFLAG_PROMPTUSER

Prompt the user for job-specific printing options using the normal print dialog for the object. Support for this option is required. (PRINTFLAG_MAYBOTHERUSER must, of course, be specified as well.)

PRINTFLAG_USERMAYCHANGEPRINTER

The user may change the target printer. (Again, this is only valid if PRINTFLAG_

PROMPTUSER is also specified.)

PRINTFLAG_RECOMPOSETODEVICE

The print job should recompose itself for the indicated target device. Otherwise, it should attempt to use its existing compositional-device association.

PRINTFLAG_DONTACTUALLYPRINT

Do a printing dry run, with any prompting as indicated but without actually sending a print job.

PRINTFLAG_PRINTTOFILE

Print to a file. (The portname field of DVTARGETDEVICE, an argument to the in on the Print function, should indicate the filename.)

* Indicates that this identifier name has replaced the DoughnutHoleSize property in Microsoft Excel as the author's favorite.


Adding ActiveX Document OLE Interfaces To An MFC Project

Microsoft beat me to the punch by including a sample called BINDSCRB in Visual C++Ò 4.1. This sample includes the implementation of the ActiveX document extensions, and shows how to write a program that uses them. Visual C++ 4.2, slated for release shortly after this article goes to press, will "naturalize" this process, migrating these interface definitions and their class wrappers to standard MFC, making it far easier to design a full-blown ActiveX document server.

The BINDSCRB sample includes nine CPP implementation files: BINDDCMT.CPP, BINDDOC.CPP, BINDIPFW.CPP, BINDITEM.CPP, BINDTARG.CPP, BINDVIEW.CPP, MFCBIND.CPP, OLEOBJCT.CPP, and PRINT.CPP. Additionally, four header files are used the implement ActiveX documents: BINDDOC.H, BINDIPFW.H, BINDITEM.H, and MFCBIND.H. (These names may change before the final release of Visual C++ 4.2.) To make it easier to use these files, they are all wrapped up in AFXBIND.H, which can be inserted into an STDAFX.H file.

These files introduce three new classes to MFC: CDocObjectIPFrameWnd (COleIPFrameWnd with ActiveX document extensions), CDocObjectServerDoc (based on COleServerDoc), and CDocObjectServerItem (from COleServerItem). In each case, these classes extend the main classes used to build MFC documents.

Look at the files in the Scribble application (as defined as a Visual C++ 4.0 sample). Three major classes—CInPlaceFrame, CScribbleDoc, and CScribbleItem—are defined as follows:


 class CInPlaceFrame: public COleIPFrameWnd
class CScribbleDoc: public COleServerDoc
class CScribbleItem: public COleServerItem

Since the new CDocObjectXxx classes are derived from the Scribble base classes directly, they can be popped in the Scribble class definitions as replacements. Turn Scribble into an ActiveX document server by changing the lines above to look like these:


 class CInPlaceFrame : public CDocObjectIPFrameWnd 
                                //(see file IPFRAME.H)
class CScribbleDoc: public CDocObjectServerDoc 
                                //(see file SCRIBDOC.H)
class CScribbleItem: public CDocObjectServerItem 
                                //(see file SCRIBITM.H)

This will soon be an option within the Visual C++ AppWizard, but it's a by-hand process right now. Also, your program must be tagged in the registry as supporting the ActiveX documents specification. MFC already takes care of registration settings every time a program executes, so this isn't a huge burden. In the CScribbleApp::InitInstance routine, adding one call to a predefined function takes care of this for you.


     MfcBinderUpdateRegistry(
             pDocTemplate,
             OAT_INPLACE_SERVER);

The MFC base class overrides handle the details of the ActiveX implementation. The source code for updated portions of Scribble is shown in Figure 10.

Figure 10 Changes in Scribble

Ipframe.h


 // ipframe.h : interface of the CInPlaceFrame class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

class CInPlaceFrame : public CDocObjectIPFrameWnd
{
    DECLARE_DYNCREATE(CInPlaceFrame)
public:
    CInPlaceFrame();

// Attributes
public:

// Operations
public:

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CInPlaceFrame)
    public:
    virtual BOOL OnCreateControlBars(CFrameWnd* pWndFrame, CFrameWnd* pWndDoc);
    //}}AFX_VIRTUAL

// Implementation
public:
    virtual ~CInPlaceFrame();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:
    CToolBar    m_wndToolBar;
    COleResizeBar   m_wndResizeBar;
    COleDropTarget m_dropTarget;

// Generated message map functions
protected:
    //{{AFX_MSG(CInPlaceFrame)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
        // NOTE - the ClassWizard will add and remove member functions here.
        //    DO NOT EDIT what you see in these blocks of generated code!
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

Ipframe.cpp


 // ipframe.cpp : implementation of the CInPlaceFrame class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include "scribble.h"

#include "ipframe.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

//BINDER:
//    An MFC application's in-place frame window is normally a subclass
//  of COleIPFrameWnd.  To be Binder-compatible, we steal code from
//  CDocObjectIPFrameWnd instead.
//BINDER_END

/////////////////////////////////////////////////////////////////////////////
// CInPlaceFrame

IMPLEMENT_DYNCREATE(CInPlaceFrame, CDocObjectIPFrameWnd)

BEGIN_MESSAGE_MAP(CInPlaceFrame, CDocObjectIPFrameWnd)
    //{{AFX_MSG_MAP(CInPlaceFrame)
    ON_WM_CREATE()
    //}}AFX_MSG_MAP
    // Global help commands
    ON_COMMAND(ID_HELP_INDEX, CDocObjectIPFrameWnd::OnHelpIndex)
    ON_COMMAND(ID_HELP_USING, CDocObjectIPFrameWnd::OnHelpUsing)
    ON_COMMAND(ID_HELP, CDocObjectIPFrameWnd::OnHelp)
    ON_COMMAND(ID_DEFAULT_HELP, CDocObjectIPFrameWnd::OnHelpIndex)
    ON_COMMAND(ID_CONTEXT_HELP, CDocObjectIPFrameWnd::OnContextHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// arrays of IDs used to initialize control bars

// toolbar buttons - IDs are command buttons
static UINT BASED_CODE buttons[] =
{
    // same order as in the bitmap 'itoolbar.bmp'
    ID_EDIT_CUT,
    ID_EDIT_COPY,
    ID_EDIT_PASTE,
        ID_SEPARATOR,
    ID_PEN_THICK_OR_THIN,
        ID_SEPARATOR,
    ID_APP_ABOUT,
    ID_CONTEXT_HELP,
};

/////////////////////////////////////////////////////////////////////////////
// CInPlaceFrame construction/destruction

CInPlaceFrame::CInPlaceFrame()
{
}

CInPlaceFrame::~CInPlaceFrame()
{
}

int CInPlaceFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDocObjectIPFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // CResizeBar implements in-place resizing.
    if (!m_wndResizeBar.Create(this))
    {
        TRACE0("Failed to create resize bar\n");
        return -1;      // fail to create
    }

    // By default, it is a good idea to register a drop-target that does
    //  nothing with your frame window.  This prevents drops from
    //  "falling through" to a container that supports drag-drop.
    m_dropTarget.Register(this);

    return 0;
}

// OnCreateControlBars is called by the framework to create control bars on the
//  container application's windows.  pWndFrame is the top level frame window of
//  the container and is always non-NULL.  pWndDoc is the doc level frame window
//  and will be NULL when the container is an SDI application.  A server
//  application can place MFC control bars on either window.
BOOL CInPlaceFrame::OnCreateControlBars(CFrameWnd* pWndFrame, 
                                        CFrameWnd* pWndDoc)
{
    // Create toolbar on client's frame window
    if (!m_wndToolBar.Create(pWndFrame) ||
        !m_wndToolBar.LoadBitmap(IDR_SCRIBTYPE_SRVR_IP) ||
        !m_wndToolBar.SetButtons(buttons, sizeof(buttons)/sizeof(UINT)))
    {
        TRACE0("Failed to create toolbar\n");
        return FALSE;
    }
    // Set owner to this window, so messages are delivered to correct app
    m_wndToolBar.SetOwner(this);

    // TODO: Delete these three lines if you don't want the toolbar to
    //  be dockable
    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    pWndFrame->EnableDocking(CBRS_ALIGN_ANY);
    pWndFrame->DockControlBar(&m_wndToolBar);

    // TODO: Remove this if you don't want tool tips
    m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
        CBRS_TOOLTIPS | CBRS_FLYBY);

    return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CInPlaceFrame diagnostics

#ifdef _DEBUG
void CInPlaceFrame::AssertValid() const
{
    CDocObjectIPFrameWnd::AssertValid();
}

void CInPlaceFrame::Dump(CDumpContext& dc) const
{
    CDocObjectIPFrameWnd::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CInPlaceFrame commands

Scribdoc.h


 // scribdoc.h : interface of the CScribDoc class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
//

// Forward declaration of data structure class
class CStroke;

class CScribItem;

class CScribDoc : public CDocObjectServerDoc
{
protected: // create from serialization only
    CScribDoc();
    DECLARE_DYNCREATE(CScribDoc)

// Attributes
protected:
    // The document keeps track of the current pen width on
    // behalf of all views. We'd like the user interface of
    // Scribble to be such that if the user chooses the Draw
    // Thick Line command, it will apply to all views, not just
    // the view that currently has the focus.

    UINT            m_nPenWidth;        // current user-selected pen width
    BOOL            m_bThickPen;        // TRUE if current pen is thick
    UINT            m_nThinWidth;
    UINT            m_nThickWidth;
    CPen            m_penCur;           // pen created according to
                                        // user-selected pen style (width)
public:
    CTypedPtrList<CObList,CStroke*>     m_strokeList;   
    CPen*           GetCurrentPen() { return &m_penCur; }

protected:
    CSize           m_sizeDoc;
public:
    CSize GetDocSize() { return m_sizeDoc; }
    COleServerItem* OnGetEmbeddedItem();
    CScribItem* GetEmbeddedItem()
        { return (CScribItem*)COleServerDoc::GetEmbeddedItem(); }

// Operations
public:
    CStroke* NewStroke();

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CScribDoc)
    public:
    virtual BOOL OnNewDocument();
    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual void DeleteContents();
    //}}AFX_VIRTUAL

// Implementation
protected:
    void ReplacePen();
    void OnSetItemRects(LPCRECT lpPosRect, LPCRECT lpClipRect);

public:
    virtual ~CScribDoc();
    virtual void Serialize(CArchive& ar);   // overridden for document i/o
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif
protected:
    void            InitDocument();

// Generated message map functions
protected:
    //{{AFX_MSG(CScribDoc)
    afx_msg void OnEditClearAll();
    afx_msg void OnPenThickOrThin();
    afx_msg void OnUpdateEditClearAll(CCmdUI* pCmdUI);
    afx_msg void OnUpdatePenThickOrThin(CCmdUI* pCmdUI);
    afx_msg void OnPenWidths();
    afx_msg void OnEditCopy();
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
// class CStroke
//
// A stroke is a series of connected points in the scribble drawing.
// A scribble document may have multiple strokes.

class CStroke : public CObject
{
public:
    CStroke(UINT nPenWidth);

protected:
    CStroke();
    DECLARE_SERIAL(CStroke)

// Attributes
    UINT                   m_nPenWidth;  // one pen width applies to entire 
                                         // stroke
    CArray<CPoint,CPoint>  m_pointArray; // series of connected points
    CRect               m_rectBounding;  // smallest rect that surrounds all
                                         // of the points in the stroke
                                         // measured in MM_LOENGLISH units
                                         // (0.01 inches, with Y-axis inverted)
public:
    CRect& GetBoundingRect() { return m_rectBounding; }

// Operations
public:
    BOOL DrawStroke(CDC* pDC);
    void FinishStroke();

public:
    virtual void Serialize(CArchive& ar);
};

Scribdoc.cpp


 // scribdoc.cpp : implementation of the CScribDoc class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
//

#include "stdafx.h"
#include "scribble.h"

#include "scribdoc.h"
#include "pendlg.h"
#include "scribvw.h"
#include "scribitm.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CScribDoc

IMPLEMENT_DYNCREATE(CScribDoc, CDocObjectServerDoc)

BEGIN_MESSAGE_MAP(CScribDoc, CDocObjectServerDoc)
    //{{AFX_MSG_MAP(CScribDoc)
    ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll)
    ON_COMMAND(ID_PEN_THICK_OR_THIN, OnPenThickOrThin)
    ON_UPDATE_COMMAND_UI(ID_EDIT_CLEAR_ALL, OnUpdateEditClearAll)
    ON_UPDATE_COMMAND_UI(ID_PEN_THICK_OR_THIN, OnUpdatePenThickOrThin)
    ON_COMMAND(ID_PEN_WIDTHS, OnPenWidths)
    ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CScribDoc construction/destruction

CScribDoc::CScribDoc()
{
    m_sizeDoc = CSize(200, 200);
}

CScribDoc::~CScribDoc()
{
}

BOOL CScribDoc::OnNewDocument()
{
    if (!CDocObjectServerDoc::OnNewDocument())
        return FALSE;
    InitDocument();
    return TRUE;
}


/////////////////////////////////////////////////////////////////////////////
// CScribDoc serialization

void CScribDoc::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << m_sizeDoc;
    }
    else
    {
        ar >> m_sizeDoc;
    }
    m_strokeList.Serialize(ar);
}

/////////////////////////////////////////////////////////////////////////////
// CScribDoc diagnostics

#ifdef _DEBUG
void CScribDoc::AssertValid() const
{
    CDocObjectServerDoc::AssertValid();
}

void CScribDoc::Dump(CDumpContext& dc) const
{
    CDocObjectServerDoc::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CScribDoc commands

BOOL CScribDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
    if (!CDocObjectServerDoc::OnOpenDocument(lpszPathName))
        return FALSE;
    InitDocument();
    return TRUE;
}

void CScribDoc::DeleteContents()
{
    while (!m_strokeList.IsEmpty())
    {
        delete m_strokeList.RemoveHead();
    }
    CDocObjectServerDoc::DeleteContents();
}

void CScribDoc::InitDocument()
{
    m_bThickPen = FALSE;
    m_nThinWidth = 2;   // default thin pen is 2 pixels wide
    m_nThickWidth = 5;  // default thick pen is 5 pixels wide
    ReplacePen();       // initialze pen according to current width

    // default document size is 2 x 2 inches
    m_sizeDoc = CSize(200,200);

}

CStroke* CScribDoc::NewStroke()
{
    CStroke* pStrokeItem = new CStroke(m_nPenWidth);
    m_strokeList.AddTail(pStrokeItem);
    SetModifiedFlag();  // Mark the document as having been modified, for
                        // purposes of confirming File Close.
    return pStrokeItem;
}




/////////////////////////////////////////////////////////////////////////////
// CStroke

IMPLEMENT_SERIAL(CStroke, CObject, 2)

CStroke::CStroke()
{
    // This empty constructor should be used by serialization only
}

CStroke::CStroke(UINT nPenWidth)
{
    m_nPenWidth = nPenWidth;
    m_rectBounding.SetRectEmpty();
}

void CStroke::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << m_rectBounding;
        ar << (WORD)m_nPenWidth;
        m_pointArray.Serialize(ar);
    }
    else
    {
        ar >> m_rectBounding;
        WORD w;
        ar >> w;
        m_nPenWidth = w;
        m_pointArray.Serialize(ar);
    }
}

BOOL CStroke::DrawStroke(CDC* pDC)
{
    CPen penStroke;
    if (!penStroke.CreatePen(PS_SOLID, m_nPenWidth, RGB(0,0,0)))
        return FALSE;
    CPen* pOldPen = pDC->SelectObject(&penStroke);
    pDC->MoveTo(m_pointArray[0]);
    for (int i=1; i < m_pointArray.GetSize(); i++)
    {
        pDC->LineTo(m_pointArray[i]);
    }

    pDC->SelectObject(pOldPen);
    return TRUE;
}

void CScribDoc::OnEditClearAll()
{
    DeleteContents();
    SetModifiedFlag();  // Mark the document as having been modified, for
                        // purposes of confirming File Close.
    UpdateAllViews(NULL);
}

void CScribDoc::OnPenThickOrThin()
{
    // Toggle the state of the pen between thin or thick.
    m_bThickPen = !m_bThickPen;

    // Change the current pen to reflect the new user-specified width.
    ReplacePen();
}


void CScribDoc::ReplacePen()
{
    m_nPenWidth = m_bThickPen? m_nThickWidth : m_nThinWidth;

    // Change the current pen to reflect the new user-specified width.
    m_penCur.DeleteObject();
    m_penCur.CreatePen(PS_SOLID, m_nPenWidth, RGB(0,0,0)); // solid black
}

void CScribDoc::OnUpdateEditClearAll(CCmdUI* pCmdUI)
{
    // Enable the command user interface object (menu item or tool bar
    // button) if the document is non-empty, i.e., has at least one stroke.
    pCmdUI->Enable(!m_strokeList.IsEmpty());
}


void CScribDoc::OnUpdatePenThickOrThin(CCmdUI* pCmdUI)
{
    // Add check mark to Draw Thick Line menu item, if the current
    // pen width is "thick".
    pCmdUI->SetCheck(m_bThickPen);
}


void CScribDoc::OnPenWidths()
{
    CPenWidthsDlg dlg;
    // Initialize dialog data
    dlg.m_nThinWidth = m_nThinWidth;
    dlg.m_nThickWidth = m_nThickWidth;

    // Invoke the dialog box
    if (dlg.DoModal() == IDOK)
    {
        // retrieve the dialog data
        m_nThinWidth = dlg.m_nThinWidth;
        m_nThickWidth = dlg.m_nThickWidth;

        // Update the pen that is used by views when drawing new strokes,
        // to reflect the new pen width definitions for "thick" and "thin".
        ReplacePen();
    }
}

void CStroke::FinishStroke()
{
    // Calculate the bounding rectangle.  It's needed for smart
    // repainting.

    if (m_pointArray.GetSize()==0)
    {
        m_rectBounding.SetRectEmpty();
        return;
    }
    CPoint pt = m_pointArray[0];
    m_rectBounding = CRect(pt.x, pt.y, pt.x, pt.y);

    for (int i=1; i < m_pointArray.GetSize(); i++)
    {
        // If the point lies outside of the accumulated bounding
        // rectangle, then inflate the bounding rect to include it.
        pt = m_pointArray[i];
        m_rectBounding.left     = min(m_rectBounding.left, pt.x);
        m_rectBounding.right    = max(m_rectBounding.right, pt.x);
        m_rectBounding.top      = max(m_rectBounding.top, pt.y);
        m_rectBounding.bottom   = min(m_rectBounding.bottom, pt.y);
    }

    // Add the pen width to the bounding rectangle.  This is necessary
    // to account for the width of the stroke when invalidating
    // the screen.
    m_rectBounding.InflateRect(CSize(m_nPenWidth, -(int)m_nPenWidth));
    return;
}

COleServerItem* CScribDoc::OnGetEmbeddedItem()
{
    // OnGetEmbeddedItem is called by the framework to get the COleServerItem
    //  that is associated with the document.  It is only called when necessary.

    CScribItem* pItem = new CScribItem(this);
    ASSERT_VALID(pItem);
    return pItem;
}

void CScribDoc::OnSetItemRects(LPCRECT lpPosRect, LPCRECT lpClipRect)
{
    // call base class to change the size of the window
    CDocObjectServerDoc::OnSetItemRects(lpPosRect, lpClipRect);

    // notify first view that scroll info should change
    POSITION pos = GetFirstViewPosition();
    CScribView* pView = (CScribView*)GetNextView(pos);
    pView->SetScrollInfo();
}

void CScribDoc::OnEditCopy()
{
    CScribItem* pItem = GetEmbeddedItem();
    pItem->CopyToClipboard(TRUE);
}

Scribitm.h


 // scribitm.h : interface of the CScribItem class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

class CScribItem : public CDocObjectServerItem
{
    DECLARE_DYNAMIC(CScribItem)

// Constructors
public:
    CScribItem(CScribDoc* pContainerDoc);

// Attributes
    CScribDoc* GetDocument() const
        { return (CScribDoc*)COleServerItem::GetDocument(); }

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CScribItem)
    public:
    virtual BOOL OnDraw(CDC* pDC, CSize& rSize);
    virtual BOOL OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize);
    //}}AFX_VIRTUAL

// Implementation
public:
    ~CScribItem();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:
    virtual void Serialize(CArchive& ar);   // overridden for document i/o
};

/////////////////////////////////////////////////////////////////////////////

Scribitm.cpp


 // scribitm.cpp : implementation of the CScribItem class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include "scribble.h"

#include "scribdoc.h"
#include "scribitm.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CScribItem implementation

IMPLEMENT_DYNAMIC(CScribItem, CDocObjectServerItem)

CScribItem::CScribItem(CScribDoc* pContainerDoc)
    : CDocObjectServerItem(pContainerDoc, TRUE)
{
    // TODO: add one-time construction code here
    //  (eg, adding additional clipboard formats to the item's data source)
}

CScribItem::~CScribItem()
{
    // TODO: add cleanup code here
}

void CScribItem::Serialize(CArchive& ar)
{
    // CScribItem::Serialize will be called by the framework if
    //  the item is copied to the clipboard.  This can happen automatically
    //  through the OLE callback OnGetClipboardData.  A good default for
    //  the embedded item is simply to delegate to the document's Serialize
    //  function.  If you support links, then you will want to serialize
    //  just a portion of the document.

    if (!IsLinkedItem())
    {
        CScribDoc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);
        pDoc->Serialize(ar);
    }
}

BOOL CScribItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
    // This implementation of CScribItem::OnGetExtent only handles
    //  the "content" aspect indicated by DVASPECT_CONTENT.

    if (dwDrawAspect != DVASPECT_CONTENT)
        return CDocObjectServerItem::OnGetExtent(dwDrawAspect, rSize);

    // CScribItem::OnGetExtent is called to get the extent in
    //  HIMETRIC units of the entire item.  The default implementation
    //  here simply returns a hard-coded number of units.

    CScribDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    rSize = pDoc->GetDocSize();
    CClientDC dc(NULL);

    // set a MM_LOENGLISH based on logical inches
    //  (we can't use MM_LOENGLISH because MM_LOENGLISH uses physical inches)
    dc.SetMapMode(MM_ANISOTROPIC);
    dc.SetViewportExt(dc.GetDeviceCaps(LOGPIXELSX), 
                      dc.GetDeviceCaps(LOGPIXELSY));
    dc.SetWindowExt(100, -100);
    dc.LPtoHIMETRIC(&rSize);

    return TRUE;
}

BOOL CScribItem::OnDraw(CDC* pDC, CSize& /* rSize */)
{
    CScribDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    pDC->SetMapMode(MM_ANISOTROPIC);
    CSize sizeDoc = pDoc->GetDocSize();
    sizeDoc.cy = -sizeDoc.cy;
    pDC->SetWindowOrg(0,0);
    pDC->SetWindowExt(sizeDoc);

    CTypedPtrList<CObList,CStroke*>& strokeList = pDoc->m_strokeList;
    POSITION pos = strokeList.GetHeadPosition();
    while (pos != NULL)
    {
        strokeList.GetNext(pos)->DrawStroke(pDC);
    }

    return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CScribItem diagnostics

#ifdef _DEBUG
void CScribItem::AssertValid() const
{
    CDocObjectServerItem::AssertValid();
}

void CScribItem::Dump(CDumpContext& dc) const
{
    CDocObjectServerItem::Dump(dc);
}
#endif

/////////////////////////////////////////////////////////////////////////////

Scribvw.h


 // scribvw.h : interface of the CScribView class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

class CScribView : public CScrollView
{
protected: // create from serialization only
       CScribView();
       DECLARE_DYNCREATE(CScribView)

// Attributes
public:
       CScribDoc* GetDocument();

protected:
       CStroke*    m_pStrokeCur;   // the stroke in progress
       CPoint      m_ptPrev;       // the last mouse pt in the stroke in progress

// Operations
public:
       void SetScrollInfo();       // resync scroll sizes

// Overrides
       // ClassWizard generated virtual function overrides
       //{{AFX_VIRTUAL(CScribView)
       public:
       virtual void OnDraw(CDC* pDC);  // overridden to draw this view
       virtual void OnInitialUpdate();
       virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL);
       protected:
       virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
       virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
       virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
       virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
       virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);
       //}}AFX_VIRTUAL

// Implementation
public:
       void PrintTitlePage(CDC* pDC, CPrintInfo* pInfo);
       void PrintPageHeader(CDC* pDC, CPrintInfo* pInfo, CString& strHeader);
       virtual ~CScribView();
#ifdef _DEBUG
       virtual void AssertValid() const;
       virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
       //{{AFX_MSG(CScribView)
       afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
       afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
       afx_msg void OnMouseMove(UINT nFlags, CPoint point);
       afx_msg void OnSize(UINT nType, int cx, int cy);
       //}}AFX_MSG
       DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in scribvw.cpp
inline CScribDoc* CScribView::GetDocument()
   { return (CScribDoc*) m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////

Scribvw.cpp


 // scribvw.cpp : implementation of the CScribView class
//
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include "scribble.h"

#include "scribdoc.h"
#include "scribvw.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CScribView

IMPLEMENT_DYNCREATE(CScribView, CScrollView)


BEGIN_MESSAGE_MAP(CScribView, CScrollView)
       //{{AFX_MSG_MAP(CScribView)
       ON_WM_LBUTTONDOWN()
       ON_WM_LBUTTONUP()
       ON_WM_MOUSEMOVE()
       ON_WM_SIZE()
       //}}AFX_MSG_MAP

       // Standard printing commands
       ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
       ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CScribView construction/destruction

CScribView::CScribView()
{
       SetScrollSizes(MM_TEXT, CSize(0, 0));
}

CScribView::~CScribView()
{
}



/////////////////////////////////////////////////////////////////////////////
// CScribView drawing

void CScribView::OnDraw(CDC* pDC)
{
       CScribDoc* pDoc = GetDocument();
       ASSERT_VALID(pDoc);


       // Get the invalidated rectangle of the view, or in the case
       // of printing, the clipping region of the printer dc.
       CRect rectClip;
       CRect rectStroke;
       pDC->GetClipBox(&rectClip);
       pDC->LPtoDP(&rectClip);
       rectClip.InflateRect(1, 1); // avoid rounding to nothing

       // Note: CScrollView::OnPaint() will have already adjusted the
       // viewport origin before calling OnDraw(), to reflect the
       // currently scrolled position.

       // The view delegates the drawing of individual strokes to
       // CStroke::DrawStroke().
       CTypedPtrList<CObList,CStroke*>& strokeList = pDoc->m_strokeList;
       POSITION pos = strokeList.GetHeadPosition();
       while (pos != NULL)
       {
              CStroke* pStroke = strokeList.GetNext(pos);
              rectStroke = pStroke->GetBoundingRect();
              pDC->LPtoDP(&rectStroke);
              rectStroke.InflateRect(1, 1); // avoid rounding to nothing
              if (!rectStroke.IntersectRect(&rectStroke, &rectClip))
                     continue;
              pStroke->DrawStroke(pDC);
       }
}

/////////////////////////////////////////////////////////////////////////////
// CScribView printing

BOOL CScribView::OnPreparePrinting(CPrintInfo* pInfo)
{
       pInfo->SetMaxPage(2);   // the document is two pages long:
                               // the first page is the title page
                               // the second is the drawing






       BOOL bRet = DoPreparePrinting(pInfo);       // default preparation
       pInfo->m_nNumPreviewPages = 2;  // Preview 2 pages at a time
       // Set this value after calling DoPreparePrinting to override
       // value read from .INI file
       return bRet;
}

void CScribView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
       // TODO: add extra initialization before printing
}

void CScribView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
       // TODO: add cleanup after printing
}


/////////////////////////////////////////////////////////////////////////////
// CScribView diagnostics

#ifdef _DEBUG
void CScribView::AssertValid() const
{
       CScrollView::AssertValid();
}

void CScribView::Dump(CDumpContext& dc) const
{
       CScrollView::Dump(dc);
}

CScribDoc* CScribView::GetDocument() // non-debug version is inline
{
       ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CScribDoc)));
       return (CScribDoc*) m_pDocument;
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CScribView message handlers

void CScribView::OnLButtonDown(UINT, CPoint point)
{  
    // When the user presses the mouse button, she may be
       // starting a new stroke, or selecting or de-selecting a stroke.

       // CScrollView changes the viewport origin and mapping mode.
       // It's necessary to convert the point from device coordinates
       // to logical coordinates, such as are stored in the document.
       CClientDC dc(this);
       OnPrepareDC(&dc);
       dc.DPtoLP(&point);

       m_pStrokeCur = GetDocument()->NewStroke();
       // Add first point to the new stroke
       m_pStrokeCur->m_pointArray.Add(point);

       SetCapture();       // Capture the mouse until button up.
       m_ptPrev = point;   // Serves as the MoveTo() anchor point for the
                           // LineTo() the next point, as the user drags the
                           // mouse.

       return;

}


void CScribView::OnLButtonUp(UINT, CPoint point)
{
       // Mouse button up is interesting in the Scribble application
       // only if the user is currently drawing a new stroke by dragging
       // the captured mouse.

       if (GetCapture() != this)
              return; // If this window (view) didn't capture the mouse,
                      // then the user isn't drawing in this window.

       CScribDoc* pDoc = GetDocument();

-----------------------------------------------------

       // We can't interpret the hint, so assume that anything might
       // have been updated.
       Invalidate(TRUE);
       return;
}

void CScribView::OnInitialUpdate()
{
       SetScrollInfo();
       CScrollView::OnInitialUpdate();
}

void CScribView::SetScrollInfo()
{
       CClientDC dc(NULL);
       OnPrepareDC(&dc);
       CSize sizeDoc = GetDocument()->GetDocSize();
       dc.LPtoDP(&sizeDoc);
       SetScrollSizes(MM_TEXT, sizeDoc);
}

void CScribView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
       if (pInfo->m_nCurPage == 1)  // page no. 1 is the title page
       {
              PrintTitlePage(pDC, pInfo);
              return; // nothing else to print on page 1 but the page title
       }
       CString strHeader = GetDocument()->GetTitle();

       PrintPageHeader(pDC, pInfo, strHeader);
       // PrintPageHeader() subtracts out from the pInfo->m_rectDraw the
       // amount of the page used for the header.

       pDC->SetWindowOrg(pInfo->m_rectDraw.left,-pInfo->m_rectDraw.top);

       // Now print the rest of the page
       OnDraw(pDC);
}

void CScribView::PrintTitlePage(CDC* pDC, CPrintInfo* pInfo)
{
       // Prepare a font size for displaying the file name
       LOGFONT logFont;
       memset(&logFont, 0, sizeof(LOGFONT));
       logFont.lfHeight = 75;  //  3/4th inch high in MM_LOENGLISH
                                                 // (1/100th inch)
       CFont font;
       CFont* pOldFont = NULL;
       if (font.CreateFontIndirect(&logFont))
              pOldFont = pDC->SelectObject(&font);

       // Get the file name, to be displayed on title page
       CString strPageTitle = GetDocument()->GetTitle();

       // Display the file name 1 inch below top of the page,
       // centered horizontally
       pDC->SetTextAlign(TA_CENTER);
       pDC->TextOut(pInfo->m_rectDraw.right/2, -100, strPageTitle);

       if (pOldFont != NULL)
              pDC->SelectObject(pOldFont);
}

void CScribView::PrintPageHeader(CDC* pDC, CPrintInfo* pInfo,
       CString& strHeader)
{
       // Print a page header consisting of the name of
       // the document and a horizontal line
       pDC->TextOut(0,-25, strHeader);  // 1/4 inch down

       // Draw a line across the page, below the header
       TEXTMETRIC textMetric;
       pDC->GetTextMetrics(&textMetric);
       int y = -35 - textMetric.tmHeight;         // line 1/10th inch below text
       pDC->MoveTo(0, y);                         // from left margin
       pDC->LineTo(pInfo->m_rectDraw.right, y);   // to right margin

       // Subtract out from the drawing rectange the space used by the header.
       y -= 25;    // space 1/4 inch below (top of) line
       pInfo->m_rectDraw.top += y;
}

void CScribView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
       CScribDoc* pDoc = GetDocument();
       CScrollView::OnPrepareDC(pDC, pInfo);

       pDC->SetMapMode(MM_ANISOTROPIC);
       CSize sizeDoc = pDoc->GetDocSize();
       sizeDoc.cy = -sizeDoc.cy;
       pDC->SetWindowExt(sizeDoc);

       // Binder objects don't scale

       int xLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSX);
       int yLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSY);

       long xExt = (long)(sizeDoc.cx * xLogPixPerInch) / 100;
       long yExt = (long)(sizeDoc.cy * yLogPixPerInch) / 100;

       pDC->SetViewportExt((int)xExt, (int)-yExt);
}

void CScribView::OnSize(UINT nType, int cx, int cy)
{
       SetScrollInfo();       // ensure that scroll info is up-to-date
       CScrollView::OnSize(nType, cx, cy);
}

Getting There In Visual Basic

As I've mentioned before, ActiveX documents are a feature of Microsoft Internet Explorer 3.0, and will be supported in the Shell Explorer in future versions of Microsoft Windows. You can't directly automate Internet Explorer from Visual Basic, but you can get to the important part: the browser window. The ActiveX SDK (currently available at http://www.microsoft.com/intdev/sdk/) contains a file called SHDOCVW.DLL, which is really an OLE control. Installing the ActiveX SDK automatically registers this file. It shows up in the Visual Basic 4.0 (32-bit version) custom control list as "Microsoft Internet and Shell Controls."

This DLL provides two controls visible in the Visual Basic toolbar: ShellExplorer and ShItemOC (shell item). The ShellExplorer control interests us most because it's an OLE control wrapper around the browser window included in Internet Explorer 3.0. Unlike the HTML control in the Internet Control Pack (which interprets HTML input and shows it in a screen), this is an actual COM wrapper around Internet Explorer 3.0. It exposes all the functionality of that product, from Visual Basic Script to ActiveX documents. If the hosting program has a menu, this control also performs proper negotiation. When you're looking at a Web site, you'll get the Internet Explorer 3.0 menu, allowing you to view a site's source—or refresh it—without any work on your part.

I'm mentioning this control because it gives you all you need to write a Visual Basic program that's also an ActiveX document container. This is logical because Internet Explorer 3.0 is an ActiveX document container and you're getting all its functionality. The simplest way to get this working is to open a new project in Visual Basic 4.0 (this is 32-bit only) and add the "Microsoft Internet and Shell controls" choice from the Tools/Custom Controls menu (or by right-clicking the toolbox). Two new control icons appear. Add a ShellExplorer to your project's form (make it as big as possible), then add three buttons and a text box to the form. Finally, add a menu with anything on it—a simple File/Exit will suffice. Now that you have a form set up, you only have to enter three lines of code to create a simple, yet highly functional, Web browser and ActiveX document shell.


 Private Sub Command1_Click()
    ShellExplorer1.Location = Text1.Text
End Sub

Private Sub Command2_Click()
    ShellExplorer1.Application.GoBack
End Sub

Private Sub Command3_Click()
    ShellExplorer1.Application.GoForward
End Sub

The first button sets the browser window's location to the string in the text box. The second and third are the Back/Forward controls you'd have in a full-blown browser. When the program first runs, the ShellExplorer displays the contents of your system, similar to the My Computer icon in Windows 95. When you enter a new Web address in the text box and click the first button, the site displays automatically, including any necessary dial-ups (see Figure 11). Clicking on an HTML hyperlink loads the new page without any intervention on your part.

Figure 11 Viewing a Web Site.

You can also get to a file by clicking on it in the My Computer screen. However, in my beta release of Internet Explorer 3.0, this method loads the file into the associated program in a separate window, even if the program is an ActiveX document server. If you type a filename into the textbox and click the first button, the file ShellExplorer control Loads the file as a working ActiveX document. Try it with a Microsoft Word or Microsoft Excel file, or even better, check out a file saved with the modified Scribble application—such as MSJSCRIB.OSC (see Figure 12).

Figure 12 Viewing a Scribble document.

Wrapup

ActiveX documents began life as Office Compatible objects, and this holds true today. To be hosted in the Microsoft Office Binder, an application must support the ActiveX document interfaces and OLE structured storage. But what started as a proprietary interface, unavailable to most users, has become an emerging standard in advanced OLE technology.

ActiveX document interfaces will be the basis of future Windows shells, starting with the Shell Explorer in Nashville. You can start to develop and test both documents and viewers with tools available today—Microsoft Visual C++ 4.0, Visual Basic 4.0, and the free ActiveX SDK. By making a few very simple changes in existing MFC-based projects, you can dramatically extend your OLE functionality, and by writing a short program in Visual Basic, you can get a jump on the next release of the Windows shell.

This article is reproduced from Microsoft Systems Journal. Copyright © 1995 by Miller Freeman, Inc. All rights are reserved. No part of this article may be reproduced in any fashion (except in brief quotations used in critical articles and reviews) without the prior consent of Miller Freeman.

To contact Miller Freeman regarding subscription information, call (800) 666-1084 in the U.S., or (303) 447-9330 in all other countries. For other inquiries, call (415) 358-9500.