Step 15: Making an OLE server

Back Home Up Next

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.

  1. Change the header files.
  2. Change the application's registration table.
  3. Change the base class constructor to register some more information, including the application dictionary.
  4. Hide the window if the application was invoked as a server.
  5. Add module identifier parameters to a number of object constructors.
  6. Change how you create new views.
  7. Change how you find the About dialog box's parent window.
  8. 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.

  1. Add the owl\oleview.h header file; the TOleView class needs to be used when you create new views
  2. 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:

  1. 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.
  2. 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.

The MDI client window takes a single parameter, a module pointer.
The OLE MDI frame takes a TModule pointer as a parameter after its menu-tracking parameter (which is the last parameter you used in Step 14).
The document manager takes a TApplication pointer after its flags parameter.

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:

Is the application being used as an embedded server? If the answer to this question is no (that is, your application is being run on its own), then you can skip the next question: you know your application isn't occupying space in the container's window, because there is no container.
Has the application been opened for editing? The user can access your embedded server in one of two ways: either in-place editing, where your server's workspace sits inside the workspace of the container, or open editing, where your server opens up for editing, looking pretty much the same as it does when opened on its own. If the user has opened your server for editing then the server is not sharing space in the container's window. Only if the user is using your server for in-place editing do you have to worry about sharing space with the container.

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:
    1. Find the window associated with the view. You can get a TWindow pointer to this window using the view's GetWindow function.
    2. 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.
    3. Once you've found the remote view bucket, call the view's SetParent function with the bucket's TWindow pointer as the parameter.
    4. 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 -RegServer option tells your application to completely register itself in the OLE registration database.
The -UnregServer option tells your application to ``unregister'' itself, that is, remove its entry in the OLE registration database.

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:

Change your header files
Modify the document registration table to provide extra information needed by an OLE server
Make some changes to the view notification functions VnRevert, VnAppend, VnModify, and VnDelete functions
Add some new members to TDrawView, including a TControlBar pointer and some new functions
Remove calls from the mouse action functions and the Paint function

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:

You need to set up the menu name that the container uses to represent your application on the container's Edit menu. You can do this with the REGDATA macro, using the menuname key and the text you want to appear on the menu as the key's value. You want to be considerate of the container application when choosing this name. Use a name that you would normally use in a menu; that is, it should be descriptive of your application but not so long that it forces the menu to be quite large to accommodate the string. In this case, you could use the application name ``Drawing Pad.''
You can specify up to twenty verbs for your server application. Specify the verbs for your server application using the REGDATA macro. The key values you use to set up verbs follow the format verbn, where n is a number from 0 to 19. The value you associate with each verb is the text that appears on the pop-up menu. Note that you can specify a keyboard shortcut for each verb by preceding the shortcut letter with an ampersand (&). For example, if you specify Edit as a verb, and you want the user to be able to press E to activate that, you specify the string ``&Edit'' for the value.

    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.

The Edit verb indicates that the user wants to manipulate the data handled by the server in place in the container. That means that the user works with the data right in the remote view area in the container's window.
The Open verb indicates that the user wants to open the server application to manipulate the data. In this case, the application opens up as if the user had run the application by itself. The main difference between using the server this way and running the server as a stand-alone application is that the server writes to a document file provided by the container; the container's compound document storage handles the details of saving the data to disk.

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.

ocrEmbedSource indicates that the server can be copied to the Clipboard as an embeddable source. If someone tries to paste an embeddable source from the Clipboard, they get a copy of the embedded server object in their application.
ocrMetafilePict indicates that the server can be copied to the Clipboard as a metafile representation.

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.

invData indicates the data in an embedded object has changed and should be updated in the container.
invView indicates the appearance of an object has changed and should be updated in the container.

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

A TControlBar pointer
Two new event handlers for cutting and copying
Two new event handlers for ObjectComponents events

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:

  1. Add the CM_EDITCUT macro to your application.
  2. Add the CmEditCut function to the TDrawView class declaration.
  3. Add an EV_COMMAND macro to the response table to call CmEditCut when the CM_EDITCUT event is received.
  4. 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.

  1. Add the CM_EDITCOPY macro to your application.
  2. Add the CmEditCopy function to the TDrawView class declaration.
  3. Add an EV_COMMAND macro to the response table to call CmEditCopy when the CM_EDITCOPY event is received.
  4. 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.

OC_VIEWPARTSIZE indicates a request from the container to find out the size of your object's view, that is, the size of the ``window'' within the container's window in which the user sees your embedded application.
OC_VIEWSHOWTOOLS indicates a request from the container for a tool bar from the server application.
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:

  1. 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.
  2. 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. 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.

  1. Add the OC_VIEWSHOWTOOLS macro to your view's response table.
  2. 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.
    The first is the Show member, a bool. If Show is true, the container wants to display your tool bar. If Show is false, the container wants to hide your tool bar.
    The second is HTopTB, an HWND. You pass back the tool bar to the container through this member.
  3. 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!
  4. 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.
  5. 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.
    1. 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.
    2. 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.
    3. 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.

    4. 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.
    5. 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.
  6. Once you have a valid tool bar object, create the tool bar itself by calling the object's Create function.
  7. 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.
  8. 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.

The Paint function calls TOleView::Paint so that any embedded objects are called and told to paint themselves. Since a server-only application has no embedded objects, this call is no longer necessary.
EvLButtonDown, EvLButtonUp, and EvMouseMove call the SelectEmbedded function to determine whether the user clicked on--and thereby selected--an embedded object. As with Paint, since there are no embedded objects in a server-only application, this call is no longer necessary.
 


Copyright © 1998-2001 Yura Bidus. All rights reserved.