Step 14: Making an OLE container
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 works
Two 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: ObjectComponents
There 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 files
You 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:
- In STEP14.CPP, instead of using TDecoratedMDIFrame, you'll use TOleMDIFrame,
which is an OLE-enabled decorated MDI frame. All OLE frame windows, whether they're MDI or
SDI, must be able to handle decorations, since many embedded OLE servers provide their own
tool bars. The TOleMDIFrame class is declared in the owl/olemdifr.h header file.
Your list of include statements in STEP14.CPP should now look something like
this: #include <owl/applicat.h>
#include <owl/appdict.h> // Needed for DEFINE_APP_DICTIONARY
#include <owl/dialog.h>
#include <owl/controlb.h>
#include <owl/buttonga.h>
#include <owl/statusba.h>
#include <owl/docmanag.h>
#include <owl/olemdifr.h>
#include <stdlib.h>
#include <string.h>
#include "step14.rc"
In STEP14DV.CPP, you need to include OLE-enabled document and view classes. These
classes, TOleDocument and TOleView, provide standard Doc/View functionality
along with the ability to support OLE. They're declared in the header files owl/oledoc.h
and owl/oleview.h. Your list of include statements in STEP14DV.CPP should now look
something like this: #include <owl/chooseco.h>
#include <owl/dc.h>
#include <owl/docmanag.h>
#include <owl/gdiobjec.h>
#include <owl/inputdia.h>
#include <owl/listbox.h>
#include <owl/oledoc.h>
#include <owl/oleview.h>
#include <classlib/arrays.h>
#include "step14dv.rc"
Registering the application for OLE
For 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 table
You 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:
REGISTRATION_FORMAT_BUFFER(100)
BEGIN_REGISTRATION(AppReg)
REGDATA(clsid, "{383882A1-8ABC-101B-A23B-CE4E85D07ED2}")
REGDATA(description,"OWL Drawing Pad 2.0")
END_REGISTRATION
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:
void
MyClass::MyFunc()
{
OtherFunc(AppReg);
}
you would write:
void
MyClass::MyFunc()
{
OtherFunc(::AppReg);
}
Creating a class factory
A 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:
TOleDocViewFactory<TDrawApp>();
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 object
The 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:
- Create a global static pointer to a TOcRegistrar object. You can do this using
the TPointer template, defined in the osl\geometry.h header file (this file is
already included by a number of the ObjectWindows header files, so you don't need to
include it again). This should look something like this:
static TPointer<TOcRegistrar> Registrar;
Using TPointer
instead of a simple pointer, such as TOcRegistrar* Registrar, provides automatic
deletion when the object referred to is destroyed or goes out of scope. The full range of
operations available with regular pointers is available in TPointer, while some of
the traditional dangers of using pointers are eliminated.
Create the actual registrar object. The TOcRegistrar constructor takes four
parameters:
For these parameters, you can
pass the following arguments when constructing the registrar object.
For example,
::Registrar = new TOcRegistrar(AppReg, TOleDocViewFactory<TDrawApp>(),
TApplication::GetCmdLine());
Call the Run function. However, instead of calling the application object's Run
function (which you couldn't do at this point if you wanted to, since you haven't created
an application object), call the registrar object's Run function. TOcRegistrar
provides a Run function that is called just like TApplication's Run
function. However, any ObjectWindows OLE application should call the registrar object's Run
function. This function performs some checks and actions required for your OLE
application.
Your OwlMain function should now look something like this:
int
OwlMain(int /*argc*/, char* /*argv*/ [])
{
::Registrar = new TOcRegistrar(AppReg, TOleDocViewFactory<TDrawApp>(),
TApplication::GetCmdLine());
return ::Registrar->Run();
}
Creating an application dictionary
The 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:
DEFINE_APP_DICTIONARY(AppDictionary);
Changes to TDrawApp
You 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 declaration
You need to make the following changes to the declaration of the TDrawApp
class:
- Derive TDrawApp from both TApplication and the TOcModule class. TOcModule
provides the interface your application object uses to communicate with OLE through the
ObjectComponents Framework. Both TApplication and TOcModule should be public
bases.
- Change the constructor so that you pass the TApplication constructor a single
parameter. You should initialize the name of the application object with the value of description
from AppReg. To make this easier, the TRegList class overloads the square
bracket operators (
[] ) to return the string associated with the key value
passed between the brackets. So to get the string associated with the description
key, call AppReg["description"] .
Your TDrawApp declaration should now resemble the following
code:
class TDrawApp : public TApplication, public TOcModule
{
public:
TDrawApp() : TApplication(::AppReg["description"]) {}
protected:
TMDIClient* Client;
// Override methods of TApplication
void InitInstance();
void InitMainWindow();
// Event handlers
void EvNewView(TView& view);
void EvCloseView(TView& view);
void EvDropFiles(TDropInfo dropInfo);
void CmAbout();
DECLARE_RESPONSE_TABLE(TDrawApp);
};
Changing the class functionality
You need to change the main window to a TOleMDIFrame object
and properly initialize it as follows:
Creating an OLE MDI frame
Next, 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:
TOleMDIFrame(const char far* title,
TResId menuResId,
TMDIClient& clientWnd = *new TMDIClient,
bool trackMenuSelection = false,
TModule* module = 0);
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 connector
In 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 identifier
OLE 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:
void
TDrawApp::InitMainWindow()
{
// Construct OLE-enabled MDI frame
TOleMDIFrame* frame;
frame = new TOleMDIFrame(GetName(), 0, *(Client = new TMDIClient), true);
// Set the frame's OcApp to OcApp
frame->SetOcApp(OcApp);
// Construct a status bar
TStatusBar* sb = new TStatusBar(frame, TGadget::Recessed);
// Construct a control bar
TControlBar* cb = new TControlBar(frame);
cb->Insert(*new TButtonGadget(CM_FILENEW, CM_FILENEW,
TButtonGadget::Command));
cb->Insert(*new TButtonGadget(CM_FILEOPEN, CM_FILEOPEN,
TButtonGadget::Command));
cb->Insert(*new TButtonGadget(CM_FILESAVE, CM_FILESAVE,
TButtonGadget::Command));
cb->Insert(*new TButtonGadget(CM_FILESAVEAS, CM_FILESAVEAS,
TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget);
cb->Insert(*new TButtonGadget(CM_PENSIZE, CM_PENSIZE,
TButtonGadget::Command));
cb->Insert(*new TButtonGadget(CM_PENCOLOR, CM_PENCOLOR,
TButtonGadget::Command));
cb->Insert(*new TSeparatorGadget);
cb->Insert(*new TButtonGadget(CM_ABOUT, CM_ABOUT,
TButtonGadget::Command));
cb->SetHintMode(TGadgetWindow::EnterHints);
// Set the control bar's id. Required for OLE tool bar merging
cb->Attr.Id = IDW_TOOLBAR;
// Insert the status bar and control bar into the frame
frame->Insert(*sb, TDecoratedFrame::Bottom);
frame->Insert(*cb, TDecoratedFrame::Top);
// Set the main window and its menu
SetMainWindow(frame);
GetMainWindow()->SetMenuDescr(TMenuDescr("MDI_COMMANDS",1,1,0,0,1,1));
// Install the document manager
SetDocManager(new TDocManager(dmMDI | dmMenu));
}
Changes to the Doc/View classes
There 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
- Add more information to the registration table for creating TDrawDocument
document templates.
- Change the base class to TOleDocument.
- Modify the constructor .
- Add two new functions, GetLine and IsOpen.
- Modify the file access functions to store and load OLE objects.
For your view class, you need to
- Change the base class to TOleView.
- Remove the DragDC member.
- Modify the constructor and destructor to remove statements with DragDC.
- Modify the Paint function to call the base class Paint function.
- Modify the mouse action commands to check when the user selects an embedded OLE object.
Changing document registration
The 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.
BEGIN_REGISTRATION(DocReg)
REGDATA(progid, "DrawContainer")
REGDATA(description,"OWL Drawing Pad 2.0 Document")
REGDATA(extension, "PTS")
REGDATA(docfilter, "*.pts")
REGDOCFLAGS(dtAutoOpen | dtAutoDelete | dtUpdateDir | dtCreatePrompt |
dtRegisterExt)
REGFORMAT(0, ocrEmbedSource, ocrContent, ocrIStorage, ocrGet)
REGFORMAT(1, ocrMetafilePict, ocrContent, ocrMfPict, ocrGet)
REGFORMAT(2, ocrBitmap, ocrContent, ocrGDI|ocrStaticMed, ocrGet)
REGFORMAT(3, ocrDib, ocrContent, ocrHGlobal|ocrStaticMed, ocrGet)
REGFORMAT(4, ocrLinkSource, ocrContent, ocrIStream, ocrGet)
END_REGISTRATION
Changing TDrawDocument to handle embedded OLE objects
You 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.
- Change TDrawDocument's base class to TOleDocument.
- Modify TDrawDocument's constructor to improve performance.
- Remove the IsOpen function.
- Add some function calls to the Commit and Open functions; these function
calls read and write OLE objects embedded in the document.
Changing TDrawDocument's base class to TOleDocument
To 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 TDrawDocument
The 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.
- Remove the Lines member from the constructor's initialization list.
- Initialize Lines in the constructor body, with an initial size of 100, lower
boundary of 0, and a delta of 5.
Your constructor should now look something like this:
TDrawDocument(TDocument* parent) :
TOleDocument(parent), UndoLine(0), UndoState(UndoNone)
{
Lines = new TLines(100, 0, 5);
}
You don't need to make any changes to the
destructor.
Removing the IsOpen function
You 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:
TLine*
TDrawDocument::GetLine(uint index)
{
return index < Lines->GetItemsInContainer() ? &(*Lines)[index] : 0;
}
The TDrawDocument class declaration
should now look something like this:
class _DOCVIEWCLASS TDrawDocument : public TOleDocument
{
public:
enum {
PrevProperty = TFileDocument::NextProperty-1,
LineCount,
Description,
NextProperty,
};
enum {
UndoNone,
UndoDelete,
UndoAppend,
UndoModify
};
TDrawDocument(TDocument* parent = 0);
~TDrawDocument() { delete Lines; delete UndoLine; }
// implement virtual methods of TDocument
bool Open(int mode, const char far* path=0);
bool Close();
bool Commit(bool force = false);
bool Revert(bool clear = false);
int FindProperty(const char far* name); // return index
int PropertyFlags(int index);
const char far* PropertyName(int index);
int PropertyCount() {return NextProperty - 1;}
int GetProperty(int index, void far* dest, int textlen=0);
// data access functions
TLine* GetLine(uint index);
int AddLine(TLine& line);
void DeleteLine(uint index);
void ModifyLine(TLine& line, uint index);
void Clear();
void Undo();
protected:
TLines* Lines;
TLine* UndoLine;
int UndoState;
int UndoIndex;
string FileInfo;
};
Reading and writing embedded OLE objects
The 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:
- Add the call to TOleDocument::Commit in the Commit function right before
you create the TOutStream object by calling the OutStream function.
- Add the call to the TOleDocument::Open function in TDrawDocument's Open
function right before you create the TInStream object by calling the InStream
function.
- At the end of the procedure, call TOleDocument::CommitTransactedStorage to make
your changes permanent. By default, TOleDocument uses the transacted mode (ofTransacted)
to buffer changes in temporary storages until they are committed permanently.
That's all you need to do read and store OLE objects in your document!
Your Commit function should now look something like this:
bool TDrawDocument::Commit(bool force)
{
TOleDocument::Commit(force);
TOutStream* os = OutStream(ofWrite);
if (!os)
return false;
// Write the number of lines in the figure
*os << Lines->GetItemsInContainer();
// Append a description using a resource string
*os << ' ' << FileInfo << '\n';
// Get an iterator for the array of lines
TLinesIterator i(*Lines);
// While the iterator is valid (i.e. we haven't run out of lines)
while (i)
// Copy the current line from the iterator and increment the array.
*os << i++;
delete os;
// Commit the storage if it was opened in transacted mode
TOleDocument::CommitTransactedStorage();
SetDirty(false);
return true;
}
Your Read function should look
something like this:
bool TDrawDocument::Open(int mode, const char far* path)
{
char fileinfo[100];
TOleDocument::Open(mode, path);
if (GetDocPath()) {
TInStream* is = (TInStream*)InStream(ofRead);
if (!is)
return false;
unsigned numLines;
*is >> numLines;
is->getline(fileinfo, sizeof(fileinfo));
while (numLines--) {
TLine line; *is >> line;
Lines->Add(line);
}
delete is;
FileInfo = fileinfo;
} else {
FileInfo = string(*::Module,IDS_FILEINFO);
}
SetDirty(false);
UndoState = UndoNone;
return true;
}
Changing TDrawView to handle embedded OLE objects
You 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.
- Change the base class of TDrawView to TOleView.
- Remove the DragDC member; TOleView supplies a TDC pointer called DragDC.
- Modify the constructor and destructor to remove initialization and deletion of DragDC.
- Remove the EvRButtonDown function.
- Modify the Paint function to call TOleView::Paint to force embedded
objects to paint themselves.
- Modify the mouse action functions to deal with user interaction with embedded OLE
objects.
- Modify the class declaration to reflect changes in the view class.
Modifying the TDrawView declaration
Here's the class declaration for TDrawView. The
modifications to it will be explained in the following sections.
class _DOCVIEWCLASS TDrawView : public TOleView
{
public:
TDrawView(TDrawDocument& doc, TWindow* parent = 0);
~TDrawView() {delete Line;}
static const char far* StaticName() {return "Draw View";}
const char far* GetViewName() {return StaticName();}
protected:
TDrawDocument* DrawDoc; // same as Doc member, but cast to derived
class TPen* Pen;
TLine* Line; // To hold a single line sent or received
from document
// Message response functions
void EvLButtonDown(uint, TPoint&);
void EvMouseMove(uint, TPoint&);
void EvLButtonUp(uint, TPoint&);
void Paint(TDC&, bool, TRect&);
void CmPenSize();
void CmPenColor();
void CmClear();
void CmUndo();
// Document notifications
bool VnCommit(bool force);
bool VnRevert(bool clear);
bool VnAppend(uint index);
bool VnDelete(uint index);
bool VnModify(uint index);
DECLARE_RESPONSE_TABLE(TDrawView);
};
Here's the response table for TDrawView.
DEFINE_RESPONSE_TABLE1(TDrawView, TOleView)
EV_WM_LBUTTONDOWN,
EV_WM_MOUSEMOVE,
EV_WM_LBUTTONUP,
EV_COMMAND(CM_PENSIZE, CmPenSize),
EV_COMMAND(CM_PENCOLOR, CmPenColor),
EV_COMMAND(CM_EDITCLEAR, CmClear),
EV_COMMAND(CM_EDITUNDO, CmUndo),
EV_VN_COMMIT,
EV_VN_REVERT,
EV_VN_DRAWAPPEND,
EV_VN_DRAWDELETE,
EV_VN_DRAWMODIFY,
END_RESPONSE_TABLE;
Changing TDrawView's base class to TOleView
To 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 DragDC
This 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 TDrawView
The 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.
TDrawView::TDrawView(TDrawDocument& doc, TWindow* parent) :
TOleView(doc, parent), DrawDoc(&doc)
{
Line = new TLine(TColor::Black, 1);
SetViewMenu(new TMenuDescr(IDM_DRAWVIEW));
}
By the same token, the only modification
needed to the destructor for TDrawView is to remove the statement deleting DragDC.
~TDrawView()
{
delete Line;
}
Modifying the Paint function
You 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:
void
TDrawView::Paint(TDC& dc, bool erase, TRect&rect)
{
TOleView::Paint(dc, erase, rect);
// Iterates through the array of line objects.
int j = 0;
TLine* line;
while ((line = const_cast<TLine *>(DrawDoc->GetLine(j++))) != 0)
line->Draw(dc);
}
Selecting OLE objects
The 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 EvLButtonDown
You 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.
- The first thing you need to do is let the TOleView base class determine whether
the user selected an OLE object. Do this by calling TOleView::EvLButtonDown. This
function deactivates any currently selected OLE object, creates a new TOleDC object
(TOleDC is derived from the TClientDC class you used in previous steps,
adding the ability to handle embedded OLE objects), and checks to see if another OLE
object was selected.
- To check whether the user wants to and is able to draw in the view, you need to check
two things: whether a valid device context was created in the call to TOleView::EvLButtonDown
and whether an OLE object was selected. You can check the validity of the device context
simply by testing DragDC. You can find out whether an OLE object was selected by
calling the SelectEmbedded function. SelectEmbedded returns true if
an object was selected and false otherwise. If both these conditions weren't met, EvLButtonDown
can just return.
- Assuming there is a valid device context and no OLE object was selected, you can go
ahead and begin the drawing operation the same as you did in Step 13. The only change you
need to make is removing the initialization of DragDC, since it's already set to a
valid device context object.
Your EvLButtonDown function should look something like this:
void TDrawView::EvLButtonDown(uint modKeys, TPoint& point)
{
TOleView::EvLButtonDown(modKeys, point);
if (DragDC && !SelectEmbedded()) {
SetCapture();
Pen = new TPen(Line->QueryColor(), Line->QueryPenSize());
DragDC->SelectObject(*Pen);
DragDC->MoveTo(point);
Line->Add(point);
}
}
Modifying EvMouseMove
The changes needed to EvMouseMove are similar to those
required by EvLButtonDown.
- Call the base class version of EvMouseMove.
- Check whether the device context is valid and whether an OLE object was selected.
- Continue the drawing operation the same way you did in Step 13.
Your EvMouseMove function should look something like this:
void TDrawView::EvMouseMove(uint modKeys, TPoint& point)
{
TOleView::EvMouseMove(modKeys, point);
if (DragDC && !SelectEmbedded()) {
DragDC->LineTo(point);
Line->Add(point);
}
}
Modifying EvLButtonUp
With 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.
- Check whether the device context is valid and whether an OLE object was selected.
- Perform the same operations as EvLButtonUp in Step 13, except for deleting and
zeroing out DragDC.
- Call TOleView::EvLButtonUp.
Your EvLButtonUp function should look something like this:
void TDrawView::EvLButtonUp(uint modKeys, TPoint& point)
{
if (DragDC && !SelectEmbedded()) {
ReleaseCapture();
if (Line->GetItemsInContainer() > 1) {
DrawDoc->AddLine(*Line);
}
Line->Flush();
delete Pen;
}
TOleView::EvLButtonUp(modKeys, point);
}
Where to find more information
Here's a guide to where you can find more information on the
topics introduced in this step:
|