Previous Page TOC Next Page



Chapter 18


OLE Document Objects


by Laurent Poulain


The specifications of OLE Document Objects (DocObjects, for short) evolved from existing OLE 2.0 specifications. However, DocObjects are more than just a new technology based on OLE 2.0 (like ActiveX controls or OCX); they represent the evolution of OLE's philosophy, and are a new breakthrough for document embedding. Microsoft intends to enforce these specifications in every document application on Windows—from Word to Internet Explorer (both of which, by the way, already support DocObjects).

Microsoft wants its upcoming Internet-oriented Windows 97 (also known as Nashville) desktop to be document oriented. That is, Microsoft wants every document from every application to be able to be inserted into a container (Office Binder, Internet Explorer,[el]).

What's New with DocObjects?


Because DocObjects are an evolution of OLE's document embedding, this chapter examines the major releases of this technology and what each release brings to document-embedding technology.

OLE 1.0


Although the first implementation of OLE (OLE 1.0) provides document-embedding capabilities, it isn't very satisfying from a visual standpoint. For example, every time a user wants to edit (create or modify) a document embedded in an OLE 1.0 container application, the user must edit the document in an external corresponding application (called the server application) that has been loaded by the container application (see Figures 18.1 and 18.2). When the user finishes, he or she closes the OLE server application to return to his or her original application. Although OLE 1.0 works perfectly from a technical standpoint, having to pass from one application frame window to another doesn't really create the visual impression of embedded document.

Figure 18.1. Viewing an OLE 1.0 embedding document in Microsoft Word.

Figure 18.2. Editing an OLE 1.0 embedded document. The OLE server application window (in this case Microsoft Paint) pops up and lets the user edit the embedded document.

OLE 2.0


OLE 2.0 addresses the visual issues of OLE 1.0. When users edit an embedded document using OLE 2.0, they can edit the embedded document within the boundaries of their current frame window. The server application is still loaded into memory, but users need not jump from the container application to the server application; indeed, some pieces of the container document are visible outside the boundaries of the embedded document. In addition, changes to OLE 2.0's environment (such as the menu bar, tool bar, and so on) have been implemented (see Figures 18.3 and 18.4).

Figure 18.3. Viewing an OLE 2.0 embedded document. So far, no change from OLE 1.0.

Figure 18.4. Editing an OLE 2.0 embedded document. The user edits the embedded document directly within the container frame window. The menu bar and toolbar have changed, but the frame window and the container document outside the embedded document's boundaries remain unchanged.

Although OLE 2.0 implements several useful changes, there remains room for improvement. OLE 2.0 lacks the capability to hide the embedded side of an embedded document. Indeed, an external, embedded document is bound to a rectangle within a native container document. A document window cannot only contain the external document. It must first contain a native document that will contain the external one. As a result, one can immediately recognize an external document. For example, Winword 6 can embed an Excel 5 graph within a Word document, but Winword cannot have a document window containing only the Excel chart (in other word, the Excel chart cannot be full-scaled).. Furthermore, the embedded application cannot control the frame window. For example, the Excel chart cannot change the type of view of the Word document (that is, normal mode, page mode) and don't have access to Word's save and print commands..

DocObjects


OLE DocObjects were designed to make the user forget that an embedded document is embedded. A DocObject can be embedded in a container application even if it is not within a native container document; it can control the frame window and can be viewed full-scale. In other words, a DocObject behaves like a native document.

Some DocObject applications already exist. Office 95 Binder and Internet Explorer 3.0 are examples of DocObject containers, and Office 95 applications, such as Word 95, Excel 95, and PowerPoint 95, are examples of DocObject server applications.

DocObject Examples


What are the benefits of DocObjects? What good is a DocObject container? After all, OLE 2.0 isn't so bad. If you want to edit a full-scale document, why not do it by loading the corresponding application? To answer these questions, let's take a look at Office Binder and Internet Explorer 3.0. Both are DocObject containers, so both allow full-scale use of DocObject applications within their frame windows.

Office Binder, shipped with Microsoft Office 95, enables you to create catalogs of Microsoft Office documents. Each catalog is a collection of DocObjects (see Figure 18.5). The left pane of Office Binder represents all documents contained in the catalog. When you click a document, the right pane displays that document using the appropriate application interface (if it is a Word document, the right pane displays the Word pull-down menu, icon bars, and so on); you can then edit this document. In Microsoft Office Binder you can edit a set of documents from different applications. When you change from a Word document to an Excel document, the right pane automatically displays the Excel interface. Except for being able to see Microsoft Office Binder's left pane, you really have the impression of using the actual desktop application.

In Figure 18.5 the left pane represents the documents of the catalog (here, one Excel document and one Internet Explorer document). The right pane displays the selected document with its corresponding application interface.

Figure 18.5. The Office Binder window.

Internet Explorer 3.0 enables you to embed and display DocObjects just like it displays HTML pages (see Figure 18.6). That might seem odd for a Web browser, but when you consider the Internet-oriented, intranet-oriented, and document-oriented desktop policy found in Windows 97, it makes more sense. In Windows 97, Internet Explorer (the Web browser) and Microsoft Explorer (the file manager) are merged into one unique Explorer application. Therefore, local documents are accessed the same way as Internet HTML pages, and every document displayed within its frame window is seen as a DocObject by Internet Explorer (either the current Internet Explorer and the future Windows 97 Explorer). When Internet Explorer loads a document, it asks the operating system (Windows 95 or Windows NT) for the corresponding server application (that is, the application that manages the required document). If the server application isn't a DocObject server, Internet Explorer calls it and asks it to display the document. Otherwise, IE acts as a DocObject container, interacts with the DocObject server, and displays the document within its frame window. When dealing with HTML pages, IE just uses its embedded HTML DocObject server (unless .HTM/.HTML files are bound to another application, say Netscape Navigator, in which case IE will load Netscape Navigator. However, it much reduces IE's interest).It makes sense, and then, for a Web Browser to access documents other than HTML pages from the local hard disk, from a corporate intranet, or from the Internet (if the corresponding DocObject server application isn't remote), and to display them as native documents. That's why ActiveX technology includes hyperlinking features based on DocObject technology.

Figure 18.6. Internet Explorer 3.0 displaying a Word document instead of a regular HTML page.

A generic DocObject container (a combination of Office Binder and Internet Explorer) could feasibly be provided with the operating system, and would be the primary (if not the only) user interface.

DocObject Mechanics


To understand how DocObjects work, you must understand their mechanics. The DocObject model adds a few things to the OLE model from which it has evolved. Because DocObjects have evolved from OLE 2.0, programmers can turn existing OLE-compliant applications into DocObject-compliant applications without having to rewrite the applications.

DocObject Components


DocObjects use the same client/server architecture as OLE. A DocObject server application provides the document object, and a DocObject client, or container, embeds the DocObject and displays it in its frame window.

The DocObject server application provides the document object on the DocObject container's request. The DocObject server application also manages the document, implementing document-specific tasks such as storing and printing. Following the Microsoft Foundation Classes document/view architecture, the DocObject server application is composed of two parts:

The DocObject client, or container, embeds the DocObject within its frame window. The client, or container, is composed of several elements:


DocObject Interfaces


The document site, view site and frame only communicate through interfaces. An interface is a set of functions. From an implementation point of view, an interface is an instance of a C++ class whose member functions are the interface functions. For further information about interface implementation, see the section later in this chapter titled "Interface Implementation."

Some interfaces found in DocObjects already exist in OLE 2.0, so you won't have to write them in programs that support OLE 2.0 (OLE 2.0's IOleObject interface, however, will have to be slightly modified). DocObjects bring a set of new interfaces; some must be implemented, but some are optional.

How Everything Works Together


DocObject mechanics might seem fuzzy because of the number of function calls from the container to the server application and vice-versa. Because of the potential for confusion, I will detail some of DocObject's common procedures.

Initialization

Initialization occurs when the user decides to embed a DocObject. The process begins as standard OLE 2.0 requests, but ends as DocObject-specific processing.


Creating Views

The container manages the server application's view. To do so, the container requires the server's views' IOleDocumentView interface to create a new view or to manage an existing one. The following describes what happens when the container asks the server application to create a new view:

  1. 1. The container calls the server application's IOleDocument::CreateView() interface member function.
  2. 2. This function creates the view and returns the view's IOleDocumentView interface to the container.
  3. 3. The container can then associate the created view with a container's view site (in order to work, a server application's view must be associated with a container's view site). To do so, the server application's view's IOleDocumentView::SetInPlaceSite() function is called, which passes the IOleInPlaceSite interface from one of the server application's view sites to the container.
  4. 4. The server application's view knows its associated container's view site through its IOleInPlaceSite interface.

Enumerating Views

To retrieve an existing view's IOleDocumentView interface, the container must enumerate the existing views. The process for this follows:

  1. 1. The container calls the server application's IOleDocument::EnumViews() interface member function.
  2. 2. This server application function creates an enumerator to enumerate the existing views. This enumerator can be controlled via its IEnumDocumentViews interface, which is returned to the container.
  3. 3. The container can then browse the existing views and select one (or more) that interests it through the server application's enumerator IEnumDocumentViews interface.
  4. 4. The server application's enumerator then sends the selected views' IOleDocumentView interfaces to the container.
  5. 5. The container can manage the selected views through their IOleDocumentView interfaces.

Interface Implementation


As you have seen, DocObject programming requires the implementation of several interfaces. You will now take a look at how interfaces are created.

An interface is implemented in several steps, the first of which is to declare the interface in the definition of its class:


class CMyClass: public CBaseClass

{

public:

    BEGIN_INTERFACE_PART(MyDocument, IMyInterface)

        INIT_INTERFACE_PART(CMyClass, MyDocument)

        STDMETHOD(Create)(int);

    END_INTERFACE_PART(MyDocument)

    ... // all the other interfaces

    DECLARE_INTERFACE_MAP()

};

In this example, the CMyClass class contains the IMyInterface interface. Notice the presence of the MyDocument name. This name represents the class that contains the interface implementation. Indeed, an implicit XMyDocument class is created and contains all the interface functions through its own member functions. Also, the member variable m_xMyDocument of type IMyInterface is implicitly declared in the CMyClass class. This variable allows a CMyClass instance to access its IMyInterface interface. Both XMyDocument class and m_xMyDocument variables are automatically created by the MFC macros. Their declaration is therefore not visible in the code.

The second step is to implement the declared interface:


IMPLEMENT_DYNAMIC(CMyClass, CBaseClass)

BEGIN_MESSAGE_MAP(CMyClass, CBaseClass)

    //{{AFX_MSG_MAP(CMyClass)

        // NOTE—the ClassWizard will add

        // and remove mapping macros here.

    //}}AFX_MSG_MAP

END_MESSAGE_MAP()

BEGIN_INTERFACE_MAP(CMyClass, CBaseClass)

   INTERFACE_PART(CMyClass, IID_IMyInterface, MyDocument)

END_INTERFACE_MAP()

STDMETHODIMP CMyClass::XMyDocument::Create(int n)

{

    METHOD_PROLOGUE_EX(CMyClass, MyDocument)

    ...

}

All interface functions always return a HRESULT variable. This variable describes the state of the operation (whether or not it succeeded, and if not, why). Because no output is directly returned, interface functions have two types of arguments: input and output. Input arguments are passed normally. Output arguments are actually pointers to a memory location where the function can write the output.

All interfaces are derived from the IUnknown base class. This class represents an unknown interface and implements the virtual IUnknown::QueryInterface() member function, which is the way to retrieve an object's interface. This function is used to determine whether an object supports a given interface. For example, a server application's document object wants to get the container's document site IOleDocumentSite interface. The server application's document object must:

  1. 1. Get the container's document site's IOleClientSite interface by calling the application server's DocObjects IOleDocument::GetClientSite() interface member function. Say the DocObjectt stores the interface pointer in m_xDocumentSite.
  2. 2. The DocObject then calls m_xDocumentSite's QueryInterface() member function and asks the document site whether it supports the IOleDocumentSite interface(if yes, its pointer is stored in m_xOleDocumentSite):

m_xDocumentSite->QueryInterface(IID_IOleDocumentSite,

                                (void **)&m_xOleDocumentSite);

This process is common with DocObjects. Because DocObject components are only seen through their interfaces, one component will know the existence of another component only if it has a pointer to (one of) its interface. In the rest of the chapter, the term passing an interface means passing the interface's pointer. This term is used for easier reading.

DocObject Server Application Implementation


Implementing a DocObject server application requires writing several non-OLE features. The biggest part of a DocObject is the set of interfaces it supports, including some OLE 2.0 interfaces that don't need to be re-written if the server application already uses OLE 2.0. However, some interfaces are new DocObject interfaces. Following is a list of the DocObject-specific interfaces:

In order to implement a server application, you must:


The IOleObject Interface


The IOleObject interface is derived from OLE 2.0. Therefore, it is useless to implement this interface again if your project is already an OLE 2.0 server application. However, because you must change one of this interface's member functions, you must include the complete interface:


BEGIN_INTERFACE_PART(OleObject, IOleObject)

        INIT_INTERFACE_PART(CMyDocObjServer, OleObject)

        STDMETHOD(SetClientSite)(IOleClientSite*);

        STDMETHOD(GetClientSite)(IOleClientSite**);

        STDMETHOD(SetHostNames)(LPCOLESTR, LPCOLESTR);

        STDMETHOD(Close)(DWORD);

        STDMETHOD(SetMoniker)(DWORD, IMoniker*);

        STDMETHOD(GetMoniker)(DWORD, DWORD, IMoniker**);

        STDMETHOD(InitFromData)(IDataObject*, BOOL, DWORD);

        STDMETHOD(GetClipboardData)(DWORD, IDataObject**);

        STDMETHOD(DoVerb)(LONG, LPMSG, IOleClientSite*, LONG,

                          HWND, LPCRECT);

        STDMETHOD(EnumVerbs)(IEnumOLEVERB**);

        STDMETHOD(Update)();

        STDMETHOD(IsUpToDate)();

        STDMETHOD(GetUserClassID)(CLSID*);

        STDMETHOD(GetUserType)(DWORD, LPOLESTR*);

        STDMETHOD(SetExtent)(DWORD, LPSIZEL);

        STDMETHOD(GetExtent)(DWORD, LPSIZEL);

        STDMETHOD(Advise)(IAdviseSink*, LPDWORD);

        STDMETHOD(Unadvise)(DWORD);

        STDMETHOD(EnumAdvise)(IEnumSTATDATA**);

        STDMETHOD(GetMiscStatus)(DWORD, LPDWORD);

        STDMETHOD(SetColorScheme)(LPLOGPALETTE);

    END_INTERFACE_PART(OleObject)

Fortunately, because you need to replace only one function, you just have to execute the following changes after the interface is implemented:


STDMETHODIMP CMyDocObjectServer::XOleObject::QueryInterface

(REFIID iid, LPVOID* ppvObj)

{

    METHOD_PROLOGUE_EX(CMyDocObjectServer, OleObject)

    return pThis->ExternalQueryInterface(&iid, ppvObj);

}

The only member function you must override is SetClientSite(). This member function is given an IOleClientSite interface (which is an OLE 2.0 interface) belonging to a container's document site. The function must perform the following actions:

The following source code is an example of SetClientSite() implementation:


STDMETHODIMP CMyDocObjectServer::XOleObject::SetClientSite

(IOleClientSite *pDocumentSite)

{

    METHOD_PROLOGUE_EX(CMyDocObjectServer, OleObject)

    HRESULT result = NOERROR;

    // Perform standard SetClientSite processing.

    // m_xOleObject is supposed to point to the ...

    result = m_xOleObject.SetClientSite(pDocumentSite);

    if (result != S_OK)

        return result;

    // If a document site pointer is already associated,

    // release it.

    // m_pDocSite is a CMyDocObjectServer IOleClientSite*

    // variable which points to the associated document site.

    if (m_pDocSite != NULL)

    {

        m_pDocSite->Release();

        m_pDocSite = NULL;

    }

    // If a document site pointer (i.e. pOleClientSite)

    // is given in argument

    if (pDocumentSite != NULL)

        // Check if it supports the IOleDocumentSite interface.

        // If yes, stores this interface in m_pDocSite.

        result = pDocumentSite->QueryInterface

                 (IID_IOleDocumentSite, (LPVOID*)&m_pDocSite);

    // Return the result of the test.

    return result;

}

The IOleDocument Interface


The IOleDocument interface is the front end of the DocObject server application. It is through this interface that the DocObject container demands the creation and enumeration of views.


BEGIN_INTERFACE_PART(OleDocument, IOleDocument)

        INIT_INTERFACE_PART(CMyDocObjectServer, OleDocument)

        STDMETHOD(CreateView)(IOleInPlaceSite*, Istream*, DWORD,

                              IOleDocumentView**);

        STDMETHOD(GetDocMiscStatus)(DWORD*);

        STDMETHOD(EnumViews)(IEnumOleDocumentViews**,

                             IOleDocumentView**);

    END_INTERFACE_PART(OleDocument)

Following is a list of member functions used in the IOleDocument interface. Each list entry contains a brief description, a syntax statement, and a table containing argument types and returned values.

IOleDocument::CreateView()

The IOleDocument::CreateView() member function asks the DocObject to create a new view. This function returns the created view's IOleDocumentView interface pointer through the last argument, and can take an IStream* parameter for initialization (an IStream is a Win32 interface that allows reading and writing data to stream objects).


STDMETHOD(CreateView)(IOleInPlaceSite*, Istream*, DWORD,

                      IOleDocumentView**);

IOleDocumentView** Where to send the new view's IOleDocumentView* interface pointer.

E_FAIL This DocObject can only create a single view, which already exists.


IOleDocument::GetDocMiscStatus()

The IOleDocument::GetDocMiscStatus() member function returns the document status through the argument, which is coded as follows:


typedef enum

{

    //Object supports multiple views

    DOCMISC_CANCREATEMULTIPLEVIEWS   = 1,

    //IOleDocumentView::SetRectComplex is supported

    DOCMISC_SUPPORTCOMPLEXRECTANGLES = 2,

    //IOleDocumentView::Open is not supported

    DOCMISC_CANTOPENEDIT             = 4,

    //Object does not support file read/write

    DOCMISC_NOFILESUPPORT            = 8

} DOCMISC;

Objects that cannot be edited or can just been in-place activated must have a CANTOPENEDIT status. Objects that only support IPersistStorage as a storage mechanism must have a DOCMISC_NOFILESUPPORT status.


STDMETHOD(GetDocMiscStatus)(DWORD*);

DWORD* Where to store the document status.

E_POINTER The DWORD* argument is NULL.


IOleDocument::EnumViews()

The IOleDocument::EnumViews() member function creates an enumerator that supports the IEnumOleDocumentViews interface. This interface is sent to either the object pointed to by the first argument if the DocObject supports multiple views, or to the object pointed to by the second argument if the DocObject supports only a single view.


STDMETHOD(EnumViews)(IEnumOleDocumentViews**, IOleDocumentView**);

IoleDocumentView** Where to send the enumerator object for a single view.

E_OUTOFMEMORY No more memory to create the enumerator.


The IEnumOleDocumentViews Interface


Use the IEnumOleDocumentViews interface to control an enumerator. An enumerator is an object that provides an IEnumOleDocumentViews interface, and that browses the existing views, returning their IOleDocumentView interfaces. An enumerator is created by calling the IOleDocument::EnumViews() member function.


BEGIN_INTERFACE_PART(EnumOleDocumentViews, IEnumOleDocumentViews)

    INIT_INTERFACE_PART(CMyDocObjectServer, EnumOleDocumentViews)

    STDMETHOD(Next)(ULONG, IOleDocumentView**, ULONG*);

    STDMETHOD(Skip)(ULONG);

    STDMETHOD(Reset)(void);

    STDMETHOD(Clone)(IEnumOleDocumentViews**);

END_INTERFACE_PART(EnumOleDocumentView)

Following is a list of member functions used in the IEnumOleDocumentViews interface. Each list entry contains a brief description, a syntax statement, and a table containing argument types and returned values.

IEnumOleDocumentViews::Next()

The IEnumOleDocumentViews::Next() member function is given an IOleDocumentView* array (the second argument) in which it puts, at most, a given number (the first argument) of IOleDocumentView* for each existing view. It will also return in the third argument the actual number of views copied.


STDMETHOD(Next)(ULONG, IOleDocumentView**, ULONG*);

ULONG* Where to send the actual number of document views enumerated. If NULL, the first argument must be 1.

E_OUTOFMEMORY No more memory to create the enumerator.


IEnumOleDocumentViews::Skip()

The IEnumOleDocumentViews::Skip() member function asks the enumerator to skip a certain number of views. Those views won't be enumerated again unless IEnumOleDocumentViews::Reset() is called.


STDMETHOD(Skip)(ULONG);

ULONG Number of views to skip.

E_UNEXPECTED Unknown error.


IEnumOleDocumentViews::Reset()

The IEnumOleDocumentViews::Reset() member function asks the enumerator to position itself at the beginning of the list of existing views. The enumerator can then browse all views again.


STDMETHOD(Reset)(void);

E_UNEXPECTED Unknown error.


IEnumOleDocumentViews::Clone()

The IEnumOleDocumentViews::Clone() member function directs the enumerator to clone itself. The clone is considered to have the same status as the original enumerator, so is has the same position in the views list. The clone's interface is returned through the function argument.


STDMETHOD(Clone)(IEnumOleDocumentViews**);

IEnumOleDocumentViews** Where to send the newly created enumerator's interface.

E_OUTOFMEMORY Not enough memory to create the enumerator.


The IOleDocumentView Interface


The IOleDocumentView interface allows a DocObject container to manage a document view. It is through this interface that the container's view site can set view characteristics, associate a view site with a view, and so on (in order to work properly, each server application's view must be associated with a container's view site).


BEGIN_INTERFACE_PART(OleDocumentView, IOleDocumentView

      INIT_INTERFACE_PART(CMyDocObjectServer, OleDocumentView

      STDMETHOD(SetInPlaceSite)(IOleInplaceSite*);

      STDMETHOD(GetInPlaceSite)(IOleInPlaceSite**);

      STDMETHOD(GetDocument)(IUnknown**);

      STDMETHOD(SetRect)(LPRECT);

      STDMETHOD(GetRect)(LPRECT);

      STDMETHOD(SetRectComplex)(LPRECT, LPRECT, LPRECT, LPRECT);

      STDMETHOD(Show)(BOOL);

      STDMETHOD(UIActivate)(BOOL);

      STDMETHOD(Open)(void);

      STDMETHOD(CloseView)(DWORD);

      STDMETHOD(SaveViewState)(IStream*);

      STDMETHOD(ApplyViewState)(IStream*);

      STDMETHOD(Clone)(IOleInPlaceSite*, IOleDocumentView**);

   END_INTERFACE_PART(OleDocumentView)

Following is a list of member functions used in the IOleDocumentView interface. Each list entry contains a brief description, a syntax statement, and a table containing argument types and returned values.

IOleDocumentView::SetInPlaceSite()

The IOleDocumentView::SetInPlaceSite() member function associates a container's view site passed in the argument with this view. If the view is already associated with a view site, it must first disassociate from this view site. When a view is associated with (or holds) a view site, it only releases it with SetInPlaceSite(NULL) or CloseView(), but NOT with the normal in-place deactivation as do regular OLE documents.


STDMETHOD(SetInPlaceSite)(IOleInplaceSite*);

IOleInPlaceSite* Interface of the site to be associated. If NULL, the view loses any view-site association.

E_FAIL An error occurred.


IOleDocumentView::GetInPlaceSite()

The IOleDocumentView::GetInPlaceSite() member function returns the view site associated with the view through the function argument. This member function returns NULL if the view isn't associated.


STDMETHOD(GetInPlaceSite)(IOleInPlaceSite**);

IOleInPlaceSite** Where to send the view site's interface that is associated with the specific view.

E_FAIL An error occurred.


IOleDocumentView::GetDocument()

The IOleDocumentView::GetDocument() member function gets the DocObject that owns the view (that is, the DocObject that is shown by the view). This function actually sends a pointer to its IUnknown interface through the function argument.


STDMETHOD(GetDocument)(IUnknown**);

IUnknown** Where to send the IUnknown* interface pointer of the DocObject that owns the view.

S_OK The interface was successfully sent.


IOleDocumentView::SetRect()

The IOleDocumentView::SetRect() member function sets a new size for the view. The rectangle passed as an argument is in the client's coordinates (that is, the caller's relative coordinates).


STDMETHOD(SetRect)(LPRECT);

LPRECT New size of the view.

E_FAIL An error occurred.


IOleDocumentView::GetRectangle()

The IOleDocumentView::GetRectangle() member function obtains the current view size in client coordinates and returns it through the function argument.


STDMETHOD(GetRect)(LPRECT);

LPRECT Where to store the current view coordinates.

E_UNEXPECTED An error occurred.


IOleDocumentView::SetRectComplex()

The IOleDocumentView::SetRectComplex() member function sets the size of several items: the view port, the scroll bars and the size box. All coordinates passed as arguments must appear as client coordinates.


STDMETHOD(SetRectComplex)(LPRECT, LPRECT, LPRECT, LPRECT);

LPRECT Client coordinates of the size box.

E_FAIL An error occurred.


IOleDocumentView::Show()

The IOleDocumentView::Show() member function asks the view to either show or hide itself.


STDMETHOD(Show)(BOOL);

BOOL If TRUE, the view must be shown. If FALSE the view must be hidden.

E_UNEXPECTED An error occurred.


IOleDocumentView::UIActivate()

The IOleDocumentView::UIActiviate() member function asks the view to either activate or deactivate its user interface elements, such as its menu bar, its toolbars, and so on.


STDMETHOD(UIActivate)(BOOL);

BOOL If TRUE, the view must activated its user interface. If FALSE it must deactivate it.

E_UNEXPECTED An error occurred.


IOleDocumentView::Open()

The IOleDocumentView::Open() member function creates a new window that displays the view.


STDMETHOD(Open)(void);

E_UNEXPECTED An error occurred.


IOleDocumentView::CloseView()

The IOleDocumentView::CloseView() member function closes the view, releasing any view site associated with it. The container must call this member function before deleting the view.


STDMETHOD(CloseView)(DWORD);

DWORD Must be zero.

S_OK The view coordinates were successfully closed.

Closing a view is not supposed to fail, which is why S_OK is the only listed return value.

IOleDocumentView::SaveViewState()

The IOleDocumentView::SaveViewState() member function asks the view to save its state in an IStream passed in an argument. This function is typically used before closing a view; the stream keeps its state, allowing the state to be restored later via IOleDocumentView::ApplyViewState().


STDMETHOD(SaveViewState)(IStream*);

IStream* Where the state is saved.

E_NOTIMPL Function not supported.


IOleDocumentView::ApplyViewState()

The IOleDocumentView::ApplyViewState() member function opposes IOleDocumentView::SaveViewState(). IOleDocumentView::ApplyViewState() restores a state saved in the stream passed as an argument.


STDMETHOD(ApplyViewState)(IStream*);

IStream* The location from which the state is loaded.

E_NOTIMPL Function not supported.


IOleDocumentView::Clone()

The IOleDocumentView::Clone() member function creates a copy of its view object, with the same state (though associated with a different view site). The first argument is the container's view site for the clone, whose IOleDocumentView interface is returned through the last argument.


STDMETHOD(Clone)(IOleInPlaceSite*, IOleDocumentView**);

IOleDocumentView** Where to store the pointer of the new view.

E_FAIL The DocObject supports only a single view.


Modify the COleIPFrameWnd Derived Class.


A DocObject server application must add functions to its COleIPFrameWnd derived class. Indeed, this class must implement the server application's part of menu merging. This implementation can be divided into two parts: the creation of the menu and its destruction.

For more information about how DocObject menu merging works, see section later in this chapter titled "Help Menu Merging."

Creating a Merged Menu'

Menus are merged whenever the server application is activated (when OleDocumentView::UIActivate(BOOL) is passed a TRUE argument).

The COleIPFrameWnd class provides four useful variables for menu merging:


LPOLEINPLACEFRAME m_lpFrame;

    HMENU m_hSharedMenu;

    OLEMENUGROUPWIDTHS m_menuWidths;

    HOLEMENU m_hOleMenu;

m_lpFrame points to the container's IOleInPlaceFrame interface. m_hSharedMenu contains the merged menu bar; m_menuWidths contains information about which menus are inserted by the container or by the server application. Finally, m_hOleMenu contains the menu descriptor.

The server application must set the m_hSharedMenu and m_menuWidths member variables, which are then sent to the container. The m_menuWidths array must be initialized with zeros, and the m_hSharedMenu variable must represent a blank menu bar by calling


    m_hSharedMenu =::CreateMenu();

The server application then passes the blank menu bar and the OLEMENUGROUPWIDTHS array to the container by calling


    m_lpFrame->InsertMenus(m_hSharedMenu, &m_menuWidths);

After the container has inserted its menus, the server application inserts desired menus by modifying m_hSharedMenu and m_menuWidths. This procedure is the standard OLE 2.0 menu merging, except that if the container inserted a Help menu, the server application must:

The server application must then sends the resulting menu to OLE and get back the menu's descriptor:


m_hOleMenu =::OleCreateMenuDescriptor(m_hSharedMenu,

                                           &m_menuWidths);

Last but not least, the server application must return m_hOleMenu to the container.

Destroying the Merged Menu'

When the DocObject is destroyed, the merged menu must also be destroyed. The container removes its menus from the menu bar and sends the resulting menu to the server application, which must also removes the menus it inserted.

The server application removes the menus (standard OLE procedure), and then destroys m_hSharedMenu by calling


::DestroyMenu(m_hSharedMenu);

Modify the COleServerItem Derived Class


The COleServerItem derived class must be slightly modified to be able to distinguish a DocObject client from a regular OLE 2.0 client. Indeed, if the IOleObject::DoVerb() is passed an OLEIVERB_SHOW, an OLEIVERB_OPEN, or an OLEIVERB_UIACTIVATE order, the DocObject server application must call the IOleDocumentSite::ActivateMe() interface member function. If the IOleObject::DoVerb() is passed an OLEIVERB_HIDE order, it must raise an E_INVALIDARG error.

To configure the COleServerItem derived class to distinguish a DocObject client from an OLE 2.0 client, implement the following changes:


Add MFC Registry


The server application must add its keys to the operating system registry (that is, the Windows 95 or Windows NT registry), which registers the server application with the system as a DocObject server application. For that purpose, the server application must add:


HKEY_CLASSES_ROOT\My.DocObject.Server.1\DocObject = 0

HKEY_CLASSES_ROOT\CLSID\<The server's CLSID> = My Document Object

HKEY_CLASSES_ROOT\CLSID\<The server's CLSID>\DocObject = 0

HKEY_CLASSES_ROOT\CLSID\<The server's CLSID>\DefaultExtention = .my, My DocObject (*.my)

HKEY_CLASSES_ROOT\CLSID\<The server's CLSID>\Printable

The last key must be added only if the server application supports the IPrint interface.

The DocObject server application can modify status bits from the MiscStatus key in the registry, which gives the server application the option of not supporting certain troublesome OLE 2.0 features. OLEMISC_CANTLINKINSIDE prevents linking to embedded DocObjects. OLEMISC_ICONICONLY forces the DocObjects to appear only as an icon when copied into containers.

Miscellaneous Stuff


Here are a few more things to remember when implementing a DocObject server application:


DocObject Container Implementation


The implementation of a DocObject container requires the creation of DocObject-specific interfaces and several other functions. A list of the necessary interfaces follows:


DocObject Initialization


When a container creates a DocObject, it can create a brand new DocObject with a standard initial state, or the container can create a DocObject from a file or from data (through a cut & paste operation, for example). The full initialization of the DocObject depends on how the DocObject was created. However, initialization will at least require:


The IOleDocumentSite Interface


The IOleDocumentSite interface is the only ActiveX interface required to create a container site (all other ActiveX interfaces are optional). The goal of this interface is to allow a DocObject to ask the container to activate itself.


BEGIN_INTERFACE_PART(OleDocumentSite, IOleDocumentSite)

    INIT_INTERFACE_PART(CMyDocObjectContainer, OleDocumentSite)

    STDMETHOD(ActivateMe)(IOleDocumentView*);

END_INTERFACE_PART(OleDocumentSite)

Following is a list of member functions used in the IOleDocumentSite interface. Each list entry contains a brief description, a syntax statement, and a table containing argument types and returned values.

IOleDocumentSite::ActivateMe()

The IOleDocumentSite::ActivateMe() member function asks a DocObject container to activate itself. This member function is called by the DocObject server application in the server's IOleObject::DoVerb() interface member function. It is passed the interface of the server application's view to activate. Following is a syntax statement and a table containing argument types and returned values.


STDMETHOD(ActivateMe)(IOleDocumentView*);

IOleDocumentView* The view to be activated.

E_FAIL An error occurred in the process.

The IOleDocumentSite::ActivateMe() member function performs the following tasks:


Design the Data Storage Structure Based on the User's Document Embedding aspect.


A DocObject container can manage multiple documents in a single data storage structure. This data storage structure can be an OLE compound file, or it can be divided into several files (one file per document). No matter which method you choose, always design your data storage structure to correspond with the user's views on document embedding.

For example, Office Binder uses an OLE compound file to store all documents. So it is normal to have every DocObject of a Binder catalog (the Binder catalog being the native Office Binder format) in one unique compound file.

On the other hand, take, for example, a container dealing with documents spread across the Internet. This time, DocObjects are not seen as being contained in a binder. Therefore, in this case, using several files would be more effective than using a compound file.

Closing a DocObject


When a DocObject is closed, the container should check whether its data was saved. It must call IOleClientSite::SaveObject() if needed, and then place IOleInplaceObject::InPlaceDeactivate(), IOleObject::Close() and IUnknown::Realease() calls to every interface it is referencing.

Optional OLE Functions and Necessary OLE Functions


The container can simply return the message ENOTIMPL instead of implementing these optional OLE functions:


IOleClientSite::GetMoniker()

IOleClientSite::GetContainer()

IOleClientSite::RequestNewObjectLayout()

IOleClientSite::OnShowWindow()

IOleClientSite::ShowObject()

IOleInPlaceSite::OnPosRectChange()

IOleInPlaceSite::Scroll()

IOleInPlaceSite::ContextSensitiveHelp()

Every AdviseSink interface member function is optional except AdviseSink::OnClose(), which must be implemented.

All other member functions of IOleClientSite, IOleInPlaceSite and IOleInPlaceFrame need some implementation. Moreover, if the container supports OLE-only documents, all normal OLE functions must be implemented.

Miscellaneous Stuff


There are still a few things you should know in order to implement a DocObject container:


Programmatic Printing


Programmatic printing is a new DocObject feature that allows the DocObject container to manage the printing of embedded DocObjects. Programmatic printing provides an interface for printing, for getting the status of print jobs, for setting the pages to be printed, and even for providing a callback mechanism with which the user can halt the printing process before its end. In other words, the container can ask the embedded object to print itself. However, the container still has the control in case the user decides to abort the printing process.

Programmatic printing is composed of two interfaces, IPrint and IContinueCallBack. The IPrint interface controls the printing process itself; it sets the initial page number, decides which pages will be printed, and retrieves printing status information (such as the number of pages that were actually printed and the last printed page number). This interface must be implemented in the DocObject server application. The IContinueCallback interface provides a way for the user to abort the printing process. Actually, IContinueCallback can be used for any lengthy operation. A background process regularly checks whether the user has decided to abort the printing process. If so, it calls an IContinueCallback member function, which asks the user whether he wants to abort printing or not. This interface must be implemented in the DocObject container.

The IPrint Interface


The IPrint interface implements the document printing management; it sets the pages to print, prints them, and retrieves print-related information.


BEGIN_INTERFACE_PART(Print, IPrint)

      INIT_INTERFACE_PART(CMyDocObjectServer, Print)

      STDMETHOD(SetInitialPageNum)(LONG);

      STDMETHOD(GetPageInfo)(LONG*, LONG*);

      STDMETHOD(Print)(DWORD, DVTARGETDEVICE**, PAGESET**,

                       STGMEDIUM*, IContinueCallBack*, LONG,

                       LONG*, LONG*);

   END_INTERFACE_PART(Print)

Before describing the interface member functions, let's take a look at some structures used by these functions. Each of the following entries contains a brief description and a table; some entries contain a syntax statement.

The PAGERANGE Structure

As its names implies, the PAGERANGE structure contains a range of pages.

nToPage LONG The last page to print.


The PAGESET Structure

The PAGESET structure identifies a set of page ranges, and optionally indicates information such as odd or even pages.

rgPages PAGERANGE* Contains the set of page ranges to be printed. The range must be sorted in increasing order and must not overlap.


The PRINTFLAG Enumeration

The PRINTFLAG enumeration contains miscellaneous values.

PRINTFLAG_PRINTTOFILE Print to a file.


IPrint::SetInitialPageNum()

The IPrint::SetInitialPageNum() member function the first page to be printed.


STDMETHOD(SetInitialPageNum)(LONG);

LONG First page number.

E_UNEXPECTED An error occurred.


IPrint::GetPageInfo()

The IPrint::GetPageInfo() member function returns information about pages through its two arguments.


STDMETHOD(GetPageInfo)(LONG*, LONG*);

LONG* Where to copy the total number of pages in the document. A NULL value means the caller doesn't need this number.

E_UNEXPECTED An error occurred.


IPrint::Print()

The IPrint::Print() member function asks the object to print itself. The first six arguments are parameters for the printing process, like the pages to be printed, the printing process's target device and so on. This function returns printing information through its last two arguments.


STDMETHOD(Print)(DWORD, DVTARGETDEVICE**, PAGESET**, STGMEDIUM*,

                 IContinueCallBack*, LONG, LONG*, LONG*);

LONG* Where to copy the last page number.

E_UNEXPECTED An error occurred.


The IContinueCallback Interface


The IContinueCallBack interface is manages a generic callback procedure, allowing the user interrupt running (lengthy) processes.


BEGIN_INTERFACE_PART(ContinueCallback, IContinueCallback)

      INIT_INTERFACE_PART(CMyDocObject, ContinueCallback)

      STDMETHOD(FContinue)(void);

      STDMETHOD(FContinuePrinting)(LONG, LONG, wchar_t*);

END_INTERFACE_PART(ContinueCallback)

Let's take a look at some member functions of this interface. Each of the following entries contains a brief description a table, a syntax statement.

IContinueCallback::FContinue()

The IContinueCallback::FContinue() member function administers the continuation (or discontinuation) of a given operation.


STDMETHOD(FContinue)(void);

E_FALSE Cancel the operation as soon as possible.


IContinueCallback::FContinuePrinting()

The IContinueCallback::FContinuePrinting() member function administers the continuation (or discontinuation) of a given printing process.


STDMETHOD(FContinuePrinting)(LONG, LONG, wchar_t*);

wchar_t* Status message.

E_UNEXPECTED An error occurred.


Command Target


Command target is a mechanism that allows the container to query and execute one or more commands from the server application, and vice-versa. In other words, a DocObject's container and server application can ask each other whether or not the requested command is supported. If so, they can request its execution.

Commands all belong to a group. An example of group is the standard command list, which regroups all standard commands from Microsoft Office applications, such as File Open, File Close, and so on. However, anybody can create a group containing its own commands.

Command target can be useful in the following cases:

Use the IOleCommandTarget to load the command target interface, which can be implemented in the DocObject's container, the server application, or both (depending on what comment target functionality is required).

The IOleCommandTarget Interface


The IOleCommandTarget interface enables to ask if a command is available and to execute it. The commands are referenced by an integer, within a group of commands. The group itself is referenced by an integer. Through this interface, one can ask whether a given command is supported and/or execute it.


BEGIN_INTERFACE_PART(OleCommandTarget, IOleCommandTarget)

    INIT_INTERFACE_PART(CMyDocObject, OleCommandTarget)

    STDMETHOD(QueryStatus)(const GUID*, ULONG, OLECMD*, OLECMDTEXT*);

    STDMETHOD(Exec)(const GUID*, DWORD, DWORD,

                    VARIANTARG*, VARIANTARG*);

END_INTERFACE_PART(OleCommandTarget)

This interface uses some structures and enumerations, which will be explained before its member functions. The following entries contain a brief description and a table; member function entries also contain a syntax statement.

The OLECMDF Enumeration

The OLECMDF enumeration contains values describing the status of a given command.

OLECMDF_NINCHED The command is an on/off switch, but the state is indeterminate.


The OLECMD Structure

The OLECMD structure contains a command identifier and its status.

cmdf DWORD OLECMDF enumeration.


The OLECMDTEXTF Enumeration

This enumeration contains flags that describe that information to return after the command was executed.

OLECMDTEXTF_STATUS Return a status string.


The OLECMDTEXT Structure

The OLECMDTEXT structure is used to return text name or status string.

rgwz wchar_t Array that will receive the output string.


The OLECMDEXECOPT Enumeration

The OLECMDEXECOPT enumeration contains flags that describe what information should be returned after the command was executed.

OLECMDTEXTF_STATUS Return a status string.


The OLECMDID Enumeration

The OLECMDID enumeration is known as the Standard Command List. This list contains several of the standard commands defined in Microsoft Office 95. Their group is referenced with a NULL identifier.

OLECMDID_SETTITLE Sets the window title.

The commands OLECMDID_ZOOM and OLECMDID_GETZOOMRANGE need some arguments, passed through IOleCommandTarget::Exec().

OLECMDID_ZOOM

The OLECMDID ZOOM command manages the zoom. It takes a LONG input argument and returns a LONG output argument. This command can be used for three different purposes:


OLECMDID_GETZOOMRANGE

The OLECMDID GETZOOMRANGE command is used to determine the range of valid zoom values. The input argument is NULL, and the execution option is MSOCMDEXECOPT_DONTPROMPTUSER. The command returns its output argument as DWORD. The HIWORD contains the maximum zoom value, whereas the LOWORD contains the minimum zoom value.

IOleCommandTarget::QueryStatus()

The IOleCommandTarget::QueryStatus() member function returns the status of one or more commands. This function is typically called after WM_INITMENU or WM_INITMENUPOPUP messages. Indeed, before displaying its menu bar or pop-up menu, a container might want to ask the server application whether some of its commands are supported so that unsupported ones can be removed from its menus. The first three arguments relate to command description, such as its group and so forth. The third argument is an array containing all commands whose status must be queried. The result is sent through the function's last argument.


STDMETHOD(QueryStatus)(const GUID*, ULONG, OLECMD*, OLECMDTEXT*);

OLECMDTEXT* Where to send name and/or status information. A NULL value indicates that the caller doesn't need this information.

OLECMDERR_E_UNKNOWNGROUP The group type (first argument) is invalid.


IOleCommandTarget::Exec()

The IOleCommandTarget::Exec() member function executes a given command or displays its help information. Although most commands do not need any argument, the fourth parameter was added for this purpose.


STDMETHOD(Exec)(const GUID*, DWORD, DWORD,

                VARIANTARG*, VARIANTARG*);

VARIANTARG* Output values. Might be NULL.

OLECMDERR_CANCELLED The user canceled an operation.


Help Menu Merging


When a container embeds a document using OLE 2.0, the OLE client and server application's menus are merged. Actually, some parts of the menu are managed by the client (such as File and Window), and some parts are managed by the server application (such as Edit, View and other server-application-specific menus). The Help menu is by default managed by the OLE client. However, when the user edits the embedded document, it is replaced by the server application's Help menu.

However, because a DocObject can be embedded without being contained by a native container document, OLE 2.0 menu merging raises a problem: which Help menu should you include? While putting the server application's help menu might seem logical, it raises a problem with DocObject containers (such as Office Binder): help would only be available when the Binder catalog contained no documents.

The solution instituted by DocObjects is to include both the container's and the server application's help information in a unique Help menu composed of two cascade menus: one for the container and one for the server application. This Help menu is described as follows:


Help

      Container Help >

      Server Help    >

How OLE 2.0 Performs Menu Merging


The DocObject approach to menu merging differs from OLE 2.0's approach. Indeed, with OLE 2.0, the merged menu bar can have 6 groups of menus: File, Edit, Container, Object, Window, and Help. Each of the 6 groups can represent some menus (they can however represent no menu at all. For example a program that doesn't support cut & paste operations doesn't need an Edit menu group). The File, Container and Window groups are managed by the container, whereas the Edit, Object and Help groups are managed by the OLE document.

The mains steps of OLE 2.0 menu merging follow:

  1. 1. The OLE server application creates a blank menu bar, along with a six-element OLEMENUGROUPWIDTHS array, and passes both to the client.
  2. 2. The OLE client inserts its own menu elements within the menu bar and writes in the array which groups were inserted. It then returns the menu bar and the array to the server application.
  3. 3. The server application then inserts its own menus, checking which groups were inserted by the client, and updating the array accordingly. The server application passes the resulting menu and array to the operating system's OLE subsystem, which returns the merged menu descriptor handle.
  4. 4. The menu bar and handle are passed to the container. The container displays the menu bar and sends the handle to the OLE subsystem to dispatch the menu messages correctly.

Implementation Differences between DocObjects and OLE 2.0


Implementing menu merging in DocObjects differs slightly from OLE 2.0's menu-merging-implementation process; DocObjects require you to implement a few extras. The following explains those steps not required in OLE 2.0 to implement menu merging in DocObjects:

  1. 1. The server application clears the array after its creation.
  2. 2. The client inserts a Help menu element as the last item and writes a 1 in the last array entry (that is Array[5]=1), indicating the insertion of a Help group menu element.
  3. 3. The server application determines whether the client inserted a Help group menu. If so, the server application inserts its help pop-up menu as a sub-menu of the existing Help pop-up menu (the client Help menu). The server application then sets the last array element to 0, but increments the element before it (Array[4]) by 1. OLE then knows that the Help menu must be merged.
  4. 4. The client forwards all menu messages that concern the embedded-document portion of the Help menu to the server application.
  5. 5. While the menu is being destroyed, the server application removes all menus and sub-menus that it inserted. The client must do the same job when it receives the menu bar.

How to Create a Basic DocObject Server


Let's look at how to create a basic DocObject server application from a basic Visual C++ 4 AppWizard application. Although Visual C++ 4.2 should include this process in one of its AppWizards, it isn't supported in previous versions of Visual C++ 4. Furthermore, some people might be willing to turn their existing OLE 2.0 server application into a DocObject server application, even if they are working with Visual C++ 4.2.

Implementing a whole DocObject server application with all the DocObject interfaces would take a lot of time. A simpler way to achieve the same effect is to borrow some files from the BINDSCRB sample shipped with Visual C++ 4.0. This sample is a little application with DocObject server application features.

The creation of a DocObject server application will take the following three steps:

The first two steps explain how to create an OLE 2.0 server application. Although creating an OLE 2.0 server application is not the goal of this chapter, some readers might find this information useful. Readers interested in turning their OLE server application into a DocObject-compliant server application can skip the first two steps and jump directly to the last one.

Creating an AppWizard Application


Although none of the three steps are really painful, creating an AppWizard Application is the easiest and fastest step. First, create an MFC AppWizard project and name it DoServer (for a DocObject Server application). You can enter a name other than DoServer if you wish, but I will refer to the MFC AppWizard project as DoServer. Next, in step 3 of the MFC AppWizard dialog box, select the option «[dg]Full-Server[dg]».

The created project will be an OLE server application with no attached document (see Figure 18.7).

Figure 18.7. The AppWizard options.

Modify Your Application to Make It an Operational OLE Server Application


The application created by AppWizard, which is virtually an OLE server application, doesn't display anything. This can be problematic if you want to test it. That is why I add a few lines of source code to make the server application display Hello, ActiveX world. In the following entries, bold code represents the code to be added or modified. Normal text code represents the existing code (created by AppWizard).

First, alter the CDoServerDoc class and add the member functions Draw() and GetExtent(), which display the text and calculate the size of the string, respectively. Also, add a few member variables for text management. In the DoServerDoc.h file, add the following bold lines:


class CDoServerDoc: public COleServerDoc

{

...

public:

    void CDoServerDoc::Draw(CDC *pDC) const;

    void CDoServerDoc::GetExtent(CDC *pDC, CSize &size) const;

protected:

    CString m_text;

    LOGFONT m_logfont;

    COLORREF m_crText;

...

};

In the sample on the CD-ROM that accompanies this book, every line added to the AppWizard application contains the following comment:


...   // OLE server addition

The class must now initialize the font. In the DoServerDoc.cpp file, add the following code:


CDoServerDoc::CDoServerDoc()

{

    memset(&m_logfont, 0, sizeof m_logfont);

    m_logfont.lfHeight = -10;

    lstrcpy(m_logfont.lfFaceName, _T("Arial"));

    m_logfont.lfOutPrecision = OUT_TT_PRECIS;

    m_logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;

    m_logfont.lfQuality = PROOF_QUALITY;

    m_logfont.lfPitchAndFamily = FF_SWISS | VARIABLE_PITCH;

    m_crText = COLOR_WINDOWTEXT+1;

    m_text = "Hello, ActiveX World !";

    ...

};

In the DoServerView.cpp file, The CDoServerView::OnDraw() member function must be altered to ask the document to display itself:


void CDoServerView::OnDraw(CDC* pDC)

{

    CDoServerDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);

    // TODO: add draw code for native data here

    pDoc->Draw(pDC);

}

The CDoServerSrvrItem::OnGetExtent() member function, whose goal is to ask the document to calculate its own size, must be implemented. In the SrvItem.cpp file, add:


BOOL CDoServerSrvrItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)

{

...

    // TODO: replace this arbitrary size

    CClientDC dc(NULL);

    dc.SetMapMode(MM_ANISOTROPIC);

    pDoc->GetExtent(&dc, rSize);

    dc.LPtoHIMETRIC(&rSize);

    return TRUE;

}

The CDoServerSrvrItem::OnDraw() member function, whose goal is to prepare the device context for drawing and to ask the document to draw itself, must be implemented. In the SrvItem.cpp file, add:


BOOL CDoServerSrvrItem::OnDraw(CDC* pDC, CSize& rSize)

{

    CDoServerDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);

    // TODO: set mapping mode and extent

    //  (The extent is usually the same as

    // the size returned from OnGetExtent)

    OnGetExtent(DVASPECT_CONTENT, rSize);

    pDC->SetMapMode(MM_ANISOTROPIC);

    pDC->SetWindowOrg(0,0);

    pDC->SetWindowExt(rSize.cx, rSize.cy);

    // TODO: add drawing code here.  Optionally, fill in the

    // HIMETRIC extent. All drawing takes place in the metafile

    // device context (pDC).

    pDoc->Draw(pDC);

    return TRUE;

}

Last but not least, the Draw() and GetExtent() member functions must be implemented in the DoServerDoc.cpp file:


void CDoServerDoc::Draw(CDC *pDC) const

{

    LOGFONT logfont = m_logfont;

    logfont.lfHeight = -::MulDiv(-logfont.lfHeight,

                              pDC->GetDeviceCaps(LOGPIXELSY), 72);

    pDC->SetTextColor(m_crText);

    CFont font;

    if (!font.CreateFontIndirect(&logfont))

    {

        CFont *pOldFont = pDC->SelectObject(&font);

        pDC->TextOut(0, 0, m_text, m_text.GetLength());

        pDC->SelectObject(pOldFont);

    }

    else

        pDC->TextOut(0, 0, m_text, m_text.GetLength());

}

void CDoServerDoc::GetExtent(CDC *pDC, CSize &size) const

{

    LOGFONT logfont = m_logfont;

    logfont.lfHeight = -::MulDiv(-logfont.lfHeight,

                              pDC->GetDeviceCaps(LOGPIXELSY), 72);

    CFont font;

    if (!font.CreateFontIndirect(&logfont))

    {

        CFont *pOldFont = pDC->SelectObject(&font);

        TEXTMETRIC tm;

        pDC->GetTextMetrics(&tm);

        size.cy = tm.tmHeight + tm.tmExternalLeading;

        size.cx = m_text.GetLength() * tm.tmAveCharWidth;

        pDC->SelectObject(pOldFont);

    }

    else

    {

        TEXTMETRIC tm;

        pDC->GetTextMetrics(&tm);

        size.cy = tm.tmHeight + tm.tmExternalLeading;

        size.cx = m_text.GetLength() * tm.tmAveCharWidth;

    }

}

Voil[aca]! Your OLE server application is ready. Test it by running it at least once; then it can be registered as an OLE server application (See Figure 18.8).

Figure 18.8. An OLE 2.0- (and soon DocObject-) compliant server application.

Modify Your Application to Make It an Operational DocObjects Server Application


This section describes how to merge the BINDSCRB sample with the OLE-created server application. This is a five step process:

  1. 1. Insert BINDSCRB sample files into the AppWizard project.
  2. 2. Alter a class name to have DocObject- derived classes instead of OLE-derived classes.
  3. 3. Add/alter strings in the resource string table.
  4. 4. Add a line to have the program registered as a DocObject server application.
  5. 5. Set compiling options.

Inserting BINDSCRB Sample Files into the Project


Copy the following files from the BINDSCRB sample (found on the Visual C++ 4 CD-ROM at D:\MSDEV\SAMPLES\MFC\OLE\BINDSCRB) into your project directory:


BINDDCMT.CPP

BINDDOC.CPP

BINDIPFW.CPP

BINDITEM.CPP

BINDTARG.CPP

BINDVIEW.CPP

MFCBIND.CPP

OLEOBJCT.CPP

PRINT.CPP

BINDDOC.H

BINDIPFW.H

BINDITEM.H

MFCBIND.H

These files implement all necessary interfaces and registration functions for a DocObject server.

Include the .CPP files in your project. Select Files into Project[el] from the Insert menu bar. Select all the preceding .CPP files and validate them. You don't need to include the .H files because they are automatically included.

Altering Classes


The key to integrating the BINDSCRB sample DocObject into your project is altering some class derivation. At least some of your project classes must no longer derive directly from OLE classes. Instead, they must derive from DocObject classes, which derive from OLE classes.

Therefore, you must change the following classes:


class CInPlaceFrame: public COleIPFrameWnd

class CDoServerDoc: public COleServerDoc

class CDoServerSrvItem: public COleServerItem

into:


class CInPlaceFrame: public CDocObjectIPFrameWnd

class CDoServerDoc: public CDocObjectServerDoc

class CDoServerSrvItem: public CDocObjectServerItem

However, you can't just alter these lines; you must also add the correct BINDSCRB header files, change the class names in the IMPLEMENT_DYNCREATE() macros, and so on. All these changes are described in detail in this section.

In the sample provided with this book, all added or altered lines of code for DocObject implementation are presented like this:


// DocObject Addition

    // DocObject Modification

In the IpFrame.h file, add the following bold lines:


#include "BINDIPFW.H"

class CInPlaceFrame: public CDocObjectIPFrameWnd

{

...

In the IpFrame.cpp file, modify the class names (see the bold lines):


IMPLEMENT_DYNCREATE(CInPlaceFrame, CDocObjectIPFrameWnd)

BEGIN_MESSAGE_MAP(CInPlaceFrame, CDocObjectIPFrameWnd)

    //{{AFX_MSG_MAP(CInPlaceFrame)

    ON_WM_CREATE()

    //}}AFX_MSG_MAP

END_MESSAGE_MAP()

In the DoServerDoc.h file, add the following bold lines:


#include "BINDDOC.H"

class CDoServerSrvrItem;

class CDoServerDoc: public CDocObjectServerDoc

{

...

In the DoServerDoc.cpp file, modify the class names (see the bold lines):


IMPLEMENT_DYNCREATE(CDoServerDoc, CDocObjectServerDoc)

BEGIN_MESSAGE_MAP(CDoServerDoc, CDocObjectServerDoc)

    //{{AFX_MSG_MAP(CDoServerDoc)

        // 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

END_MESSAGE_MAP()

In the SrvrItem.h file, add the following bold lines:


#include "BINDITEM.H"

class CDoServerSrvrItem: public CDocObjectServerItem

{

In the SrvrItem.cpp file, modify the class names (even in the class constructor):


IMPLEMENT_DYNAMIC(CDoServerSrvrItem, CDocObjectServerItem)

CDoServerSrvrItem::CDoServerSrvrItem(CDoServerDoc* pContainerDoc)

: CDocObjectServerItem(pContainerDoc, TRUE)

{

    ...

Adding and Modifying Resource Text Strings

Some text strings from the project resources must be altered for the program run correctly. Use the resource editor and open the string table, and then follow these steps:

  1. 1. Add the BIND_IDP_FAILED_TO_REGISTER string. In its caption, type Unable to add Binder compatible Registry. This string is used if the function that registers the program fails.
  2. 2. Modify the IDR_DOSERVTYPE string caption. Without using a carriage return, type \nDoServ\nDoServ\nDoServer Files (*.dos)\n.DOS\nDoServ.Document.1\nDOS DoServ Document\nDOSE\ndose Files. This string is used to register the program, and is slightly different from the original OLE string.

Registering the Project

Finally, in order to register the server application, add the bold text to DoServer.cpp:


#include "stdafx.h"

#include "DoServer.h"

#include "mfcbind.h"

#include "bindipfw.h"

#include "binddoc.h"

...

BOOL CDoServerApp::InitInstance()

{

    ...

    MfcBinderUpdateRegistry(pDocTemplate, OAT_INPLACE_SERVER);

    // m_server.UpdateRegistry(OAT_INPLACE_SERVER);

    ...

}

This function automatically registers your project with your OS registry the first time you run the program, thus recognizing it is a DocObject server application.

Setting Compiling Options

Last but not least, you must include the uuid3.lib library in the project. Select Settings[el] from the Build menu bar. Click the Link tab control and enter uuid3.lib in the Object/library modules field (shown in Figure 18.9).

Figure 18.9. Setting the compiling options.

Running and Testing the Project

Compile the project, run it once in order to have it registered, and run the Office Binder. When you select Add[el], you'll see a DoServ document among the choices (see Figure 18.10). Select DoServ to view your DocObject embedded in the Office Binder (see Figure 18.11).

Figure 18.10. DoServer is recognized as a DocObject server application by the Office Binder.

Figure 18.11. A DoServer document embedded within the Office Binder.

How to Create a Basic DocObject Container


This section describes how to create a basic DocObject container from a bare AppWizard application. This two-step process is far easier than creating a DocObject server.

  1. 1. Create an OLE container AppWizard application.

People who already have an OLE 2.0 container and want to turn it into a DocObject container can skip the first step and go directly to the last one.

Create an AppWizard Application


Create an MFC AppWizard project named DoContainer (you can choose another name; however, I will always refer to DoContainer in the following text). In step 3 of the MFC AppWizard dialog box select the option Container.

The resulting program is an OLE 2.0 container.

Modify It in Order to Have a DocObject Container


Once you have your OLE container, all you have to do is to implement the IOleDocumentSite interface, which contains just one member function. Of course, this is a basic DocObject container. Further interfaces will have to be implemented if you want to fully use DocObject technology.

In the following, the text in bold indicates the code to add (except for the code to add at the end of CntrItem.cpp, where everything is in normal font). The text in normal font represents the code generated by AppWizard.

First, declare the IOleDocumentSite interface within the COleClientItem derived class. In the CntrItem.h file, add


#include <docobj.h>

class CDoContainerDoc;

class CDoContainerView;

class CDoContainerCntrItem: public COleClientItem

{

...

public:

    BEGIN_INTERFACE_PART(DocumentSite, IOleDocumentSite)

        INIT_INTERFACE_PART(CContainerItem, DocumentSite)

        STDMETHOD(ActivateMe)(IOleDocumentView*);

    END_INTERFACE_PART(DocumentSite)

    DECLARE_INTERFACE_MAP()

};

Now, at the top of the CntrItem.cpp file, add the MFC implementation of the interface:


IMPLEMENT_SERIAL(CDoContainerCntrItem, COleClientItem, 0)

BEGIN_INTERFACE_MAP(CDoContainerCntrItem, COleClientItem)

    INTERFACE_PART(CDoContainerCntrItem, IID_IOleDocumentSite, DocumentSite)

END_INTERFACE_MAP()

CDoContainerCntrItem::CDoContainerCntrItem(CDoContainerDoc* pContainer)

Last but not least, add the implementation of the interface member functions at the end of the file.


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

// IOleDocumentSite interface

STDMETHODIMP_(ULONG) CDoContainerCntrItem::XDocumentSite::AddRef()

{

    METHOD_PROLOGUE(CDoContainerCntrItem, DocumentSite)

    return pThis->ExternalAddRef();

}

STDMETHODIMP_(ULONG) CDoContainerCntrItem::XDocumentSite::Release()

{

    METHOD_PROLOGUE(CDoContainerCntrItem, DocumentSite)

    return pThis->ExternalRelease();

}

STDMETHODIMP CDoContainerCntrItem::XDocumentSite::QueryInterface(REFIID iid, LPVOID* ppvObj)

{

    METHOD_PROLOGUE(CDoContainerCntrItem, DocumentSite)

    return pThis->ExternalQueryInterface(&iid, ppvObj);

}

STDMETHODIMP CDoContainerCntrItem::XDocumentSite::ActivateMe(IOleDocumentView *pView)

{

    METHOD_PROLOGUE(CDoContainerCntrItem, DocumentSite)

    // If there is no view sent, create one

    if (!pView)

      {

        IOleDocument *pDocument;

        if (pThis->m_lpObject->QueryInterface(IID_IOleDocument, (void**)&pDocument) != S_OK)

            return E_FAIL;

        if (pDocument->CreateView(&pThis->m_xOleIPSite, NULL, 0, &pView) != S_OK)

            return E_OUTOFMEMORY;

      }

    else

      {

    // Associates the view with the view site

    pView->SetInPlaceSite(&pThis->m_xOleIPSite);

    pView->AddRef();

  }

    // Here, the pView pointer isn't stored.

    // It's up to you to store it if you need it later.

    // Activate the view's user interface

    pView->UIActivate(TRUE);

    // Retrieves the client coordinates and sets the view port

    RECT  rect;

    GetClientRect(pThis->m_pView->m_hWnd, &rect);

    pView->SetRect(&rect);

    // Displays the view

    pView->Show(TRUE);

    return NOERROR;

}

Before compiling, add the uuid3.lib library in the build options. Select Settings[el] from the Build menu bar. Click the Link tab control and enter uuid3.lib in the Object/library modules field (as shown in Figure 18.12).

Compile and execute the program. Then select the Edit menu and choose Insert new object[el]. A dialog box appears and shows all types of objects that can be inserted. In Figure 18.12, the dialog box shows all OLE-compliant servers available (either OLE or DocObject servers). If an OLE-compliant document is selected, the object is inserted within the document already existing. If a DocObject is selected, the DocObject container creates it within its own MDI child window (see Figure 18.13).

Figure 18.12: When inserting an object, the DocObject container shows every OLE document or DocObject available.

Figure 18.13: If a DocObject is inserted, the container inserts it within its own MDI child window.

Summary


OLE Document Objects (DocObjects) represent the latest evolution of OLE, Microsoft's document embedding technology. If this technology is part of ActiveX, whose goal is to «[dg]activate the Internet[dg]», it is because Microsoft sees it as the next Internet standard replacing HTML, Web browsers not displaying HTML pages or Word documents, but DocObjects.

The first part of this chapter has explained what DocObject is and how it works. It then described the full DocObject API, along with how to implement a DocObject server and a DocObject container. Last but not least, it has indicated, step by step, how to create a DocObject server and a DocObject container, either from scratch or from an already existing OLE 2.0 application, thus turning your legacy OLE 2.0 application server or client into a DocObject-compliant server or container application.

Previous Page Page Top TOC Next Page