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 be done to improve 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 container and server. You also learn about ActiveX documents and how they can be used in other applications.
Like Chapter 23, "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, because ShowString doesn't do very much and can be written quickly.
Build the new ShowString in a different directory, making almost exactly the same AppWizard choices as when you built versions of ShowString in Chapters 17 and 23: call it ShowString, and choose an MDI application with no database support. In AppWizard's Step 3, select an OLE full server. This enables the checkbox for ActiveX document support. Leave this unchecked for now. Later in this chapter you see the consequences of selecting this option. Continue the AppWizard process selecting a docking toolbar, status bar, printing and print preview, context sensitive Help, and 3D controls. Finally, select source file comments and a shared DLL.
NOTE |
Even though the technology is now called ActiveX, the AppWizard dialogs still refer to OLE. Many of the class names that are used throughout this chapter have Ole in their names. While 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 until the next release of Visual C++. |
There are a lot of 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 24.1. When an item is being edited in place, the container in-place menu (called IDR_SHOWSTTYPE_CNTR_IP in the container version of ShowString) is combined with the server in-place menu, IDR_SHOWSTTYPE_SRVR_IP, to build the in-place menu as shown in Figure 24.2. The double separators in each partial menu show where the menus are joined.
Figure 24.1 : AppWizard adds another menu for editing in place.
Figure 24.2 : The container and server in-place menus are interlaced during editing in place.
The second new menu is IDR_SHOWSTTYPE_SRVR_EMB, used when an embedded item is being edited in a separate window. Figure 24.3 shows this new menu next to the more familiar IDR_SHOWSTTYPE menu, 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 As does not save the whole document, but just a copy of this embedded portion.
Figure 24.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 most of the work of 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 24.1 are added:
Listing 24.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 17. The code in Listing 24.2 initializes the ActiveX (OLE) libraries:
Listing 24.2 Excerpt from ShowString.cpp-Initializing Libraries
// Initialize OLE libraries if (!AfxOleInit()) { AfxMessageBox(IDP_OLE_INIT_FAILED); return FALSE; }
Still in CShowStringApp::InitInstance(), after the MultiDocTemplate 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 /Embedded to the invoking command line, as mentioned in Chapter 16, "Choosing an Application Type and Building an Empty Shell." 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 24.3.
Listing 24.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 it is, there is no need to continue with the initialization, so this function returns, as shown in Listing 24.4.
Listing 24.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 stand-alone, execution continues with a registration update:
// When a server application is launched stand-alone, 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. When a user chooses Insert, Object or Edit, Insert Object, the Registry provides the list of object types that can be inserted. So, 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 stand-alone.
CShowStringDoc The document class, CShowStringDoc, now inherits from COleDocument 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 OnGetEmbedded Item(). That function must be overridden in the showstring document class as shown in Listing 24.5.
Listing 24.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, the cancellation of editing 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 has caused ShowString to be launched, and 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 24.6.
Listing 24.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, then 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 24.7.
Listing 24.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 24.8.
Listing 24.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 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 18, "Interface Issues." COleDropTarget is discussed in the drag and drop section of Chapter 23, "Building an ActiveX Container Application." COleResizeBar looks just like a CRectTracker, which was used extensively in Chapter 23, 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 24.9
Listing 24.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 to be created and drawn on the screen. It calls the base class function, 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 24.10.
Listing 24.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 tool tips, docked against the edge of the main frame window for the application.
TIP |
If you are developing an MDI application and prefer the toolbar against the document frame, use pWndDoc instead of PWndFrame, 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 24.11.
Listing 24.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, then run it once stand-alone just to register it.
Figure 24.4 shows the Object dialog box in Microsoft Excel, 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 24.5 shows this dialog box and the long and short OLE names of the file type.
So, the ActiveX (OLE) names have been set incorrectly for this project. The next few pages take you on a tour of the way ActiveX names are stored, and show 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 Edit, Properties. This string is saved in the document template when a new one is constructed in CShowStringApp::InitInstance(), as shown in Listing 24.12.
Listing 24.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 fig. 24.5 and you can see where these values came from.) Try changing the last entry. In the properties box, change the caption so that the last element of the string is ShowString Document. 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 just to delete the ShowString directory and create it again, this time setting the long file type to ShowString Document.
CAUTION |
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 thing. |
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 OLE names as shown in Figure 24.6. After you click Finish, AppWizard asks whether you wish to reuse the existing CLSID, as shown in Figure 24.7. Click Yes and then OK to create the project. This makes a new showstring.reg file for you with the correct Registry values.
Figure 24.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. And 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 sounds intimidating, it should be. 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:
Now if you run the application again, those error messages do not appear. Run Excel again, and choose Insert Object. The Object dialog box now has a more meaningful ShowString entry, as shown in Figure 24.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 OLE 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 Excel sheet. You can immediately edit it in place, as shown in Figure 24.10. You can see that the combined server and container in-place menus are being used. There's not much you can do 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 Excel menus, as shown in Figure 24.11.
Figure 24.10 : While editing in place, the in-place menus replace the Excel menus.
Figure 24.11 : When the object is inactive, Excel 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.
As you did in Chapter 23, "Building an ActiveX Container Application," 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 17, "Building Menus and Dialogs," so that you can copy code and resources from the functional ShowString to the do-nothing OLE server you have just created and explored. Here's what to do:
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 24.13. It draws the string when ShowString is running stand-alone 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. Figure 24.11 shows how Excel reminds the user of the object type when the object is inactive.
Listing 24.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 like 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 24.14.
Listing 24.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,300). (You'd want to add the code to OnGetExtent(), too.) But hardcoding the numbers works for this simple example. Next, paste in the drawing code from the view's OnDraw(), but change the colors slightly to give the user a reminder.
Build the application, fix any typos or other simple errors, and then start Excel and insert a ShowString document into your worksheet. ShowString should run as before, saying 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 center 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 hold this document are also notified:
NotifyChanged();
Now, build it again, and insert a different ShowString object into a different Excel worksheet. This time the changes are reflected in the inactive server display as well. Figure 24.12 shows a ShowString item being edited in place, and Figure 24.13 shows the same item inactive.
Figure 24.12 : This ShowString item is being edited in place.
Figure 24.13 : This ShowString item is inactive.
NOTE |
There is one oddity you may notice as you edit. If you choose to have the string centered horizontally, when it is inactive, the first character of the string is centered, but when it is active the entire string is centered. Because the code is identical for these cases, this behavior has to be blamed on MFC. |
Good old ShowString has been through a lot. It's time for one more transformation.
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 OLE to bring extraordinary power to your work and your documents.
The way to get a ShowString that is both a container and a server is to follow these steps:
This section does not present the process of building a container and server application in detail; it is covered in the "Adding Server Capabilities to ShowString" section of this chapter and all of the previous chapter. Rather, the focus is on the consequences of building such an application.
After an application is both a server, which can be embedded in other applications, and a container, it is possible to create nested documents. For example, an Excel spreadsheet might contain a Word document, which, in turn, contains a bitmap, as shown in Figure 24.14.
Figure 24.14 : This Excel spreadsheet contains a Word document that contains a bitmap.
Within Excel, you can double-click the Word document to edit it in place, as shown in Figure 24.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 24.16. It is a limitation of ActiveX that you cannot nest in-place editing sessions indefinitely.
Figure 24.15 : This Word document is being edited in place.
The final important recent addition to ActiveX is ActiveX documents, also 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 ActiveX Document server takes over far more dramatically.
The first application to demonstrate the use of ActiveX Documents is the Microsoft Office Binder, shown in Figure 24.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 3.0 is also an ActiveX Document container-Figure 24.18 shows a Word document open in Explorer. Notice the menus are 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.
Figure 24.17 : The Microsoft Office Binder makes it simple to pull Office documents together.
Figure 24.18 : Microsoft Internet Explorer is also a container for ActiveX documents.
What this means to users 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.
Making yet another version of ShowString, this one as an ActiveX document server, is pretty simple. Follow the instructions from the "AppWizard's Server Boilerplate" subsection at the beginning of this chapter, with two exceptions: in AppWizard's Step 3, select ActiveX document support, and in AppWizard's Step 4, click the Advanced button. Fix the OLE names, and fill in the file extension as sst, as shown in Figure 24.19. This helps ActiveX document containers to determine what application to launch when you open a ShowString file.
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 ActiveX 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.
ActiveX Document Server Boilerplate Selecting ActiveX document support makes remarkably little difference to code generated by AppWizard. In CShowStringApp:: InitInstance (), the versions of ShowString that were not ActiveX document servers had this call to update the Registry:
m_server.UpdateRegistry(OAT_INPLACE_SERVER);
The ActiveX 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 ActiveX 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. Build the application, run it once to register it, and then run the Microsoft Binder (if you have Office installed). Choose Section Add to bring up the Add Section dialog box shown in Figure 24.20. Highlight ShowString Document and click OK.
The menus include ShowString's Tools menu, as before. Choose Tools Options and change something-for example, in Figure 24.21, the string has been changed to "Hello from the Binder" and the vertical centering has been turned off. You have access to all of ShowString's functionality although it doesn't look as though you are running ShowString.
Figure 24.21 : All of ShowString's functionality is available from within the Binder.
Now run ShowString alone and save the document by choosing File, Save. You do not need to enter an extension: the extension sst is used automatically. Open Internet Explorer 3.0 and choose File, Open. On the Open dialog box, shown in Figure 24.22, click Browse and explore until you reach the file you saved, then click Open.
Your ShowString document opens in Explorer, as you can see in Figure 24.23. 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 previous document you had open. If you change the ShowString document before clicking Back, you're even 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.
Figure 24.23 : Internet Explorer appears to be able to read and write ShowString files now.
This chapter built a third version of ShowString that can act as an ActiveX server. The AppWizard boilerplate did not need to be modified much, and now you can embed a ShowString document in any ActiveX container. You also saw how to construct a ShowString that is both a server and a container. Your glimpse into the future of Windows came with the ActiveX document objects, the Microsoft Office Binder, and the idea of opening a Word document in another application like Microsoft Internet Explorer. Eventually Windows will look very much like Internet Explorer, and it's ActiveX doc objects that will make that possible.
To explore some related material, try these chapters: