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.
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_REGISTRATIONNote: 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!
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) {}
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:
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)); }
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 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()); } }
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(); }
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:
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(); }
#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"
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.
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!
So there are two things you need to set up for this:
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
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.
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; }
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.
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:
To add this functionality, follow these steps:
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; }
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.
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; }