Special Edition Using Visual C++ 6

Previous chapterNext chapterContents


- 15 -

Building an ActiveX Server Application


Just as AppWizard builds ActiveX containers, it also builds ActiveX servers. However, unlike containers, the AppWizard code is complete, so there isn't much work to do for improving the AppWizard code. This chapter builds a version of ShowString that is only a server and discusses how to build another version that is both a container and a server. You also learn about ActiveX documents and how they can be used in other applications.

Adding Server Capabilities to ShowString

Like Chapter 14, "Building an ActiveX Container Application," this chapter starts by building an ordinary server application with AppWizard and then adds the functionality that makes it ShowString. This is far quicker than adding ActiveX functionality to ShowString because ShowString doesn't have much code and can be written quickly.

AppWizard's Server Boilerplate

Build the new ShowString in a different directory, making almost exactly the same AppWizard choices as when you built versions of ShowString in Chapter 8, "Building a Complete Application: ShowString," and Chapter 14. Call it ShowString, and choose an MDI application with no database support. In AppWizard's Step 3, select full server as your compound document support. This enables the check box for ActiveX document support. Leave this deselected for now. Later in this chapter you see the consequences of selecting this option. Continue the AppWizard process, selecting a docking toolbar, initial status bar, printing and print preview, context sensitive Help, and 3D controls. Finally, select source file comments and a shared DLL. Finish AppWizard and, if you want, build the project.


NOTE:: Even though the technology is now called ActiveX, the AppWizard dialog boxes refer to compound document support. Many of the class names that are used throughout this chapter have Ole in their names as well. Although Microsoft has changed the name of the technology, it has not propagated that change throughout Visual C++ yet. You will have to live with these contradictions for awhile. 

There are many differences between the application you have just generated and a do-nothing application without ActiveX server support. These differences are explained in the next few sections.

Menus  There are two new menus in an ActiveX server application. The first, called IDR_SHOWSTTYPE_SRVR_IP, is shown in Figure 15.1. When an item is being edited in place, the container in-place menu (called IDR_SHOWSTTYPE_CNTR_IP in the container version of ShoeString) is combined with the server in-place menu, IDR_SHOWSTTYPE_SRVR_IP, to build the in-place menu as shown in Figure 15.2. The double separators in each partial menu show where the menus are joined.

FIG. 15.1 AppWizard adds another menu for editing in place.

FIG. 15.2 The container and server in-place menus are interlaced during in-place editing.

The second new menu is IDR_SHOWSTTYPE_SRVR_EMB, used when an embedded item is being edited in a separate window. Figure 15.3 shows this new menu next to the more familiar IDR_SHOWSTTYPE menu, which is used when ShowString is acting not as a server but as an ordinary application. The File menus have different items: IDR_SHOWSTTYPE_SRVR_EMB has Update in place of Save, and Save Copy As in place of Save As. This is because the item the user is working on in the separate window is not a document of its own, but is embedded in another document. File, Update updates the embedded item; File, Save Copy As doesn't save the whole document, just a copy of this embedded portion.

FIG. 15.3 The embedded menu has different items under File than the usual menu.

CShowStringApp  Another member variable has been added to this class. It is declared in ShowString.h as:

COleTemplateServer m_server;

COleTemplateServer handles the majority of the work involved in connecting documents to code, as you will see.

The following line is added at the top of ShowString.cpp:

#include "IpFrame.h"

This sets up the class CInPlaceFrame, discussed later in this chapter. Just before InitInstance(), the lines shown in Listing 15.1 are added.

Listing 15.1  Excerpt from ShowString.cpp--CLSID

// This identifier was generated to be statistically unique for
// your app. You may change it if you prefer to choose a specific
// identifier.
// {0B1DEE40-C373-11CF-870C-00201801DDD6}
static const CLSID clsid =
{ 0xb1dee40, 0xc373, 0x11cf,
     { 0x87, 0xc, 0x0, 0x20, 0x18, 0x1, 0xdd, 0xd6 } };

The numbers will be different in your code. This Class ID identifies your server application and document type. Applications that support several kinds of documents (for example, text and graphics) use a different CLSID for each type of document.

As it did for the OLE container version of ShowString, CShowStringApp::InitInstance() has several changes from the non-ActiveX ShowString you developed in Chapter 8. The code in Listing 15.2 initializes the ActiveX (OLE) libraries.

Listing 15.2  Excerpt from ShowString.cpp--Initializing Libraries

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

While still in CShowStringApp::InitInstance(), after the CMultiDocTemplate is initialized but before the call to AddDocTemplate(), the following line is added to register the menu used for in-place editing and for separate-window editing:

pDocTemplate->SetServerInfo(
          IDR_SHOWSTTYPE_SRVR_EMB, IDR_SHOWSTTYPE_SRVR_IP,
          RUNTIME_CLASS(CInPlaceFrame));

A change that was not in the container version is connecting the template for the document to the class ID, like this:

// Connect the COleTemplateServer to the document template.
     //  The COleTemplateServer creates new documents on behalf
     //  of requesting OLE containers by using information
     //  specified in the document template.
     m_server.ConnectTemplate(clsid, pDocTemplate, FALSE);

Now when a user chooses Create New when inserting an object, the document used for that creation will be available.

When a server application is launched to edit an item in place or in a separate window, the system DLLs add /Embedding to the invoking command line. But if the application is already running, and it is an MDI application, a new copy is not launched. Instead, a new MDI window is opened in that application. That particular piece of magic is accomplished with one function call, as shown in Listing 15.3.

Listing 15.3  Excerpt from ShowString.cpp--Registering Running MDI Apps

     // Register all OLE server factories as running.  This enables the
     //  OLE libraries to create objects from other applications.
     COleTemplateServer::RegisterAll();
     // Note: MDI applications register all server objects without regard
     //  to the /Embedding or /Automation on the command line.

After parsing the command line, the AppWizard boilerplate code checks to see if this application is being launched as an embedded (or automation) application. If so, there is no need to continue with the initialization, so this function returns, as shown in Listing 15.4.

Listing 15.4  Excerpt from ShowString.cpp--Checking How the App was Launched

     // Check to see if launched as OLE server
     if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
     {
          // Application was run with /Embedding or /Automation.
          // Don't show the main window in this case.
          return TRUE;
     }

If the application is being run standalone, execution continues with a registration update:

// When a server application is launched standalone, it is a good idea
     //  to update the system Registry in case it has been damaged.
     m_server.UpdateRegistry(OAT_INPLACE_SERVER);

ActiveX information is stored in the Registry. (The Registry is discussed in Chapter 7, "Persistence and File I/O.") When a user chooses Insert, Object or Edit, Insert Object, the Registry provides the list of object types that can be inserted. Before ShowString can appear in such a list, it must be registered. Many developers add code to their install programs to register their server applications, and MFC takes this one step further, registering the application every time it is run. If the application files are moved or changed, the registration is automatically updated the next time the application is run standalone.

CShowStringDoc  The document class, CShowStringDoc, now inherits from COleServerDoc rather than CDocument. As well, the following line is added at the top of ShowStringdoc.cpp:

#include "SrvrItem.h"

This header file describes the server item class, CShowStringSrvrItem, discussed in the CShowStringSrvrItem subsection of this section. The constructor, CShowStringDoc::CShowStringDoc(), has the following line added:

     EnableCompoundFile();

This turns on the use of compound files.

There is a new public function inlined in the header file so that other functions can access the server item:

CShowStringSrvrItem* GetEmbeddedItem()
          { return (CShowStringSrvrItem*)COleServerDoc::GetEmbeddedItem(); }

This calls the base class GetEmbeddedItem(), which in turn calls the virtual function OnGetEmbeddedItem(). That function must be overridden in the ShowString document class as shown in Listing 15.5.

Listing 15.5  ShowStringDoc.cpp--CShowStringDoc::OnGetEmbeddedItem()

COleServerItem* CShowStringDoc::OnGetEmbeddedItem()
{
     // OnGetEmbeddedItem is called by the framework to get the 
     // COleServerItem that is associated with the document.  
     // It is only called when necessary.
     CShowStringSrvrItem* pItem = new CShowStringSrvrItem(this);
     ASSERT_VALID(pItem);
     return pItem;
}

This makes a new server item from this document and returns a pointer to it.

CShowStringView  The view class has a new entry in the message map:

     ON_COMMAND(ID_CANCEL_EDIT_SRVR, OnCancelEditSrvr)

This catches ID_CANCEL_EDIT_SRVR, and the cancellation of editing is in place. An accelerator has already been added to connect this message to Esc. The function that catches it looks like this:

void CShowStringView::OnCancelEditSrvr()
{
     GetDocument()->OnDeactivateUI(FALSE);
}

This function simply deactivates the item. There are no other view changes--server views are so much simpler than container views.

CShowStringSrvrItem  The server item class is a completely new addition to ShowString. It provides an interface between the container application that launches ShowString to and opens a ShowString document. It describes an entire ShowString document that is embedded into another document, or a portion of a ShowString document that is linked to part of a container document. It has no member variables other than those inherited from the base class, COleServerItem. It has overrides for eight functions. They are as follows:

The constructor simply passes the document pointer along to the base class. The destructor does nothing. GetDocument() is an inline function that calls the base class function with the same name and casts the result. AssertValid() and Dump() are debug functions that simply call the base class functions. Serialize() actually does some work, as shown in Listing 15.6.

Listing 15.6  SrvrItem.cpp--CShowStringSrvrItem::Serialize()

void CShowStringSrvrItem::Serialize(CArchive& ar)
{
     // CShowStringSrvrItem::Serialize will be called by the framework if
     //  the item is copied to the clipboard.  This can happen automatically
     //  through the OLE callback OnGetClipboardData.  A good default for
     //  the embedded item is simply to delegate to the document's Serialize
     //  function.  If you support links, then you will want to serialize
     //  just a portion of the document.
     if (!IsLinkedItem())
     {
          CShowStringDoc* pDoc = GetDocument();
          ASSERT_VALID(pDoc);
          pDoc->Serialize(ar);
     }

There is no need to duplicate effort here. If the item is embedded, it is an entire document, and that document has a perfectly good Serialize() that can handle the work. AppWizard doesn't provide boilerplate to handle serializing a linked item because it is application-specific. You would save just enough information to describe what part of the document has been linked in, for example, cells A3 to D27 in a spreadsheet. This doesn't make sense for ShowString, so don't add any code to Serialize().

You may feel that OnDraw() is out of place here. It is normally thought of as a view function. But this OnDraw() draws a depiction of the server item when it is inactive. It should look very much like the view when it is active, and it makes sense to share the work between CShowStringView::OnDraw() and CShowStringSrvrItem::OnDraw(). The boilerplate that AppWizard provides is in Listing 15.7.

Listing 15.7  SrvrItem.cpp--CShowStringSrvrItem::OnDraw()

BOOL CShowStringSrvrItem::OnDraw(CDC* pDC, CSize& rSize)
{
     CShowStringDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: set mapping mode and extent
     //  (The extent is usually the same as the size returned from OnGetExtent)
     pDC->SetMapMode(MM_ANISOTROPIC);
     pDC->SetWindowOrg(0,0);
     pDC->SetWindowExt(3000, 3000);
     // TODO: add drawing code here.  Optionally, fill in the HIMETRIC extent.
     //  All drawing takes place in the metafile device context (pDC).
     return TRUE;
}

This will change a great deal, but it's worth noting now that unlike CShowStringView::OnDraw(), this function takes two parameters. The second is the size in which the inactive depiction is to be drawn. The extent, as mentioned in the boilerplate comments, typically comes from OnGetExtent(), which is shown in Listing 15.8.

Listing 15.8  SrvrItem.cpp--CShowStringSrvrItem:: OnGetExtent()

BOOL CShowStringSrvrItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
     // Most applications, like this one, only handle drawing the content
     //  aspect of the item.  If you wish to support other aspects, such
     //  as DVASPECT_THUMBNAIL (by overriding OnDrawEx), then this
     //  implementation of OnGetExtent should be modified to handle the
     //  additional aspect(s).
     if (dwDrawAspect != DVASPECT_CONTENT)
          return COleServerItem::OnGetExtent(dwDrawAspect, rSize);
     // CShowStringSrvrItem::OnGetExtent is called to get the extent in
     //  HIMETRIC units of the entire item.  The default implementation
     //  here simply returns a hard-coded number of units.
     CShowStringDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: replace this arbitrary size
     rSize = CSize(3000, 3000);   // 3000 x 3000 HIMETRIC units
     return TRUE;
}

You will replace this with real code very shortly.

CInPlaceFrame  The in-place frame class, which inherits from COleIPFrameWnd, handles the frame around the server item and the toolbars, status bars, and dialog-box bars, collectively known as control bars, that it displays. It has the following three protected member variables:

     CToolBar    m_wndToolBar;
     COleResizeBar   m_wndResizeBar;
     COleDropTarget m_dropTarget;

The CToolBar class is discussed in Chapter 9, "Status Bars and Toolbars." COleDropTarget is discussed in the drag and drop section of Chapter 14. COleResizeBar looks just like a CRectTracker, which was used extensively in Chapter 14, but allows the resizing of a server item rather than a container item.

The following are the seven member functions of CInPlaceFrame:

The constructor and destructor do nothing. AssertValid() and Dump() are debug functions that simply call the base class functions. OnCreate() actually has code, shown in Listing 15.9.

Listing 15.9  IPFrame.cpp--CInPlaceFrame::OnCreate()

int CInPlaceFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
     if (COleIPFrameWnd::OnCreate(lpCreateStruct) == -1)
          return -1;
     // CResizeBar implements in-place resizing.
     if (!m_wndResizeBar.Create(this))
     {
          TRACE0("Failed to create resize bar\n");
          return -1;      // fail to create
     }
     // By default, it is a good idea to register a drop-target that does
     //  nothing with your frame window.  This prevents drops from
     //  "falling through" to a container that supports drag-drop.
     m_dropTarget.Register(this);
     return 0;
}

This function catches the WM_CREATE message that is sent when an in-place frame is created and drawn onscreen. It calls the base class function and then creates the resize bar. Finally, it registers a drop target so that if anything is dropped over this in-place frame, it is dropped on this server rather than the underlying container.

When a server document is activated in place, COleServerDoc::ActivateInPlace() calls CInPlaceFrame::OnCreateControlBars(), which is shown in Listing 15.10.

Listing 15.10  IPFrame.cpp--CInPlaceFrame::OnCreateControlBars()

BOOL CInPlaceFrame::OnCreateControlBars(CFrameWnd* pWndFrame, 
                                        CFrameWnd* pWndDoc)
{
     // Set owner to this window, so messages are delivered to correct app
     m_wndToolBar.SetOwner(this);
     // Create toolbar on client's frame window
     if (!m_wndToolBar.Create(pWndFrame) || 
          !m_wndToolBar.LoadToolBar(IDR_SHOWSTTYPE_SRVR_IP))
     {
          TRACE0("Failed to create toolbar\n");
          return FALSE;
     }
     // TODO: Remove this if you don't want tool tips or a resizeable toolbar
     m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
          CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
     // TODO: Delete these three lines if you don't want the toolbar to
     //  be dockable
     m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
     pWndFrame->EnableDocking(CBRS_ALIGN_ANY);
     pWndFrame->DockControlBar(&m_wndToolBar);
     return TRUE;
}

This function creates a docking, resizable toolbar with ToolTips, docked against the edge of the main frame window for the application.


TIPP: If you are developing an MDI application and prefer the toolbar against the document frame, use pWndDoc instead of PWndFrame, in the call to m_wndToolBar.Create() but be sure to check that it is not NULL.

The last function in CInPlaceFrame is PreCreateWindow(). At the moment, it just calls the base class, as shown in Listing 15.11.

Listing 15.11  IPFrame.cpp--CInPlaceFrame::PreCreateWindow()

BOOL CInPlaceFrame::PreCreateWindow(CREATESTRUCT& cs)
{
     // TODO: Modify the Window class or styles here by modifying
     //  the CREATESTRUCT cs
     return COleIPFrameWnd::PreCreateWindow(cs);
}

This function is called before OnCreate() and sets up the styles for the frame window through a CREATESTRUCT.


CAUTION: Modifying these styles is not for the faint of heart. The Microsoft documentation recommends reading the source code for all the classes in the hierarchy of your CInPlaceFrame (Cwnd, CFrameWnd, COleIPFrameWnd) to see what CREATESTRUCT elements are already set before making any changes. For this sample application, don't change the CREATESTRUCT.

Shortcomings of This Server  Apart from the fact that the starter application from AppWizard doesn't show a string, what's missing from this server? The OnDraw() and GetExtent()TODOs are the only significant tasks left for you by AppWizard. Try building ShowString, and then run it once standalone just to register it.

Figure 15.4 shows the Object dialog box in Microsoft Word, reached by choosing Insert, Object. ShowString appears in this list as ShowSt Document--not surprising considering the menu name was IDR_SHOWSTTYPE. Developer Studio calls this document a ShowSt document. This setting could have been overriden in AppWizard by choosing the Advanced button in Step 4 of AppWizard. Figure 15.5 shows this dialog box and the long and short names of the file type.

FIG. 15.4 The ShowString document type, called ShowSt document, now appears in the Object dialog box when inserting a new object into a Word document.

FIG. 15.5 The Advanced Options dialog box of Step 4 in AppWizard provides an opportunity to change the name of the file type.

So, the file type names used by the Registry have been set incorrectly for this project. The next few pages take you on a tour of the way file type names are stored and show you how difficult they are to change.

The file type name has been stored in the string table. It is the caption of the IDR_SHOWSTTYPE resource, and AppWizard has set it to:

\nShowSt\nShowSt\n\n\nShowString.Document\nShowSt Document

To look at this string, choose String Table from the Resource View, open the only string table there, click IDR_SHOWSTTYPE once to highlight it, and choose View, Properties (or double-click the string). This string is saved in the document template when a new one is constructed in CShowStringApp::InitInstance(), like this:

Listing 15.12   ShowString.cpp--Excerpt from ShowStringApp::InitInstance()

     pDocTemplate = new CMultiDocTemplate(
          IDR_SHOWSTTYPE,
          RUNTIME_CLASS(CShowStringDoc),
          RUNTIME_CLASS(CChildFrame), // custom MDI child frame
          RUNTIME_CLASS(CShowStringView));

The caption of the menu resource holds seven strings, and each is used by a different part of the framework. They are separated by the newline character \n. The seven strings, their purposes, and the values provided by AppWizard for ShowString are as follows:

Look again at Figure 15.5 and you can see where these values came from. Try changing the last entry. In the Properties dialog box, change the caption so that the last element of the string is ShowString Document and press Enter. Build the project. Run it once and exit. In the output section of Developer Studio, you see these messages:

Warning: Leaving value `ShowSt Document' for key `ShowString.Document'
 in registry
 intended value was `ShowString Document'.
Warning: Leaving value `ShowSt Document' for key
 `CLSID\{0B1DEE40-C373-11CF-870C-00201801DDD6}' in registry
 intended value was `ShowString Document'.

This means that the call to UpdateRegistry() did not change these two keys. There is a way to provide parameters to UpdateRegistry() to insist that the keys be updated, but it's even more complicated than the route you will follow. Because no code has been changed from that provided by AppWizard, it's much quicker to delete the ShowString directory and create it again, this time setting the long file type to ShowString Document.


CAUTTION: Always test AppWizard-generated code before you add changes of your own. Until you are familiar with every default you are accepting, it is worth a few moments to see what you have before moving on. Rerunning AppWizard is easy, but if you've made several hours worth of changes and then decide to rerun it, it's not such a simple task.

Close Visual Studio, delete the ShowString folder entirely, and generate a new application with AppWizard as before. This time, in Step 4, click the Advanced button and change the file type names as shown in Figure 15.6. After you click Finish, AppWizard asks whether you wish to reuse the existing CLSID, as shown in Figure 15.7. Click Yes and then OK to create the project. This makes a new showstring.reg file for you with the correct Registry values.

FIG. 15.6 The Advanced Options dialog box of Step 4 of AppWizard is the place to improve the file type names.

FIG. 15.7 AppWizard makes sure that you don't accidentally reuse a CLSID.

This changes the string table as well as the showstring.reg file, so you might be tempted to build and run the application to make this fix complete. It's true, when you run the application, it will update the Registry for you, using the values from the new string table. Alas, the registration update will fail yet again. If you were to try it, these messages would appear in the output window:

Warning: Leaving value `ShowSt Document' for key
 `ShowString.Document' in registry
 intended value was `ShowString Document'.
Warning: Leaving value `ShowSt Document' for key
 `CLSID\{0B1DEE40-C373-11CF-870C-00201801DDD6}' in registry
 intended value was `ShowString Document'.
Warning: Leaving value `ShowSt' for key
 `CLSID\{0B1DEE40-C373-11CF-870C-00201801DDD6}\AuxUserType\2'
 in registry
 intended value was `ShowString'.

So, how do you get out of this mess? You have to edit the Registry. If that doesn't sound intimidating, it should. Messing with the Registry can leave your system unusable. But you are not going to go in by hand and change keys; instead, you are going to use the Registry file that AppWizard generated for you. Here's what to do:

1. Choose Start, Run.

2. Type regedit and press Enter.

3. Choose Registry, Import Registry File from the Registry Editor menu.

4. Using the Import Registry File dialog box, move through your folders until you reach the one where the replacement ShowString server was just generated by AppWizard, as shown in Figure 15.8. Click Open.

5. A success message is shown. Click OK.

6. Close the Registry Editor.

FIG. 15.8 Registry files generated by AppWizard have the extension .reg.


Now if you run ShowString again, those error messages don't appear. Run Word again and choose Insert, Object. The Object dialog box now has a more meaningful ShowString entry, as shown in Figure 15.9.


NOTE: There are three morals to this side trip. The first is that you should think really carefully before clicking Finish on the AppWizard dialog box. The second is that you cannot ignore the Registry if you are an ActiveX programmer. The third is that anything can be changed if you have the nerve for it. 

Click OK on the Object dialog box to insert a ShowString object into the Word document. You can immediately edit it in place, as shown in Figure 15.10. You can see that the combined server and container in-place menus are being used. There's not much you can do to the embedded object at this point because the ShowString code that actually shows a string has not been added. Press Esc to finish editing in place, and the menus return to the usual Word menus, as shown in Figure 15.11.

FIG. 15.9 The updated long file type name appears in the Object dialog box of other applications.

FIG. 15.10 While editing in place, the in-place menus replace the Word menus.

FIG. 15.11 When the object is inactive, Word reminds the user of the object type.

Although this server doesn't do anything, it is a perfectly good server. You can resize and move the embedded item while it is active or inactive, and everything operates exactly as you expect. All that remains is to restore the ShowString functionality.

Showing a String Again

As you did in Chapter 14, it is time to add the ShowString functionality to this version of the program. If you went through this process before, it will be even quicker this time. Remember to open the ShowString files from Chapter 8, so that you can copy code and resources from the functional ShowString to the do-nothing ActiveX server you have just created and explored. (If you didn't code along in Chapter 8, you can get the completed code on the Web at www.mcp.com/info or www.gregcons.com/uvc6.htm.) Here's what to do:

1. In ShowStringDoc.h, add the private member variables and public Get functions to the class.

2. In CShowStringDoc::Serialize(), paste in the code that saves or restores these member variables.

3. In CShowStringDoc::OnNewDocument(), paste in the code that initializes the member variables. Change the default values of horizcenter and vertcenter to FALSE. You'll see why towards the end of the chapter.

4. Copy the entire Tools menu from the old ShowString to the new server ShowString. Choose File, Open to open the old ShowString.rc, open the IDR_SHOWSTTYPE menu, click the Tools menu, and choose Edit, Copy. Open the new ShowString's IDR_SHOWSTTYPE menu, click the Window menu, and choose Edit, Paste.

5. Paste the Tools menu into the IDR_SHOWSTTYPE_SRVR_IP (before the separator bars) and IDR_SHOWSTTYPE_SRVR_EMB menus in the same way.

6. Add the accelerator Ctrl+T for ID_TOOLS_OPTIONS as described in Chapter 8. Add it to all three accelerators.

7. Delete the IDD_ABOUTBOX dialog box from the new ShowString. Copy IDD_ABOUTBOX and IDD_OPTIONS from the old ShowString to the new.

8. While IDD_OPTIONS has focus, choose View, ClassWizard. Create the COptionsDialog class as in the original ShowString.

9. Use ClassWizard to arrange for CShowStringDoc to catch the ID_TOOLS_OPTIONS command.

10. In ShowStringDoc.cpp, replace the ClassWizard version of CShowStringDoc::OnToolsOptions() with the one that puts up the dialog box.

11. In ShowStringDoc.cpp, add #include "OptionsDialog.h" after the #include statements already present.

12. Use ClassWizard to connect the dialog box controls to COptionsDialog member variables as before. Connect IDC_OPTIONS_BLACK to m_color, IDC_OPTIONS_HORIZCENTER to m_horizcenter, IDC_OPTIONS_STRING to m_string, and IDC_OPTIONS_VERTCENTER to m_vertcenter.

To confirm you've made all the changes correctly, build the project--there should be no errors.

You haven't restored CShowStringView::OnDraw() yet because there are actually going to be two OnDraw() functions. The first is in the view class, shown in Listing 15.13. It draws the string when ShowString is running standalone and when the user is editing in place, and it's the same as in the old version of ShowString. Just copy it into the new one.

Listing 15.13  ShowStringView.cpp--CShowStringView::OnDraw()

void CShowStringView::OnDraw(CDC* pDC)
{
     CShowStringDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     COLORREF oldcolor;
     switch (pDoc->GetColor())
     {
     case 0:
          oldcolor = pDC->SetTextColor(RGB(0,0,0)); //black
          break;
     case 1:
          oldcolor = pDC->SetTextColor(RGB(0xFF,0,0)); //red
          break;
     case 2:
          oldcolor = pDC->SetTextColor(RGB(0,0xFF,0)); //green
          break;
     }
     int DTflags = 0;
     if (pDoc->GetHorizcenter())
     {
          DTflags |= DT_CENTER;
     }
     if (pDoc->GetVertcenter())
     {
          DTflags |= (DT_VCENTER|DT_SINGLELINE);
     }
     CRect rect;
     GetClientRect(&rect);
     pDC->DrawText(pDoc->GetString(), &rect, DTflags);
     pDC->SetTextColor(oldcolor);
}

When the embedded ShowString item is inactive, CShowStringSrvrItem::OnDraw() draws it. The code in here should be very similar to the view's OnDraw, but because it is a member of CShowStringSrvrItem rather than CShowStringView, it doesn't have access to the same member variables. So although there is still a GetDocument() function you can call, GetClientRect doesn't work. It's a member of the view class but not of the server item class. You use a few CDC member functions instead. It's a nice touch to draw the item slightly differently to help remind the user that it is not active, as shown in Listing 15.14. You can paste in the drawing code from the view's OnDraw(), but change the colors slightly to give the user a reminder.

Listing 15.14  SrvrItem.cpp--CShowStringSrvrItem::OnDraw()

BOOL CShowStringSrvrItem::OnDraw(CDC* pDC, CSize& rSize)
{
     CShowStringDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     // TODO: set mapping mode and extent
     //  (The extent is usually the same as the size returned from OnGetExtent)
     pDC->SetMapMode(MM_ANISOTROPIC);
     pDC->SetWindowOrg(0,0);
     pDC->SetWindowExt(3000, 3000);
     COLORREF oldcolor;
     switch (pDoc->GetColor())
     {
     case 0:
          oldcolor = pDC->SetTextColor(RGB(0x80,0x80,0x80)); //gray
          break;
     case 1:
          oldcolor = pDC->SetTextColor(RGB(0xB0,0,0)); // dull red
          break;
     case 2:
          oldcolor = pDC->SetTextColor(RGB(0,0xB0,0)); // dull green
          break;
     }
     int DTflags = 0;
     if (pDoc->GetHorizcenter())
     {
          DTflags |= DT_CENTER;
     }
     if (pDoc->GetVertcenter())
     {
          DTflags |= (DT_VCENTER|DT_SINGLELINE);
     }
     CRect rect;
     rect.TopLeft() = pDC->GetWindowOrg();
     rect.BottomRight() = rect.TopLeft() + pDC->GetWindowExt();
     pDC->DrawText(pDoc->GetString(), &rect, DTflags);
     pDC->SetTextColor(oldcolor);
     return TRUE;
}

The function starts with the boilerplate from AppWizard. With an application that doesn't just draw itself in whatever space is provided, you would want to add code to determine the extent rather than just using (3000,3000). (You'd want to add the code to OnGetExtent(), too.) But hardcoding the numbers works for this simple example.

Build the application, fix any typos or other simple errors, and then start Word and insert a ShowString document into your worksheet. ShowString should run as before, with Hello, world! in the center of the view. Convince yourself that the Options dialog box still works and that you have restored all the old functionality. Be sure to change at least one thing: the string, the color, or the centering. Then, press Esc to finish editing in place. Oops! It still draws the old Hello, world! in gray in the top left of the server area. Why?

Remember that in CShowStringDoc::OnToolsOptions(), after the user clicks OK, you tell the document that it has been changed and arrange to have the view redrawn:

     SetModifiedFlag();
     UpdateAllViews(NULL);

You need to add another line there to make sure that any containers that are containing this document are also notified:

     NotifyChanged();

Now build it again and insert a different ShowString object into a Word document. This time the changes are reflected in the inactive server display as well. Figure 15.12 shows a ShowString item being edited in place, and Figure 15.13 shows the same item inactive.


NOTE: If you turn on either centering option, the string will not appear when the item is inactive. It seems that DrawText is centering the string within a much larger rectangle than the one you pass to it. Simpler CDC functions, such as DrawEllipse, don't have this problem. It might be wise to avoid centering text with DrawText() if your inactive appearance is important. 

FIG. 15.12 This ShowString item is being edited in place.

FIG. 15.13 This ShowString item is inactive.

Good old ShowString has been through a lot. It's time for one more transformation.

Applications That Are Both Container and Server

As you might expect, adding container features to this version of ShowString is as difficult as adding them to the ordinary ShowString of the previous chapter. If you add these features, you gain an application that can tap the full power of ActiveX to bring extraordinary power to your work and your documents.

Building Another Version of ShowString

The way to get a ShowString that is both a container and a server is to follow these steps:

1. Build a new ShowString with AppWizard that is a container and a full server. Run AppWizard as usual but in a different directory than the one where you created the server-only ShowString. Be sure to select the Both Container And Server radio button in Step 3. In Step 4, click the Advanced button and change the filename types as you did earlier in this chapter. Finally, when asked whether you want to use the same CLSID, click No. This is a different application.

2. Make the container changes from the preceding chapter. When adding the Tools, Options menu item and accelerator, add it to the main menu, the server in-place menu, and the server-embedded menu.

3. Make the server changes from this chapter.

4. Add the ShowString functionality.

This section does not present the process of building a container and server application in detail; that is covered in the "Adding Server Capabilities to ShowString" section of this chapter and all of Chapter 14. Rather, the focus here is on the consequences of building such an application.

Nesting and Recursion Issues

After an application is both a server (meaning its documents can be embedded in other applications) and a container, it is possible to create nested documents. For example, Microsoft Word is both container and server. An Excel spreadsheet might contain a Word document, which in turn contains a bitmap, as shown in Figure 15.14.

Within Excel, you can double-click the Word document to edit it in place, as shown in Figure 15.15, but you cannot go on to double-click the bitmap and edit it in place, too. You can edit it in a window of its own, as shown in Figure 15.16. It is a limitation of ActiveX that you cannot nest in-place editing sessions indefinitely.

Active Documents

The final, important recent addition to ActiveX is Active Documents, formerly known as ActiveX Document Objects. An ordinary ActiveX server takes over the menus and interface of a container application when the document is being edited in place but does so in cooperation with the container application. An Active Document server takes over far more dramatically, as you will shortly see.

FIG. 15.14 This Excel spreadsheet contains a Word document that contains a bitmap.

FIG. 15.15 This Word document is being edited in place.

FIG. 15.16 This bitmap is nested within a Word document within an Excel spreadsheet, and so cannot be edited in place. Instead, it is edited in a separate window.

What Active Documents Do

The first application to demonstrate the use of Active Documents is the Microsoft Office Binder, shown in Figure 15.17. To the user, it appears that this application can open any Office document. In reality, the documents are opened with their own server applications while the frame around them and the list of other documents remain intact. Microsoft Internet Explorer (version 3.0 and later) is also an Active Document container--Figure 15.18 shows a Word document open in Explorer. Notice the menus are mostly Word menus, but the Explorer toolbar can still be used. For example, clicking the Back button closes this Word document and opens the document that was loaded previously.

To users, this is a complete transition to a document-centered approach. No matter what application the user is working with, any kind of document can be opened and edited, using the code written to work with that document but the interface that the user has learned for his or her own application.

Making ShowString an Active Document Server

Making yet another version of ShowString, this one as an Active Document server, is pretty simple. Follow the instructions from the "AppWizard's Server Boilerplate" section at the beginning of this chapter, with two exceptions: in AppWizard's Step 3, select Active Document Server and in AppWizard's Step 4, click the Advanced button. Fix the file type names and fill in the file extension as .SST, as shown in Figure 15.19. This helps Active Document containers determine what application to launch when you open a ShowString file.

FIG. 15.17 The Microsoft Office Binder makes it simple to pull Office documents together.

FIG. 15.18 Microsoft Internet Explorer is also a container for Active Documents.

FIG. 15.19 The Advanced Options dialog box of AppWizard's Step 4 is where you specify the extension for ShowString files.

Document Extension Boilerplate  Any one of the versions of ShowString built up to this point could have had a document extension specified. AppWizard adds these lines to CShowStringApp::InitInstance() when you specify a document extension for an Active Document server application:

     // Enable drag/drop open
     m_pMainWnd->DragAcceptFiles();
     // Enable DDE Execute open
     EnableShellOpen();
     RegisterShellFileTypes(TRUE);

It is the call to RegisterShellFileTypes() that matters here, though the drag and drop is a nice touch. You're able to drag files from your desktop or a folder onto the ShowString icon or an open copy of ShowString, and the file opens in ShowString.

Active Document Server Boilerplate  Selecting Active Document support makes remarkably little difference to the code generated by AppWizard. In CShowStringApp::InitInstance(), the versions of ShowString that were not Active Document servers had this call to update the Registry:

     m_server.UpdateRegistry(OAT_INPLACE_SERVER);

The Active Document version of ShowString has this line:

     m_server.UpdateRegistry(OAT_DOC_OBJECT_SERVER);

In both cases, m_server is a CShowStringSrvrItem, but now the Active Document server version has a server item that inherits from CDocObjectServerItem. This causes a number of little changes throughout the source and includes files for CShowStringSrvrItem, where base class functions are called. Similarly, the in-place frame object, CInPlaceFrame, now inherits from COleDocIPFrameWnd.

Showing Off the Newest ShowString  Restore the ShowString functionality once again as described in the section "Showing a String Again," earlier in this chapter. Also copy the OnDraw() code from an old version of ShowString to CshowStringDoc::OnDraw(). Build the application, run it once to register it, and then run Microsoft Binder (if you have Office installed). Choose Section Add to bring up the Add Section dialog box shown in Figure 15.20. On the General tab, highlight ShowString Document and click OK.

FIG. 15.20 Not many applications on the market are Active Document servers, but you can write one in minutes.

The menus include ShowString's Tools menu, as before. Choose Tools, Options and change something--for example, in Figure 15.21, the string has been changed to "Hello from the Binder" and the horizontal centering has been turned on. You have access to all of ShowString's functionality, although it doesn't look as though you are running ShowString.

Now run ShowString alone and save a document by choosing File, Save. You don't need to enter an extension: The extension .SST is used automatically. Open an Explorer window and explore until you reach the file you saved. Bring up Internet Explorer 4.0 and drag the file you saved onto Internet Explorer.

Your ShowString document opens in Explorer, as you can see in Figure 15.22. The toolbar is clearly the Explorer toolbar, but the menu has the Tools item, and you can change the string, centering, and color as before. If you use the Back button on the Explorer toolbar, you reload the document you had open. If you change the ShowString document before clicking Back, you'll even be prompted to save your changes! Microsoft plans to integrate the desktop in the next generation of Windows with the Internet Explorer interface. What you see here is a sneak preview of how that will work.

FIG. 15.21 All of ShowString's functionality is available from within the Binder.

FIG. 15.22 Internet Explorer appears to be able to read and write ShowString files now.

You can also arrange for your applications to be Active Document containers. Perhaps you noticed the check box on AppWizard's Step 3 where you could ask AppWizard to turn on this feature. It's not much harder to do than serving Active Documents, so you can explore it on your own. If you would like your users to be able to open Word files, Excel spreadsheets, or other Active Documents from within your application, be sure to look into this feature.

Eventually Windows will look very much like Internet Explorer; Active Documents will make that possible.


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.