|
The next step in the ObjectWindows tutorial shows you how to make an OLE 2 container from the Drawing Pad application. Object Linking and Embedding (OLE) is an extension to Windows that lets the user seamlessly combine several applications into a single workspace. An OLE container application can host server objects, providing additional workspace where the user of your application can expand your application with the capabilities provided by OLE-server-enabled application. The code for the example used in this chapter is contained in the files STEP14.CPP, STEP14DV.CPP, STEP14.RC, and STEP14DV.RC in the EXAMPLES/OWL/TUTORIAL directory where your compiler is installed. How OLE worksTwo different types of application are necessary for basic OLE operations:
What is a container?In this step of the tutorial, you'll make your Doc/View Drawing Pad application into an OLE container. Making Drawing Pad into an OLE container has some important ramifications: the application is no longer limited to displaying a set of lines, but can also display any kind of data that can be presented by any server users embed within their drawings. Although line drawing capability is still in the application and producing line drawings is still the main function of the application, users can now spice up their drawings with bitmaps, spreadsheet charts, even sound files. Although you can do many of the same tasks by using the Clipboard to transfer data, it's easier to use OLE. Using the Clipboard, your application has to be able to accept the type of data stored there. This means if you want to accept bitmaps in the Drawing Pad application, you have to build the functionality required to accept and display bitmaps. This in no way prepares the application to accept spreadsheet charts, database tables, or or data in other graphic formats. To include another type of data requires implementing more functionality to interpret and display that data. Using OLE, your application can display any type of data that is supported by an available OLE server. As far as your application is concerned, a bitmap looks exactly like a spreadsheet chart, a database table, or any other kind of object; that is, they all look like OLE server objects. Also, using the Clipboard, you can build the ability to display a bitmap into your application. But modifying the bitmap after it's been pasted in requires more functionality to be built into your application. Using OLE, the embedded server handles its embedded data whenever the user wants to modify or change it. The type of data used in the server is of no consequence to the container. Implementing OLE in ObjectWindows: ObjectComponentsThere is a price to pay for the advantages OLE provides for your application: programming an OLE implementation has historically been very messy and time consuming. You needed to modify your code to conform to OLE specifications. Even more than this, OLE doesn't follow the event-based paradigm that Windows applications were previoiusly based on. Instead it implements a new interface-based paradigm, requiring an understanding of standard OLE interfaces, reference counting, and other OLE specifications. ObjectWindows implements OLE through the ObjectComponents Framework. You can use ObjectComponents to make your application an OLE container or server with only minor modifications to your code. You can use ObjectComponents with the following application types:
The fewest modifications are required for Doc/View ObjectWindows applications, which is shown in this chapter. Implementing OLE with ObjectComponents in non-Doc/View ObjectWindows applications and non-ObjectWindows C++ applications is described in the Borland C++ Programmer's Guide in Part 5, "ObjectComponents Programmer's Guide.'' The following steps are required to convert your Doc/View ObjectWindows application to an OLE container application:
The ObjectComponents objects used in this chapter are explained as you add them to the Drawing Pad application. The ObjectComponents Framework is described in detail in Part 5 of the Borland C++ Programmer's Guide and in the ObjectWindows Reference in Part 2, "ObjectComponents Programmer's Reference." Adding OLE class header filesYou need to add new headers to your files to use the ObjectComponents classes. ObjectComponents adds OLE capabilities by adding deriving new OLE-enabled classes from existing classes. To add new headers to your files so you can use ObjectComponents classes:
Registering the application for OLEFor OLE to keep track of the applications running on a particular system, any application that wants to use OLE must register in the system-wide OLE registration database. You need to provide a unique identifier number and a description of the application. You also need to create objects that let your application communicate with OLE. ObjectComponents simplifies the process of registering your application through a set of registration macros. These macros create an object of type TRegList, known as a registration table, which contains the information required by the OLE registration database. The macros are the same ones you use when creating a Doc/View template, but you use more of the capabilities available in the TRegList class. Once you've created a registration table, you need to pass it to a connector object. A connector object provides the channel through which a Doc/View application communicates with ObjectComponents and, by extension, with OLE. The registration table is passed to an object of type TOcApp (ObjectComponents connector objects all begin with TOc). Later, you'll modify the declaration of your TDrawApp class to be derived from both TApplication and TOcModule. Your application object initilizes the TOcApp connector object during the application object's construction. The connector object is then accessed through a pointer contained in the TOcModule class. Creating the registration tableYou use the REGDATA macro to create a container application's registration table. This is the same macro you used earlier to register your default document extension and file name filter. For your purposes now, you need the following key values:
Your registration table should look something like this:
Note: You must select a unique GUID for your application. There are a number of ways to get a unique identifier for your application. Generating a GUID and describing your application is presented in detail in "Turning an application into an OLE server" in the ObjectWindows Programmer's Guide. For this tutorial, you can use the GUIDs provided in the tutorial examples. Do not use these same numbers when you create other applications. Other macros can go into your registration table. Those for creating AppReg are the bare minimum for a container application object. You'll get to see a more complicated table when you create the registration table for your document class. Also, because AppReg is created in the global name space of your application, it's safer and more informative to refer to it inside your classes and functions using the global scoping qualifier. So instead of:
you would write:
Creating a class factoryA class factory is pretty much what it sounds like--it's an object that can make more objects. It is used in OLE to provide objects for linking and embedding. When an application wants to embed your application's objects in itself, it's the class factory that actually produces the embedded object. ObjectWindows makes it easy to create a class factory with the TOleDocViewFactory template. All you need to do is create an instance of the template with the application class you want to produce as the template type. In this case, you want to produce instances of TDrawApp with your factory. Creating the template would look like this:
You need to pass an instance of this template as the second parameter of the TOcRegistrar constructor. You can see how this looks in the sample OwlMain below. The objects themselves are created in the factory using the same Doc/View templates used by your application when it's run as a stand-alone application. TOleDocViewFactory is the class factory template for Doc/View ObjectWindows applications. There are other class factory templates for different types of applications. These are discussed in Chapter 36 in the ObjectWindows Reference. Creating a registrar objectThe registration table contains information about your application object for the system. The registrar object, which is of type TOcRegistrar, takes the registration table and registers the application with the OLE registration database. It also parses the application command line looking for OLE-related options. To create a registrar object:
Your OwlMain function should now look something like this:
Creating an application dictionaryThe application dictionary is an object that helps coordinate associations between processes or tasks and TApplication pointers. Before diving into OLE, this was relatively simple: a TApplication object was pretty much synonymous with a process. With OLE, the environment becomes confused: there can be multiple tasks and processes in a single application, with a container application, a number of embedded servers, possibly more servers embedded within those servers--the neighborhood's gotten a little more crowded. To deal with this, ObjectWindows provides application dictionaries with the TAppDictionary class. The best thing about TAppDictionary is that, in order to use it for our purposes here, you don't have to know a whole lot about it. ObjectWindows also provides a macro, DEFINE_APP_DICTIONARY, that creates and initializes an application dictionary object for you. DEFINE_APP_DICTIONARY takes a single parameter, the name of the object you want to create. You should place this near the beginning of your source file in the global name space. You must at least place it before TDrawApp's constructor, since that's where you'll use it. Your application dictionary definition should look something like this:
Changes to TDrawAppYou need change the TDrawApp class to support ObjectComponents. These changes are fairly standard when you're creating a Doc/View application in an OLE container. Changing the class declarationYou need to make the following changes to the declaration of the TDrawApp class:
Your TDrawApp declaration should now resemble the following code:
Changing the class functionalityYou need to change the main window to a TOleMDIFrame object and properly initialize it as follows:
Creating an OLE MDI frameNext, you need to change your InitMainWindow function by changing your frame window object from a decorated MDI frame to an OLE-aware decorated MDI frame (note that all OLE-aware ObjectWindows frame window classes are decorated). The window class to use for this is TOleMDIFrame. TOleMDIFrame is based on TMDIFrame, which provides MDI support, and TOleFrame, which provides the ability to work with ObjectComponents. Here's the constructor for TOleMDIFrame:
The parameters to the TOleMDIFrame constructor are the same as those for TDecoratedMDIFrame. This makes the conversion simple: all you need to do is change the name of the class when you create the frame window object. Setting the OLE MDI frame's application connectorIn order for the OLE MDI frame to be able to handle embedded OLE objects, it needs to know how to communicate with the ObjectComponents mechanism. This is accessed through the TOcApp object associated with the application object. The frame window must be explicitly associated with this object. To do this, TOleMDIFrame provides a function (inherited from TOleFrame) called SetOcApp. SetOcApp returns void and takes a pointer to a TOcApp object. For the parameter to SetOcApp, you can just pass OcApp. Adding a tool bar identifierOLE servers often provide their own tool bar to replace yours while the server is functioning. The mechanics of this are handled by ObjectComponents, but to put the server's tool bar in place of yours, ObjectWindows must be able to find your tool bar. ObjectWindows tries to locate your tool bar by searching through the list of child windows owned by the OLE MDI frame window and checking each window's identifier. Up until now, your tool bar hasn't actually had an identifier, which would cause ObjectWindows to not find the tool bar. In order for ObjectWindows to identify the container's tool bar, the container must use the IDW_TOOLBAR as its window ID (the Id member of the tool bar's Attr member object). Your InitMainWindow function should now look something like this:
Changes to the Doc/View classesThere are a number of changes you need to make to your TDrawDocument and TDrawView classes to support OLE containers. For your document class, you need to
For your view class, you need to
Changing document registrationThe registration table you created earlier contains information necessary for the creation of a basic document template. This functions fine when the only thing using the document template is the document manager. But the way that ObjectComponents uses the Doc/View classes requires some more information:
The following registration table shows how your registration table should look. The values for the REGFORMAT macro are described in the ObjectWindows Reference.
Changing TDrawDocument to handle embedded OLE objectsYou need to make a few changes to TDrawDocument to support embedded OLE objects. These changes mainly affect reading and writing documents that contain OLE objects. The changes are fairly simple; most of the capabilities required to handle embedded OLE objects are handled in the new base class TOleDocument. Here's a summary of the changes required.
Changing TDrawDocument's base class to TOleDocumentTo get your document class ready to work in an ObjectComponents environment, you need to change the base class from TFileDocument to TOleDocument. TOleDocument is based on the TStorageDocument class, which is in turn based on TDocument. TStorageDocument provides the ability to manage and store compound documents. Compound documents provide a way to combine multiple objects into a single disk file, without having to worry about where each of the individual objects are stored or how they are written out or read in. On top of TStorageDocument's capabilities, TOleDocument adds the ability to interface with an OLE object, control and display the OLE object, and read and write the object to and from storage. To change your base class from TFileDocument to TOleDocument, you first need to change all references from TFileDocument to TOleDocument. This is fairly simple, since all that needs to change is the actual name; all the function signatures, including the base class constructor's, are the same. Constructing and destroying TDrawDocumentThe only change you need to make to the constructor for TDrawDocument (other than changing the base class to TOleDocument) basically serves to enhance the performance of the Drawing Pad application, and is not connected to its OLE functionality.
Your constructor should now look something like this:
You don't need to make any changes to the destructor. Removing the IsOpen functionYou need to remove the IsOpen function from the TDrawDocument class. This function is made obsolete by the change you made to the constructor, since the function tests the validity of the Lines member, and Lines now always points to a valid object. TStorageDocument provides an IsOpen function that tests whether the document object has a valid IStorage member. IStorage is an OLE 2 construct that manages compound file storage and retrieval. A compound file is a basically a file that contains references to objects in a number of other locations. To the user, the compound file appears to be a single document. In reality, the different elements of the file are stored in various areas determined by the system and managed through the IStorage object. By constructing an OLE container, you're venturing into supporting compound documents in your application. However, since the support is provided through the OLE-enabled ObjectComponents classes, you don't need to worry about managing the compound documents yourself. Along with removing the IsOpen function declaration and definition from TDrawDocument, you need to eliminate any references to the IsOpen function. This function is called only once, in the GetLine function. In this case, you can simply remove the entire statement that contains the call to IsOpen. This statement checks the validity of the document's TLine object referenced by the Lines data member, but the change you made to the constructor, which ensures that each document object is always associated with a valid TLine object, makes the check unnecessary. Your GetLine function should now look something like this:
The TDrawDocument class declaration should now look something like this:
Reading and writing embedded OLE objectsThe last change you need to make to your document class provides the ability to save and load OLE objects embedded in a document. This is contained in two functions provided by TOleDocument. The functions are named Open and Commit. As you can probably guess, Open reads in the OLE objects contained in the document and Commit writes them out, that is, it commits the changes to disk. To add these changes to your document class:
That's all you need to do read and store OLE objects in your document! Your Commit function should now look something like this:
Your Read function should look something like this:
Changing TDrawView to handle embedded OLE objectsYou need to make a few changes to TDrawView to support embedded OLE objects. These changes mainly affect handling OLE objects through the mouse, including dragging the objects and activating the object's server. The changes are fairly simple; most of the capabilities required to handle embedded OLE objects are handled in the new base class TOleView. Here's a summary of the changes required.
Modifying the TDrawView declarationHere's the class declaration for TDrawView. The modifications to it will be explained in the following sections.
Here's the response table for TDrawView.
Changing TDrawView's base class to TOleViewTo get your view class ready to work in an ObjectComponents environment, you need to change the base class from TWindowView to TOleView. TOleView is itself based on the TWindowView class. TOleView provides the ability required to manipulate and move OLE objects and activate an object's server. To change your base class from TWindowView to TOleView, you first need to change all references from TWindowView to TOleView. This is fairly simple, since all that needs to change is the actual name; all the function signatures, including the base class constructor's, are the same. Removing DragDCThis change is relatively straightforward. TOleView provides a pointer to a TDC called DragDC, obviating the need for this member in the TDrawView class. You'll also need to remove a lot of the actions you previously took with DragDC. Many of these, such as creating a device context object when the left mouse button is clicked, is taken care of by TOleView. These changes are discussed in the next section. Constructing and destroying TDrawViewThe only change you need to make to the TDrawView constructor is to remove the initialization of the DragDC member. Although DragDC was removed from the TDrawView class declaration, it is still a class member; it is provided by TOleView. But TOleView also handles initializing DragDC, since TOleView needs to check for OLE actions that the user might have taken. Note that the TOleView constructor signature is the same as that of TWindowView, meaning all you have to do is change the name and nothing else. Here's how your TDrawView constructor should look.
By the same token, the only modification needed to the destructor for TDrawView is to remove the statement deleting DragDC.
Modifying the Paint functionYou need to modify the Paint function to call TOleView::Paint. TOleView::Paint finds each linked or embedded object in the document (if there are any) and instructs each one to paint itself. Once this has been done, you can go on and paint the screen just as you did in Step 13. Your new Paint function should look something like this:
Selecting OLE objectsThe next changes you need to make involve the functions dealing with mouse actions, namely EvLButtonDown, EvMouseMove, and EvLButtonUp. The changes you need to make in these functions involve checking whether the user's mouse actions involve an OLE object and what drawing mode is set. This is mostly handled by TOleView; for the most part, all you have to do is call the base class versions of the functions. The changes for each function are discussed in the following sections. Modifying EvLButtonDownYou don't need to change the basic workings of the EvLButtonDown function as it exists in Step 13. What you do need to do is add a couple of extra steps to take into account OLE objects that might be in the view.
Your EvLButtonDown function should look something like this:
Modifying EvMouseMoveThe changes needed to EvMouseMove are similar to those required by EvLButtonDown.
Your EvMouseMove function should look something like this:
Modifying EvLButtonUpWith EvLButtonUp, you need to do the same things as you did in EvLButtonDown and EvMouseMove, but with a bit of a twist. In this case, call the base class version of the function last instead of first. TOleView::EvLButtonUp performs a number of cleanup operations, including deleting the device context object pointed to by DragDC.
Your EvLButtonUp function should look something like this:
Where to find more informationHere's a guide to where you can find more information on the topics introduced in this step:
|
|||||||||||||||||||
Last updated: NA |