[Contents] [Prev] [Next]

Step 17: Enhancing the linking and embedding capabilities of an OLE container / server

In 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 class

The first step to enhancing the linking and embedding capabilities of the drawing application is to make changes to the TLine class:

  1. Add new data members for storing the rectangular boundaries of a line and whether or not the line is selected.
  2. Update the constructor so the new data members are initialized.
  3. Add new member functions that draw a rectangle around a line, invalidate a selection, update a boundary, and change the position of a line.

Adding new data members

A 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 constructor

The 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 functions

The following sections show the member functions you must add to the TLine class.

DrawSelection

When 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.

UpdateBound

When 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;
}

UpdatePosition

A 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);
}

Invalidate

A 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 class

You must change the TOleDocument class so that it can save and open individual lines.

Saving individual lines

When 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 lines

When 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 opened

When 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 class

Next you must change the TOleView class:

  1. Add new data members for storing the currently selected line and the current mode (normal or selection).
  2. Name the Clipboard format.
  3. Add the Pen, Select, Cut, and Copy commands.
  4. Implement the Select function.
  5. Implement the PaintLink function.
  6. Implement or enhance the OLE message handlers.

Adding new data members

To 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 format

Since 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 commands

In 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,

  1. Include the commands in the Edit menu section of the resource (.RC) file.
  2. Add the buttons to the toolbar bitmap section of the resource file.
  3. Add the commands and their corresponding enablers to the response table for the view class.
  4. Insert the new buttons on the Toolbar by enhancing the TDrawView::EvOcViewShowTools function.
  5. Declare and implement member functions of the view class that correspond to the commands. For the implementations of the member functions for each command, see the STEP17DV.CPP source file.

Implementing the Select function

By 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 function

When 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 handlers

Next you must implement or enhance the OLE message handlers, as shown in the next sections.

EvOcViewClipData

Implement 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;
}

EvOcViewGetItemName

Implement 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;
}

EvOcViewSetLink

Implement 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;
}

EvOcViewPartsize

Implement 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;
}

EvLButtonDown

Enhance 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 class

Finally, 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 application

Step 17 also demonstrates the following non-OLE features:

For more information, see the STEP17DV.CPP file.


[Contents] [Prev] [Next]