Step 15: Making an OLE server
Supporting OLE servers by being a OLE container is a big step ahead in
flexibility for your applications. It expands the functionality of your application into
just about any area you can think of. But one thing is missing: if you can make your
application an OLE server, your application can be used to extend the functionality of
other applications.
For example, suppose you're developing database forms and you want to
add some of your line drawings to make the database forms more attractive. Without OLE,
including line drawings in the database form is rather cumbersome, requiring you somehow
to capture the drawing and paste it into the form. Then, once it's in the form, you have
no way to modify it besides going back to Drawing Pad, editing it, then pasting it back
into the form.
If the database is an OLE container, and you've made Drawing Pad an
OLE server, you can easily drop line drawings into your database forms. The embedded OLE
server lets you modify the line drawing without having to leave your database application.
This chapter describes how to take your Doc/View Drawing Pad
application from Step 14 and make it an OLE server. The code for this example can be found
in the files STEP15.CPP, STEP15DV.CPP, STEP15.H, STEP15DV.H, STEP15.RC, and STEP15DV.RC in
the EXAMPLES/OWL/TUTORIAL directory of your compiler installation.
Note: After making the changes in this step, the Drawing Pad
application will be a server-only application; that is, it will no longer support
containing embedded OLE objects. This is to demonstrate the unique server functionality
added to the application. Changes that remove the container support will be noted. If you
want to combine container and server support in a single application, you need only to
skip those steps that remove container support.
Converting your application object
There are a few changes you need to make in your application object
to become an OLE server.
- Change the header files.
- Change the application's registration table.
- Change the base class constructor to register some more information, including the
application dictionary.
- Hide the window if the application was invoked as a server.
- Add module identifier parameters to a number of object constructors.
- Change how you create new views.
- Change how you find the About dialog box's parent window.
- Change the OwlMain function to check for action options.
Changing the header files
You only need to make two changes to the list of header files in
STEP15.CPP.
- Add the owl\oleview.h header file; the TOleView class needs to be used when you
create new views
- Change from including STEP14.RC to STEP15.RC
Changing the application's registration table
You basically need to change your entire application registration
table from Step 14. However, only one of these changes is directly related to making the
application an OLE server. You need to change the values associated with the clsid and
description keys. Because the end result is an application that is different from
Step 14, all of these values should change.
Your new registration table should look something like this:
BEGIN_REGISTRATION(AppReg)
REGDATA(clsid, "{5E4BD320-8ABC-101B-A23B-CE4E85D07ED2}")
REGDATA(description,"OWL Drawing Pad Server")
END_REGISTRATION
Note: Remember, don't try to duplicate
the GUID or program identifier in your other applications! Preventing such duplication is
why these values were changed from Step 14 to Step 15!
Changing the application constructor
For OLE servers, you need to change TDrawApp's base class
constructor to take the application dictionary object as a parameter. For a container
application, you didn't need to do this. The reason is that a container is always be
created as an executable application as opposed to a DLL. When you don't specify an
application dictionary in TApplication's constructor, it uses the global
application dictionary ::OwlAppDictionary. This works fine for an executable: since
it has its own instance, it's entered in the global application dictionary. But DLLs don't
have their own instance.
TApplication provides a couple more parameters to its
constructor than you've been using. The first is the name of the application, which you
used in the last step to set the application name.
The second is a pointer to a reference to a TModule object
(that is, TModule*&). TApplication's constructor sets this pointer to
point at the new application object. In this case, you want to pass in the global module
object ::Module. ::Module is used by ObjectWindows and ObjectComponents to
identify the current module. Note that ::Module is the default value for this
parameter.
The last parameter is a pointer to a TAppDictionary object.
Use a pointer to the TAppDictionary object you created using the
DEFINE_APP_DICTIONARY macro for this parameter.
Now your constructor should look something like this:
TDrawApp() : TApplication(::AppReg["description"], ::Module, &::AppDictionary) {}
Hiding a server's main window
Under regular circumstances, when your application is started, it
does some setup and initialization, then creates a main window for the user to work in.
That's fine when someone is using your application as their primary workplace. But when
your application is being used as an OLE server, it's not the primary workplace;
the main window has already been created by another application. In this case, you need to
set your main window to be hidden.
The best place to do this is in InitMainWindow, before your
window object has been created. To find out whether the application is an embedded server
and to hide the main window if so:
- Call the IsOptionSet function of the TOcRegistrar object, passing TOcCmdLine::Embedding
as the function's argument. You can get a reference to the application's registrar object
by calling the GetRegistrar function. IsOptionSet checks to see if the
application's command line contained the option passed to it as a parameter. When an
application is created as an embedded server, the -Embedding option is specified on
the command line. Therefore, if the application was created as an embedded server, IsOptionSet
returns true when passed TOcCmLine::Embedding. You'll see more of these
options later.
- If IsOptionSet returns true, the application is being invoked as an
embedded server, so set nCmdShow to SW_HIDE. This causes the main window to be
hidden when it's created and activation to be passed to the window from which the server
was invoked.
Identifying the module
When you constructed the TApplication base class, you had to
add in a couple of new parameters to make sure the object could find itself in complicated
OLE environment. You need to do the same basic thing for a number of other objects in your
application. In the case of these objects, though, you just need to direct them to the
application object, which then handles all the transactions between your application and
whatever's outside of the application.
Use TDrawApp's this
pointer for each of these parameters.
Your InitMainWindow function should look something like this:
void
TDrawApp::InitMainWindow()
{
if (GetRegistrar().IsOptionSet(TOcCmdLine::Embedding))
nCmdShow = SW_HIDE;
TOleMDIFrame* frame;
frame = new TOleMDIFrame(GetName(), 0, *(Client = new TMDIClient(this)),
true, this);
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);
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(IDM_MDICMNDS));
// Install the document manager
SetDocManager(new TDocManager(dmMDI | dmMenu, this));
}
Creating new views
When creating a new view window in an OLE server application, you
need to be careful about setting the view's parent. In the case where your application is
being run as a stand-alone program, you don't have to change anything. The code in EvNewView
that you used in the last few steps is just fine.
Things become complicated when the server is embedded in a container
application. You need to determine one basic thing: is your view using space inside one of
the container's windows? You can determine this by answering two questions:
The reason you need to
determine this has to do with setting the parent window of the view. When the server is
being used as an in-place server, you must set the parent window of the view properly.
ObjectComponents provides an object known as a view bucket to make this easier. Once
you've set your view's parent to the view bucket, ObjectComponents takes care of setting
the view's parent when the view is activated, deactivated, moved around, and so on. To set
the view's parent, follow this procedure:
- Downcast the TView parameter of the EvNewView function to a TOleView.
Take the address of the object by prefixing it with an ampersand (&) and assign it to
a TOleView pointer using the TYPESAFE_DOWNCAST macro.
- Check whether the view is an embedded server by calling the view's associated document's
IsEmbedded function. The view itself doesn't know if it's embedded. You can find
the view's associated document by calling the view's GetDocument function. If the
document's not embedded, you can stop checking here and just go to the code you used in
the last few steps.
- Check whether the view is activated for open editing. You can check this by calling the IsOpenEditing
function of the view's remote view. You can get a pointer to the remote view by calling GetOcRemView.
If IsOpenEditing returns true, you can stop checking here and go to the code
you used in the last few steps.
- Once you've determined that the application is being used as a server for in-place
editing, you can work on setting up the view's parent. Follow this procedure:
- Find the window associated with the view. You can get a TWindow pointer to this
window using the view's GetWindow function.
- You need to find the remote view bucket associated with the server. To do this, call the
GetMainWindow function and downcast the return value to a TOleFrame pointer.
TOleFrame provides a function called GetRemViewBucket. This function returns
a TWindow pointer that references the remote view bucket.
- Once you've found the remote view bucket, call the view's SetParent function with
the bucket's TWindow pointer as the parameter.
- Call the view's Create function.
Note that you haven't really set the view's parent as you normally
think of it. But the remote view bucket lets you set this once and then lets
ObjectComponents take care of the work of keeping track of the active parent window.
The code for this function should look something like this:
void
TDrawApp::EvNewView(TView& view)
{
TOleView* ov = TYPESAFE_DOWNCAST(&view, TOleView);
if (view.GetDocument().IsEmbedded() && !ov->GetOcRemView()->IsOpenEditing()) {
TWindow* vw = view.GetWindow();
vw->SetParent(TYPESAFE_DOWNCAST(GetMainWindow(), TOleFrame)->GetRemViewBucket());
vw->Create();
} else {
TMDIChild* child = new TMDIChild(*Client, 0);
if (view.GetViewMenu())
child->SetMenuDescr(*view.GetViewMenu());
child->Create();
child->SetClientWindow(view.GetWindow());
}
}
Changing the About dialog box's parent window
In previous versions of the tutorial application, when you created
the About dialog box, you simply called the GetMainWindow function to find the
dialog box's parent window. This is no longer adequate, however, since you don't know if
your main window is actually the main window that the application user sees. If your
application is embedded in another application, you've already determined in the TDrawApp
constructor that you're not displaying your main window.
To find the window with focus or other appropriate view window on
the desktop (which functions as the dialog's parent), you can call the GetCommandTarget
function. This function is provided by TFrameWindow and returns a handle to the
current active window. Note that calling this function works whether or not the
application is running as an embedded server or as a stand-alone application, since it
returns the command focus window. When the tutorial application is an embedded server, it
returns a handle to the focus window of the client application. When the tutorial
application is running on its own, it returns a handle to itself.
Note that you still need to call GetMainWindow to get a
pointer to the tutorial application's main window. You then call the GetCommandTarget
function of that window object. You also need to create a temporary TWindow to pass
GetCommandTarget's return value to the TDialog constructor. Your modified CmAbout
function should look something like this:
void
TDrawApp::CmAbout()
{
TDialog(&TWindow(GetMainWindow()->GetCommandTarget()), IDD_ABOUT).Execute();
}
Modifying OwlMain
There's only one new thing you need to take care of before running
an OLE server application. You need to check the command line to see if one of the
standard action options was specified.
There are a couple of standard ObjectComponents command-line options
that may be specified for your server application. The presence of one of these ``action''
options signals that, instead of executing normally, your application should perform a
particular action, then exit. For an OLE server application, the action options you need
to check for are:
The good thing about these
options is that ObjectComponents automatically performs these actions for you when you
create the registrar object. The only thing you need to do is check in the OwlMain
function whether one of these options was set. If so, you can return immediately. If none
of the action options was specified, you can go on to the next step.
You can check for these options using the IsOptionSet
function that you used in the InitMainWindow function to check for the -Embedding
flag. For these options, you should check for the TOcCmdLine::AnyRegOptions flag.
This flag checks to see if any of the options relevant to your application was set. IsOptionSet
returns true if any of the options was set.
If one of the flags was set, you can return 0 from OwlMain.
When one of these action options is set, ObjectComponents performs some registration task.
Once that task is done, the application is complete. Your application never performs a
registration task then executes as normal.
Your OwlMain function should look something like this:
int
OwlMain(int /*argc*/, char* /*argv*/ [])
{
Registrar = new TOcRegistrar(AppReg, TOleFactory<TDrawApp>(),
TApplication::GetCmdLine());
if (Registrar->IsOptionSet(TOcCmdLine::RegServer | TOcCmdLine::UnregServer))
return 0;
return Registrar->Run();
}
Changes to your Doc/View classes
There are a number of changes you need to make to your Doc/View
classes to support OLE server functionality:
Changing header files
You need to change your list of header files to include a few new
header files, along with changing to including the resource script file for Step 15. The
new files you need to include are owl/controlb.h and owl/buttonga.h. Your include
statements should look something like this:
#include <owl/dc.h>
#include <owl/inputdia.h>
#include <owl/chooseco.h>
#include <owl/gdiobjec.h>
#include <owl/docmanag.h>
#include <owl/listbox.h>
#include <owl/controlb.h>
#include <owl/buttonga.h>
#include <owl/olemdifr.h>
#include <owl/oledoc.h>
#include <owl/oleview.h>
#include <classlib/arrays.h>
#include "step15dv.rc"
Changing the document registration table
You need to make some fairly extensive changes to your document
registration table to support being an OLE server. The parts that don't change are
discussed in this section.
Defining the registration table isn't different from before. This
basically involves using the BEGIN_REGISTRATION and END_REGISTRATION macros. As before,
your table begins with the BEGIN_REGISTRATION macro, which takes the name of the
registration as its only parameter. The END_REGISTRATION macro closes out the table
definition.
The two REGDATA macros that set the extension and docfilter
table entries remain the same. The REGDOCFLAGS macro also doesn't change.
The parts of the registration table that you need to change are
discussed in the next sections.
Program identifier and description
Step 14's program identifier (the value associated with the progid
key) and its description (the value associated with the description key) described
the application as a ``Draw Container'' and ``OWL Drawing Pad Container,'' respectively.
These values need to be changed to reflect the application being a server.
Making the application insertable
ObjectComponents provides a special key value called insertable.
You can register insertable using the REGDATA macro. The value associated with the insertable
key is irrelevant; it's never used, so usually you'll just want to set an empty string for
the value.
The presence of the insertable key indicates to
ObjectComponents that the application is insertable, that is, the application can be
embedded into other applications. All ObjectComponents servers must specify the insertable
key in their registration table!
Setting the server's menu items
When the user activates a server embedded in a container by
clicking on the server's view, the container sets a menu item (usually on its Edit menu)
that the user can use to access the server. This menu goes to a pop-up menu that provides
a number of ``verbs''--menu choices that let the user work with the server application and
manipulate the data in it.
So there are two things you need to set up for this:
Note that the first verb in the verb list, that is, the value associated
with the verb0 key is the default verb for your server. Thus if the user
double-clicks on your embedded server, the server acts just the same as if the user had
selected the verb0 value from the server's menu.
ObjectComponents servers are set up to automatically handle two verbs.
Specifying Clipboard formats
For the server application, you can trim down the number of
Clipboard formats available. You really only need to provide two formats.
As before, the actual
copying operation is handled by ObjectComponents. Note that these are the only formats
necessary to support an ObjectComponents server; the other formats provided by the
container application are removed. To support dual container/server functionality, you
should leave these formats in.
Your finished document registration table should look something like
this:
BEGIN_REGISTRATION(DocReg)
REGDATA(progid, "DrawServer")
REGDATA(menuname, "Drawing Pad")
REGDATA(description, "OWL Drawing Pad Server")
REGDATA(extension, "PTS")
REGDATA(docfilter, "*.pts")
REGDOCFLAGS(dtAutoOpen | dtAutoDelete | dtUpdateDir | dtCreatePrompt |
dtRegisterExt
REGDATA(insertable, "")
REGDATA(verb0, "&Edit")
REGDATA(verb1, "&Open")
REGFORMAT(0, ocrEmbedSource, ocrContent, ocrIStorage, ocrGet)
REGFORMAT(1, ocrMetafilePict, ocrContent, ocrMfPict, ocrGet)
END_REGISTRATION
Changing the view notification functions
You need to make a change to a number of the view notification
functions to support proper painting of the server's remote view. The functions you need
to change are VnRevert, VnAppend, VnModify, and VnDelete. Each
of these view notifications indicates that the drawing has been modified in some way and
the display needs to be updated.
To force the container to update the view and reflect the changes in
the view's appearance, you need to call the InvalidatePart function. This function
is provided by TDrawView's base class TOleView. This function tells the
container window that the area inside the embedded server's remote view is invalid and
needs repainting. InvalidatePart takes a single parameter, a TOcInvalidate enum.
A TOcValidate can be one of two values.
In this case, each of
these view notification events indicates that the appearance of the drawing has changed,
whether it was by discarding changes, appending a new line, modifying one of the current
lines, or deleting a line. So when you do call the InvalidatePart function, you
should call it with the invView argument. The invData argument is used when
the container has a link to data in the server, but the container actually takes care of
displaying the data.
You should first call the Invalidate function of the view
when applicable (each of these functions already calls Invalidate, except for VnAppend,
which doesn't need to), then call the InvalidatePart function. Here's how your
modified view notification functions should look:
bool
TDrawView::VnRevert(bool /*clear*/)
{
Invalidate(); // force full repaint
InvalidatePart(invView);
return true;
}
bool
TDrawView::VnAppend(uint)
{
InvalidatePart(invView);
return true;
}
bool
TDrawView::VnModify(uint /*index*/)
{
Invalidate(); // force full repaint
InvalidatePart(invView);
return true;
}
bool
TDrawView::VnDelete(uint /*index*/)
{
Invalidate(); // force full repaint
InvalidatePart(invView);
return true;
}
Adding new members to TDrawView
You need to add some new members to your TDrawView class.
These members are
Adding a control bar
When your application is activated as an embedded server, the
container often lets the application provide a tool bar to access its functionality. This
tool bar should be different from the regular application tool bar and provides button
gadgets only to access the unique functions of your application and not those things
handled by containers, that is, the object's editing and viewing commands. For example,
opening a file is handled by any adequate container application, so it's not a unique
ability of the Drawing Pad application. On the other hand, no container knows how to
change Drawing Pad's pen color.
Since the commands supported by this tool bar are a subset of the
commands supported by the application's tool bar, you can't simply use that tool bar.
Instead you need to provide one for each embedded server view. To support this, just add a
TControlBar pointer as a protected data member. You should initialize this member
to 0 in TDrawView's constructor. The tool bar itself is constructed in one of the
new ObjectComponents event handlers.
Cutting and copying data
Your server will often receive requests to cut or copy data to the
Clipboard. You need to provide functions to handle these requests.
Cutting
Cutting data is copying information from the drawing, placing that
information in the Clipboard, then removing the information from the drawing. This is a
fairly common way to exchange data. However, in the context of the Drawing Pad
application, this behavior is undefined: what does it mean to cut lines from a window?
But since this is a very common (almost mandatory) function in an
OLE server, you should provide at least a place holder for it. You can declare and define
a function called CmEditCut to do this. This function is called when TDrawView
receives the CM_EDITCUT event, which you also need to add (it's in the STEP15DV.RC file in
the sample code). So follow this procedure:
- Add the CM_EDITCUT macro to your application.
- Add the CmEditCut function to the TDrawView class declaration.
- Add an EV_COMMAND macro to the response table to call CmEditCut when the
CM_EDITCUT event is received.
- Define CmEditCut to have no functionality.
Copying
To copy, you can call a member function of one of the classes
provided by ObjectComponents. This class is called TOcRemView and provides a remote
view object for a server document. A remote view handles the view of your server
application from the container application. TOcRemView provides a function called Copy,
which copies the document's data to the Clipboard. You get the TOcRemView object to
work with by calling the GetOcRemView function, which is provided by TOleView.
- Add the CM_EDITCOPY macro to your application.
- Add the CmEditCopy function to the TDrawView class declaration.
- Add an EV_COMMAND macro to the response table to call CmEditCopy when the
CM_EDITCOPY event is received.
- Define CmEditCopy to call GetOcRemView and call the Copy function
of the TOcRemView object.
Handling ObjectComponents events
There are a couple of ObjectComponents events that you need to
handle.
Reporting server view size
For formatting reasons, a container often needs to find out the
size of an embedded server's view. The container signals that it needs this information by
sending an ObjectComponents message to the view. The view then needs to calculate the size
of the server view and get that information back to the container.
To add this functionality, follow these steps:
- The container lets the server know that it needs the size of the view by sending the
OC_VIEWPARTSIZE, a standard ObjectComponents event. ObjectWindows provides a response
table macro for this and other standard ObjectComponents event. The ObjectComponents event
macros are defined in the header file owl/ocfevent.h, which is automatically included.
These macros add EV_ to the beginning of the ObjectComponents event name, so that in this
case the macro would be EV_OC_VIEWPARTSIZE. Add this macro to your view's response table.
Like other standard message macros, it has no parameters and calls a predefined function
name when the event is received.
- Add a function to your TDrawView class declaration to handle this event. The
function called through the predefined response table macro is EvOcViewPartSize.
This function returns bool and takes a pointer to a TRect.
- 3 Define the EvOcViewPartSize function. To do this, create a device context
object (in the sample code here, we've used a TClientDC). You should place the size
of the view in the TRect object passed into EvOcViewPartSize by pointer. In
the Drawing Pad application, the size of the view is limited to 2 inches on the screen.
This is an arbitrary measurement; you can also calculate the area necessary to display the
information in the document and pass that back. For simplicity, though, it's easiest to
pass back an absolute measurement. In this case, set the top and left
members of the TRect to 0. You can then get the number of pixels in the size of the
view by calling the GetDeviceCaps function of the device context object with the
LOGPIXELSX parameter to get the width and the LOGPIXELSY parameter to get the height. This
actually returns the number of pixels in an inch on the screen. Multiply this result by
two in each case and assign the width to the right member of the TRect
object and the height to the bottom member.
The completed function should look something like this:
bool
TDrawView::EvOcViewPartSize(TRect far* size)
{
TClientDC dc(*this);
// a 2" x 2" extent for server
size->top = size->left = 0;
size->right = dc.GetDeviceCaps(LOGPIXELSX) * 2;
size->bottom = dc.GetDeviceCaps(LOGPIXELSY) * 2;
return true;
}
Setting up the view's tool bar
The OC_VIEWSHOWTOOLS event indicates that the container in which
your server is embedded wants to either show or hide your server's tool bar.
- Add the OC_VIEWSHOWTOOLS macro to your view's response table.
- Add a function to your TDrawView class declaration to handle this event. The
function called through the predefined response table macro is EvOcViewShowTools.
This function returns bool and takes reference to a TOcToolBarInfo object. TOcToolBarInfo
is a simple structure; it only has a couple of members that we're concerned with here.
If the container wants to hide the tool bar (that is, Show is false), you
need to destroy the tool bar window, delete the tool bar object, and set your TControlBar
pointer to 0. Before doing this, though, you should check to make sure that the TControlBar
pointer references a valid object!
If the container wants to show the tool bar, you should first check to see that the TControlBar
pointer doesn't already point to a valid object. If so, you can skip Step 5 and go on to
Step 6.
The most complicated thing about constructing a tool bar in these circumstances is
finding the parent window. This takes a few steps, since you need to find your main
window, and then, through the main window, which is an OLE frame window, you need to find
the remote view bucket the application is using in the container's window.
- The first step is to find the application object. This is the easiest way to find the
main window, since the application object provides a function to get a pointer to the main
window. To find the application object, call the GetApplication function. This
returns a TApplication pointer to the application object.
- Once you've found the application object, you can get a TFrameWindow pointer to
the main window by calling the GetMainWindow function of the application object.
- Now that you've found the main window, you need to cast it to a TOleFrame window
to be able to find the remote view bucket window. Although the main window is already a TOleFrame
object, GetMainWindow returns it as a TFrameWindow. Since you are
downcasting (that is, casting from a base object to a class derived from that base), you
need to be careful. It is quite possible to try to cast an object of one type to an object
of another type. If both of these types are derived from the same base class, this can
cause serious trouble.
For example, suppose you have a function that
takes a TWindow pointer as its only parameter. When the function is called, you
assume that the TWindow value you received in the function actually referenced a TControl
object (since TControl is derived from TWindow, you can safely pass a TControl
object as a TWindow object). But TControl and TFrameWindow are both
derived from TWindow. What if the object passed in was actually a TFrameWindow
object? Serious havoc could ensue.
ObjectWindows provides a macro called TYPESAFE_DOWNCAST that
downcasts objects that are typed as a base class to objects of a derived type. If the
downcast isn't typesafe (that is, the object isn't what you're actually trying to downcast
to, such as trying to cast a TFrameWindow to a TControl), the macro returns
0. Otherwise the macro makes the cast for you and returns the appropriate value.
TYPESAFE_DOWNCAST takes two parameters. The first is the object you
want to cast and the second is the type you want to cast the object to.
- Once you've found the application's main window and cast it appropriately, you need to
call the GetRemViewBucket function. This function returns a TWindow pointer
that references the remote view bucket window. This is quite important: with the tool bar
parented properly, it's easy for the container to switch tool bars automatically among any
of the servers that might be embedded in the container.
- Once you've got a pointer to the remote view bucket window, construct a TControlBar
object like normal, passing the view pointer as the parent. When the tool bar object is
constructed, you can insert button gadgets to control the application. For now, it's
sufficient to just add the CM_PENSIZE and CM_PENCOLOR buttons.
Once you have a valid tool bar object, create the tool bar itself by calling the
object's Create function.
Once the tool bar is created, cast it to an HWND and assign it to the TOcToolBarInfo's
HTopTB member. You could instead assign it to one of TOcToolBarInfo's other
members to place it somewhere besides the top of the container's window.
Assuming everything went alright during this process, return true. This lets the
container know that everything went alright and it can display the tool bar.
Here's how your EvOcViewShowTools function should look:
bool
TDrawView::EvOcViewShowTools(TOcToolBarInfo far& tbi)
{
// Construct & create a control bar for show, destroy our bar for hide
if (tbi.Show) {
if (!ToolBar) {
TOleFrame* frame = TYPESAFE_DOWNCAST(GetApplication()GetMainWindow(),
TOleFrame;
ToolBar = new TControlBar(frame->GetRemViewBucket());
ToolBarInsert(*new TButtonGadget(CM_PENSIZE, CM_PENSIZE,
TButtonGadget::Command));
ToolBar->Insert(*new TButtonGadget(CM_PENCOLOR, CM_PENCOLOR,
TButtonGadget::Command));
}
ToolBar->Create();
tbi.HTopTB = (HWND)*ToolBar;
} else {
if (ToolBar) {
ToolBar->Destroy();
delete ToolBar;
ToolBar = 0;
}
}
return true;
}
Removing calls from the Paint and mouse action functions
TDrawView's Paint function and its mouse action
functions EvLButtonDown, EvLButtonUp, and EvMouseMove all make calls
that are necessary to support container functionality. You should remove these calls for
your application to function as a server-only application.
|