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:
- Add new data members for storing the rectangular boundaries of a line and whether or not
the line is selected.
- Update the constructor so the new data members are initialized.
- 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:
- Add new data members for storing the currently selected line and the current mode
(normal or selection).
- Name the Clipboard format.
- Add the Pen, Select, Cut, and Copy commands.
- Implement the Select function.
- Implement the PaintLink function.
- 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,
- Include the commands in the Edit menu section of the resource (.RC) file.
- Add the buttons to the toolbar bitmap section of the resource file.
- Add the commands and their corresponding enablers to the response table for the view class.
- Insert the new buttons on the Toolbar by enhancing the TDrawView::EvOcViewShowTools function.
- 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:
- Using the cursor to show the current mode.
- Dragging and dropping a selection.
- Zooming in on and out from a drawing document.
- Adding scroll bars to windows containing linked documents.
For more information, see
the STEP17DV.CPP file.
Prev
Up
Next
|