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 Windowsfrom 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]).
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.
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 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..
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.
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.
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.
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:
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.
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 occurs when the user decides to embed a DocObject. The process begins as standard OLE 2.0 requests, but ends as DocObject-specific processing.
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:
To retrieve an existing view's IOleDocumentView interface, the container must enumerate the existing views. The process for this follows:
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) // NOTEthe 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:
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.
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 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 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.
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**);
IOleInPlaceSite* Container's IOleInPlaceSite interface, which must be associated with the new view. If NULL, the view must be associated with a container's view site by calling IOleDocumentView::SetInPlaceSite().
IStream* Stream from which the view should initialize itself. If NULL, the created view maintains a standard initial state.
DWORD Must be zero.
IOleDocumentView** Where to send the new view's IOleDocumentView* interface pointer.
S_OK The view was created successfully.
E_POINTER The IOleDocumentView** argument is NULL.
E_OUTOFMEMORY No more memory to create the view.
E_UNEXPECTED Unknown error.
E_FAIL This DocObject can only create a single view, which already exists.
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.
S_OK The status was sent successfully.
E_POINTER The DWORD* argument is NULL.
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**);
IenumOleDocumentViews** Where to send the enumerator object.
IoleDocumentView** Where to send the enumerator object for a single view.
S_OK The object was successfully sent.
E_POINTER The appropriate argument is NULL.
E_OUTOFMEMORY No more memory to create the enumerator.
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.
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 Number of IOleDocumentView* values to put in the array pointed to by the second argument.
IOleDocumentView** Array where view interfaces are stored.
ULONG* Where to send the actual number of document views enumerated. If NULL, the first argument must be 1.
S_OK The requested number of views has been copied.
S_FALSE There were fewer copied views than requested because there were not enough existing views.
E_POINTER The IOleDocumentView* array is NULL.
E_INVALIDARG The values of the first and last arguments don't match.
E_UNEXPECTED Unknown error.
E_OUTOFMEMORY No more memory to create the enumerator.
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.
S_OK The requested number of views has been skipped.
S_FALSE There were fewer skipped views than requested because there were not enough views left. The enumerator is now positioned at the end of the list.
E_INVALIDARG Invalid argument value.
E_UNEXPECTED Unknown error.
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);
S_OK The operation succeeded.
S_FALSE The operation failed.
E_UNEXPECTED Unknown error.
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.
S_OK The enumerator was successfully cloned.
E_NOTIMPL Cloning is not supported.
E_POINTER Invalid argument.
E_UNEXPECTED Unknown error.
E_OUTOFMEMORY Not enough memory to create the enumerator.
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.
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.
S_OK Association or disassociation succeeded.
E_FAIL An error occurred.
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.
S_OK The view site was successfully sent.
E_FAIL An error occurred.
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.
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.
S_OK The view was resized.
E_FAIL An error occurred.
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.
S_OK The view coordinates were successfully retrieved.
E_UNEXPECTED An error occurred.
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 view port.
LPRECT Client coordinates of the horizontal scroll bar.
LPRECT Client coordinates of the vertical scroll bar.
LPRECT Client coordinates of the size box.
S_OK The view was successfully resized.
E_NOTIMPL Function not supported be the DocObject.
E_FAIL An error occurred.
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.
S_OK The view has successfully shown or hidden itself.
E_OUTOFMEMORY Not enough memory.
E_FAIL The operation failed.
E_UNEXPECTED An error occurred.
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.
S_OK The operation succeeded.
E_OUTOFMEMORY Not enough memory.
E_FAIL The operation failed.
E_UNEXPECTED An error occurred.
The IOleDocumentView::Open() member function creates a new window that displays the view.
STDMETHOD(Open)(void);
S_OK The view successfully created another window.
E_OUTOFMEMORY Not enough memory to create a window.
E_NOTIMPL Function not supported.
E_FAIL The operation failed.
E_UNEXPECTED An error occurred.
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.
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.
S_OK The view state was successfully saved.
E_POINTER Invalid argument.
E_NOTIMPL Function not supported.
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.
S_OK The view state was successfully loaded.
E_POINTER Invalid argument.
E_NOTIMPL Function not supported.
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**);
IOleInPlaceSite* In-place site to be associated with the clone.
IOleDocumentView** Where to store the pointer of the new view.
S_OK The view was successfully cloned.
E_POINTER Invalid argument.
E_FAIL The DocObject supports only a single view.
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."
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.
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);
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:
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.
Here are a few more things to remember when implementing a DocObject server application:
The implementation of a DocObject container requires the creation of DocObject-specific interfaces and several other functions. A list of the necessary interfaces follows:
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 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.
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.
S_OK The DocObject container successfully activated the view.
E_OUTOFMEMORY Not enough memory to create the view.
E_FAIL An error occurred in the process.
The IOleDocumentSite::ActivateMe() member function performs the following tasks:
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.
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.
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.
There are still a few things you should know in order to implement a DocObject container:
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 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.
As its names implies, the PAGERANGE structure contains a range of pages.
nFrontPage LONG The first page to print.
nToPage LONG The last page to print.
The PAGESET structure identifies a set of page ranges, and optionally indicates information such as odd or even pages.
cbStruct ULONG Number of bytes in this PAGESET structure. Must be a multiple of 4.
FOddPages BOOL If TRUE, print only odd pages.
fEvenPages BOOL If TRUE, print only even pages.
cPageRange ULONG Number of page ranges in rgPages.
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 contains miscellaneous values.
PRINTFLAG_MAYBOTHERUSER Indicates that the printing process can interact with the user.
PRINTFLAG_PROMPTUSER Indicates the normal print dialog box must be used.
PRINTFLAG_USERMAYCHANGEPRINTER The user may change the printer.
PRINTFLAG_RECOMPOSETODEVICE The document must recompose itself to the printer.
PRINTFLAG_DONTACTUALLYPRINT Only simulate printing.
PRINTFLAG_PRINTTOFILE Print to a file.
The IPrint::SetInitialPageNum() member function the first page to be printed.
STDMETHOD(SetInitialPageNum)(LONG);
LONG First page number.
S_OK The first page was set as desired.
E_FAIL The first page could not set as desired.
E_UNEXPECTED An error occurred.
The IPrint::GetPageInfo() member function returns information about pages through its two arguments.
STDMETHOD(GetPageInfo)(LONG*, LONG*);
LONG* Where to copy the page number of the first page. A NULL value means the caller doesn't need this number.
LONG* Where to copy the total number of pages in the document. A NULL value means the caller doesn't need this number.
S_OK The operation succeeded.
E_UNEXPECTED An error occurred.
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*);
DWORD A PRINTFLAG enumeration.
DVTARGETDEVICE** The target device.
PAGESET** Pages to be printed.
STGMEDIUM** Document-specific printing functions.
IContinueCallback* A callback interface allowing the user to cancel printing.
LONG The starting page number.
LONG* Where to copy the actual number of successfully printed pages.
LONG* Where to copy the last page number.
S_OK The pages were successfully printed.
PRINT_E_CANCELLED The operation was canceled.
PRINT_E_NOSUCHPAGE A page that was to be printed does not exist.
E_UNEXPECTED An error occurred.
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.
The IContinueCallback::FContinue() member function administers the continuation (or discontinuation) of a given operation.
STDMETHOD(FContinue)(void);
S_OK Continue the operation.
E_FALSE Cancel the operation as soon as possible.
The IContinueCallback::FContinuePrinting() member function administers the continuation (or discontinuation) of a given printing process.
STDMETHOD(FContinuePrinting)(LONG, LONG, wchar_t*);
LONG Actual number of printed pages.
LONG The page number of the page being printed.
wchar_t* Status message.
S_OK Continue printing.
S_FALSE Cancel printing as soon as possible.
E_UNEXPECTED An error occurred.
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 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 contains values describing the status of a given command.
OLECMDF_SUPPORTED The command is supported.
OLECMDF_ENABLED The command is enabled.
OLECMDF_LATCHED The command is an on/off switch, and is currently on.
OLECMDF_NINCHED The command is an on/off switch, but the state is indeterminate.
The OLECMD structure contains a command identifier and its status.
cmdID ULONG Command identifier.
cmdf DWORD OLECMDF enumeration.
This enumeration contains flags that describe that information to return after the command was executed.
OLECMDTEXTF_NONE No extra information is requested.
OLECMDTEXTF_NAME Return the command name.
OLECMDTEXTF_STATUS Return a status string.
The OLECMDTEXT structure is used to return text name or status string.
cmdtextf DWORD OLECMDTEXTF enumeration.
cwActual ULONG Where to store the number of characters actually written into rgwz.
cwBuf ULONG Size of the string buffer.
rgwz wchar_t Array that will receive the output string.
The OLECMDEXECOPT enumeration contains flags that describe what information should be returned after the command was executed.
OLECMDTEXTF_NONE No extra information is requested.
OLECMDTEXTF_NAME Return the command name.
OLECMDTEXTF_STATUS Return a status string.
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_OPEN File Open
OLECMDID_NEW File New
OLECMDID_SAVE File Save
OLECMDID_SAVEAS File Save As
OLECMDID_SAVECOPY 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 Selection
OLECMDID_ZOOM View Zoom
OLECMDID_GETZOOMRANGE Get Zoom Range.
OLECMDID_UPDATECOMMANDS Tells when one can retrieve the commands status at a convenient time.
OLECMDID_REFRESH Display refresh request.
OLECMDID_STOP Stop all current processing.
OLECMDID_HIDETOOLBARS Hide the tool bars of the object.
OLECMDID_SETPROGRESSMAX Sets the maximum value of the progress indicator owned by the object.
OLECMDID_SETPROGRESSPOS Sets the current value of the progress indicator.
OLECMDID_SETPROGRESSTEXT Sets the text of the progress indicator.
OLECMDID_SETTITLE Sets the window title.
The commands OLECMDID_ZOOM and OLECMDID_GETZOOMRANGE need some arguments, passed through IOleCommandTarget::Exec().
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:
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.
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*);
const GUID* Identifier of the command group. A NULL value indicates the standard group.
ULONG Number of commands in the OLECMD array.
OLECMD* Array of OLECMD structures.
OLECMDTEXT* Where to send name and/or status information. A NULL value indicates that the caller doesn't need this information.
S_OK Continue printing.
E_POINTER The third argument is NULL.
E_UNEXPECTED An unexpected error occurred.
E_FAIL An error occurred.
OLECMDERR_E_UNKNOWNGROUP The group type (first argument) is invalid.
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*);
const GUID* Identifier of the command group. A NULL value indicates the standard group.
DWORD The command to execute.
DWORD One or more values from OLECMDEXECOPT enumeration.
VARIANTARG* Input arguments. Might be NULL.
VARIANTARG* Output values. Might be NULL.
S_OK Continue printing.
E_UNEXPECTED An unexpected error occurred
E_FAIL An error occurred.
OLECMDERR_E_UNKNOWNGROUP The group type (first argument) is invalid.
OLECMDERR_E_NOTSUPPORTED The command is invalid.
OLECMDERR_DISABLED The command is currently disabled.
OLECMDERR_NOHELP The caller asked for help, which is not available.
OLECMDERR_CANCELLED The user canceled an operation.
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 >
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:
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:
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.
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.
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.
This section describes how to merge the BINDSCRB sample with the OLE-created server application. This is a five step process:
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.
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) // NOTEthe 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) { ...
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:
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.
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.
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.
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.
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 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.
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.13: If a DocObject is inserted, the container inserts it within its own MDI child window.
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.