Step 17: Enhancing the linking and embedding capabilities of an OLE container / serverIn this step, you'll learn how to enhance an OLE container/server so that you can link and embed parts of a document instead of a whole document. Specifically, you'll enhance the drawing application so that you can link and embed individual lines. And along the way, you'll implement functions that select, cut, and copy a line. Changing the line classThe first step to enhancing the linking and embedding capabilities of the drawing application is to make changes to the TLine class:
Adding new data membersA line must keep track of its boundary and whether or not it is selected: TRect Bound; // Stores the rectangular boundary of the line. bool Selected; // Stores whether or not the line is selected. Updating the constructorThe Bound and Selected data members should be initialized in the TLine constructor: TLine(const TColor& color = TColor(0), int penSize = 1) : TPoints(10,0,10), PenSize(penSize), Color(color), Bound(0,0,0,0), Selected(false) By default, the line has no boundary and is not selected. Adding new member functionsThe following sections show the member functions you must add to the TLine class. DrawSelectionWhen a line is selected, it must draw a rectangle around itself: TLine::DrawSelection(TDC& dc) { TUIHandle(Bound, TUIHandle::DashFramed).Paint(dc); } For information about the TUIHandle function, search the OWL.HLP file for TUIHandle. UpdateBoundWhen a line moves, it must update its boundary, which is stored in the Bound data member: void TLine::UpdateBound() { // Iterates through the points in the line i. TPointsIterator j(*this); if (!j) return; TPoint p = j++; Bound.Set(p.x, p.y, 0, 0); while (j) { p = j++; if ((p.x - PenSize) < Bound.left) Bound.left = (p.x - PenSize); if ((p.x + PenSize) > Bound.right) Bound.right = (p.x + PenSize); if ((p.y - PenSize) < Bound.top) Bound.top = (p.y - PenSize); if ((p.y + PenSize) > Bound.bottom) Bound.bottom = (p.y + PenSize); } Bound.right += 1; Bound.bottom += 1; } UpdatePositionA line must be able to change its position when a new origin is specified: void TLine::UpdatePosition(TPoint& newPos) { for (TPointsIterator i(*this); i; i++) { TPoint* pt = (TPoint *)&i.Current(); pt->x += newPos.x; pt->y += newPos.y; } Bound.Offset(newPos.x, newPos.y); } InvalidateA line must notify views when it has changed: void TLine::Invalidate(TDrawView& view) { TOleClientDC dc(view); TRect rUpdate(GetBound()); rUpdate.Inflate(1, 1); dc.LPtoDP((TPoint *)&rUpdate, 2); TUIHandle handle(rUpdate, TUIHandle::Framed); rUpdate = handle.GetBoundingRect(); view.GetDocument().NotifyViews(vnInvalidate, (long)&rUpdate, 0); } Changing the document classYou must change the TOleDocument class so that it can save and open individual lines. Saving individual linesWhen a document is saved, it calls the CommitSelection function of each linked or embedded line it contains. (The document also calls the Commit function of each linked or embedded document, as shown in Step 16.) Here is the CommitSelection function:bool TDrawDocument::CommitSelection(TOleWindow& oleWin, void* userData) { TOleDocument::CommitSelection(oleWin, userData); TDrawView* drawView = TYPESAFE_DOWNCAST(&oleWin, TDrawView); TOutStream* os = OutStream(ofWrite); if (!os || !drawView) return false; // Make the line usable in a container by adjusting its origin // TLine* line = (TLine*)userData; int i = line? 1 : 0; TPoint newPos(Margin, Margin); if (line) { newPos -= line->GetBound().TopLeft(); line->UpdatePosition(newPos); } // Write the number of lines in the figure *os << i; // Append a description using a resource string *os << ' ' << FileInfo << '\n'; // Copy the current line from the iterator and increment the array. if (line) *os << *line; delete os; // restore line // if (line) line->UpdatePosition(-newPos); // // Commit the storage if it was opened in transacted mode // TOleDocument::CommitTransactedStorage(); return true; } Opening individual linesWhen a document is opened, it calls the OpenSelection function of each linked or embedded line it contains. (The document also calls the Open function for each linked or embedded document it contains.) Here is the OpenSelection function:bool TDrawDocument::OpenSelection(int mode, const char far* path, TPoint far* where) { char fileinfo[100]; TOleDocument::Open(mode, path); // normally path should be null //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; if (where) { TPoint newPos(where->x, where->y); newPos -= line.GetBound().TopLeft(); line.UpdatePosition(newPos); } line.UpdateBound(); Lines->Add(line); } delete is; if (GetDocPath()) { FileInfo = fileinfo; else { FileInfo = string(*::Module,IDS_FILEINFO); } SetDirty(false); UndoState = UndoNone; return true; } Updating line boundaries when a document is openedWhen a linked or embedded document is opened, the boundaries of the lines it contains must be updated: TDrawDocument::Open(int mode, const char far* path) { ? while (numLines--) { TLine line; *is >> line; line.UpdateBound(); ines->Add(line); } ? } Changing the view classNext you must change the TOleView class:
Adding new data membersTo store the currently selected line and current mode (Pen or Selection), declare the following data members of the view class, as shown in the STEP17DV.H file: TLine* Selected; enum DRAWTOOL { DrawSelect = 0, DrawPen, }; DRAWTOOL Tool; Then initialize the data members in the view class constructor, as shown in the STEP17DV.CPP file: TDrawView::TDrawView(TDrawDocument& doc, TWindow* parent) : TOleView(doc, parent), DrawDoc(&doc) { Selected = 0; Tool = DrawPen; ? } Naming the Clipboard formatSince the drawing application will be able to copy selections to the Clipboard, you need to name the Clipboard format. The format name appears in the Paste Link dialog box and is used in the EvOcClipData event handler, which is shown later. You can specify the name of the Clipboard format in the constructor for the view object, as shown in STEP17DV.CPP:TDrawView::TDrawView(TDrawDocument& doc, TWindow* parent) : TOleView(doc, parent), DrawDoc(&doc) { ? OcApp->AddUserFormatName("DrawPad Native Data", "Owl DrawPad native data", DrawPadFormat); } Adding the Select, Cut, Copy, and Pen commandsIn order for a document part to be linked or embedded, it must be selected, cut or copied from a server, and then pasted into a container. So in order for the drawing application to provide lines for linking and embedding, it must have Select, Cut, and Copy commands. These commands typically appear on the Edit menu and as buttons on the toolbar. Note: The Cut and Copy commands were not added in the previous steps of the tutorial because OWL handles the commands automatically for linked and embedded objects. However, in order to work with document parts, you must add the commands manually. In addition, in order to return to normal mode from the selection mode, you need a Pen command. (When an application is in normal mode, you can perform normal actions, but you can't select document parts. When an application is in selection mode, you can select document parts, but you can't perform normal actions. For example, in the drawing application, the Pen and Select commands allow you to switch between drawing and selecting lines.) To add the Select, Cut, Copy, and Pen commands,
Implementing the Select functionBy implementing the Select function, you control how a view responds to left-mouse clicks. (Select is a virtual member function of the TOleWindow class, from which the view class is derived.) This is the new Select function for the view class, as shown in STEP17DV.CPP:bool TDrawView::Select(uint modKeys, TPoint& point) { if (Tool != DrawSelect) return false; // Clicked in lines? TLine *line = HitTest(point); SetLineSelection(line); if (Selected) { // there is a selection ? } else // Select OLE object, if any return TOleView::Select(modKeys, point); } The Select function calls the HitTest function to determine whether or not the mouse cursor falls within a line boundary. If so, the SetLineSelection function is called to store the selected line in the Line data member of the view class. Otherwise, the TOleView::Select function is called, which automatically handles the selecting of OLE objects. To see the implementations of the HitTest and SetLineSelection functions, see the STEP17DV.CPP file. Implementing the PaintLink functionWhen a document wants to draw a linked object, it calls the PaintLink function, which is implemented in the step17dv.cpp file: bool TDrawView::PaintLink(TDC& dc, bool erase, TRect& rect, TString& moniker) { TLine* line = 0; //Draw the whole document if linking to the whole doc // if (strcmp(moniker, OleStr(DocContent)) == 0) { Paint(dc, erase, rect); return true; } // Find the selection with the corresponding moniker // line = DrawDoc->GetLine(moniker); if (!line) return false; TPoint newPos(Margin, Margin); newPos -= rect.TopLeft(); line->UpdatePosition(newPos); line->Draw(dc); line->UpdatePosition(-newPos); return true; } Implementing or enhancing OLE message handlersNext you must implement or enhance the OLE message handlers, as shown in the next sections. EvOcViewClipDataImplement this event handler to save the selected line to storage when the user copies it and to retrieve a selected line from storage when the user pastes it: bool TDrawView::EvOcViewClipData(TOcFormatData far& formatData) { if (strcmp(OleStr(formatData.Format.GetRegName()), OleStr(DrawPadFormat)) != 0) return false; // not our clipboard format bool status = true; if (formatData.Paste) { // Pasting native data DrawDoc->SetHandle(ofReadWrite, formatData.Handle, false); DrawDoc->OpenSelection(ofRead, 0, formatData.Where); Invalidate(); // Restore the original storage // DrawDoc->RestoreStorage(); } else { // Copying native data HANDLE data = GlobalAlloc(GHND|GMEM_SHARE, 0); DrawDoc->SetHandle(ofReadWrite, data, true); // Copy the selection if the target format is "DrawPad" // if (formatData.UserData) { status = DrawDoc->CommitSelection(*this, formatData.UserData); } else { status = DrawDoc->Commit(true); } formatData.Handle = data; // Restore the original storage // DrawDoc->RestoreStorage(); } return status; } EvOcViewGetItemNameImplement this event handler so that a name is associated with each line that is linked or embedded in a document. bool TDrawView::EvOcViewGetItemName(TOcItemName& item) { if (item.Selection) { if (!Selected) return false; char name[32]; itoa(DrawDoc->GetLines()->Find(*Selected), name, 10); item.Name = name; } else { item.Name = "content"; // item name representing the whole document } return true; } EvOcViewSetLinkImplement this event handler to create a TOleLinkView object for each linked or embedded line. (The TOleLinkView class is implemented later.) bool TDrawView::EvOcViewSetLink(TOcLinkView& view) { // Attach a linked view to this document // new TDrawLinkView(GetDocument(), view); return true; } EvOcViewPartsizeImplement this event handler so a container can determine the size of each line that is linked or embedded. bool TDrawView::EvOcViewPartSize(TOcPartSize far& ps) { TClientDC dc(*this); TRect rect(0, 0, 0, 0); TLine* line = 0; if (ps.Selection) { if (ps.Moniker) { if (strcmp(*ps.Moniker, OleStr(DocContent)) == 0) line = 0; // whole document else line = DrawDoc->GetLine(*ps.Moniker); } else{ line = (TLine*) ps.UserData; } } if (line) { *(TPoint*)&rect.left = line->GetBound().TopLeft(); rect.right = rect.left + line->GetBound().Width() + 2 * Margin; rect.bottom = rect.top + line->GetBound().Height() + 2 * Margin; } else { // a 2" x 2" extent for server // rect.right = dc.GetDeviceCaps(LOGPIXELSX) * 2; rect.bottom = dc.GetDeviceCaps(LOGPIXELSY) * 2; } ps.PartRect = rect; return true; } EvLButtonDownEnhance this event handler so that lines are drawn when the view is in Pen mode and lines are selected when the view is in Select mode. void TDrawView::EvLButtonDown(uint modKeys, TPoint& point) { TOleView::EvLButtonDown(modKeys, point); if (SelectEmbedded() || !DragDC) return; if (Tool == DrawSelect) { // selection // Select(modKeys, point); } else if (Tool == DrawPen) { SetCapture(); Pen = new TPen(Line->QueryColor(), Line->QueryPenSize()); DragDC->SelectObject(*Pen); DragRect.SetNull(); DragDC->MoveTo(point); Line->Add(point); } } Adding a TOleLinkView classFinally, you must add a TOleLinkView class. When a TOleLinkView object is associated with a linked or embedded line, all documents containing the line are notified when the line is changed or deleted (if you implement the VnModify and VnDelete event handlers). The actual TOleLinkView objects are created in the EvOcPasteLink event handler.DEFINE_RESPONSE_TABLE1(TDrawLinkView, TOleLinkView) EV_VN_DRAWDELETE, EV_VN_DRAWMODIFY, END_RESPONSE_TABLE; TDrawLinkView::TDrawLinkView(TDocument& doc, TOcLinkView& view) : TOleLinkView(doc , view) { DrawDoc = TYPESAFE_DOWNCAST(&doc, TDrawDocument); CHECK(DrawDoc); } TDrawLinkView::~TDrawLinkView() { } // // Line was modified // bool TDrawLinkView::VnModify(uint index) { // Get the selection corresponding to the moniker // TLine * line = DrawDoc->GetLine(GetMoniker()); if (!line) return false; // Notify the container // if (index == DrawDoc->GetLines()->Find(*line)) { UpdateLinks(); } return true; } // // Line was deleted // bool TDrawLinkView::VnDelete(uint index) { // Get the selection corresponding to the moniker // TLine * line = DrawDoc->GetLine(GetMoniker()); if (!line) return false; // Notify the container // if (index == DrawDoc->GetLines()->Find(*line)) { UpdateLinks(); } return true; } Adding non-OLE enhancements to the drawing applicationStep 17 also demonstrates the following non-OLE features:
For more information, see the STEP17DV.CPP file. |
|