Step 8: Adding multiple lines

Back Home Up Next

You can find the source for Step 8 in the files STEP08.CPP and STEP08.RC in the directory EXAMPLES\OWL\TUTORIAL. Step 8 makes a great leap in terms of usefulness. In this step, you'll add a new class, TLine, that is derived from the TPoints array you've been using to contain the points in a line. You'll then define another array class, TLines, that contains an array of TLine objects, enabling us to have multiple lines in the window. You'll add streaming operators to make it a little easier to save drawings. Lastly, you'll develop the Paint function further to handle drawings with multiple lines.

TLine class

The TLine class is derived from the public base class TPoints. This gives TLine all the functionality that you've been using with the Line member of the TDrawWindow class. This includes the Add, Flush, and GetItemsInContainer functions that you've been using. In addition, you can continue to use TPointsIterator with the TLine class in the same way you used it with TPoints.

But because you're creating your own class now, you can also add any additional functionality you need. For example, you should add a data member to contain the size of the pen for each line. Then, to hide the data, add accessor functions to manipulate the data.

In TLine, the pen size is contained in a protected int called PenSize. PenSize is accessed by one of two functions, both called QueryPen. Both versions of QueryPen return an int, which contains the value of PenSize. Here's the difference between the two functions:

The first QueryPen function takes no parameters. This function returns the pen size.
The second QueryPen function takes a single parameter, an int. This function sets PenSize to the value passed in, then returns the new value of PenSize. You can use the return value to check whether QueryPen actually set the pen to the value you passed to it. This version of QueryPen checks the value of the parameter to make sure that it's a legal value for the pen size.

TLine also contains a definition for the == operator. This operator checks to see if the two objects are actually the same object. If so, the operator returns true. Defining an array using the TArray class (which you'll do later when defining TLines) requires that the object used in TArray have the == operator defined.

Lastly you should declare two operators, << and >>, to be friends of the TLine class. When these operators are implemented later in this section, they'll provide easy access to stream operations for the SaveFile and OpenFile functions.

Here is the declaration of the TLine class:

class TLine : public TPoints
{
  public:
    TLine(int penSize = 1) : TPoints(10, 0, 10) { PenSize = penSize; }

    int QueryPen() const { return PenSize; }
    int QueryPen(int penSize);

    // The == operator must be defined for the container class,
    // even if unused
    bool operator ==(const TLine& other) const
      { return &other == this; }
    friend ostream& operator <<(ostream& os, const TLine& line);
    friend istream& operator >>(istream& is, TLine& line);
  protected:
    int PenSize;
};
       

TLines array

Once you've defined the TLine class, you can define the TLines array and the TLinesIterator array. These containers work the same way as the TPoints and TPointsIterator container classes that you defined earlier. The only difference is that, instead of containing an array of TPoint objects like TPoints, TLines contains an array of TLine objects.

Here are the definitions of TLines and TLinesIterator:

typedef TArray<TLine> TLines;
typedef TArrayIterator<TLine> TLinesIterator;
      

Insertion and extraction of TLine objects

Most objects that need to be saved to and retrieved from files on a regular basis are set up to use the insertion and extraction operators << and >>. By declaring these operators as friends of TLine, you need to define the operators to handle the particular type of data encapsulated in TLine.

Having these operators defined gives you the ability to place an entire TLine object into a file with a single line of code. You'll see how this is used when you make the changes to the OpenFile and SaveFile functions.

Insertion operator <<

In essence, the insertion operator takes on the functionality of the SaveFile function used in Step 7. It doesn't have to open a file (that's handled by whatever function uses the operator) and it has an extra piece of data to insert (PenSize). Other than that, it's not much different. Compare the definition of this function with the SaveFile function from Step 7. Notice the use of TPointsIterator with the TLine object:

ostream& operator <<(ostream& os, const TLine& line)
{
  // Write the number of points in the line
  os << line.GetItemsInContainer() << '


  // Write the pen size
  os << ' ' << line.PenSize;

  // Get an iterator for the array of points
  TPointsIterator j(line);

  // While the iterator is valid (i.e. it hasn't run out of points)
  while(j)
    // Write the point from the iterator and increment the array.
    os << j++;

  os << '


  // return the stream object
  return os;
}
       

Extraction operator >>

Much like the insertion operator, the extraction operator takes on the functionality of the OpenFile function in Step 7. It doesn't have to open a file itself and it has an extra piece of data to extract. Other than that, it's implemented similarly to the OpenFile function:

istream& operator >>(istream& is, TLine& line)
{
  unsigned numPoints;

  is >> numPoints;

  is >> line.PenSize;

  while (numPoints--) {
    TPoint point;
    is >> point;
    line.Add(point);
  }
  // return the stream object
  return is;
}
       

Extending TDrawWindow

There are a number of changes required in TDrawWindow to accommodate the new TLine class. First, there are a number of changes in data members:

PenSize is removed. Each individual line now contains its pen size.
The Line data member is changed from a TPoints * to a TLine *. The Line object holds the points in the line currently being drawn.
The Lines data member, a TLines *, is added. The Lines object contains all the TLine objects.

The following functions are modified or added:

The SetPenSize function is made protected because changes to the pen size should be made to the TLine class. SetPenSize should now be used only by the TDrawWindow class internally. SetPenSize also sets the pen size for the current line by calling that line's QueryPen function.
The GetPenSize function is added. This function implements the TInputDialog that was handled in EvRButtonDown. This is because two functions now use this same dialog box, EvRButtonDown and CmPenSize.
The EvRButtonDown function now calls GetPenSize to open the input dialog box.
The CmPenSize function handles the CM_PENSIZE event. This event comes from a new menu choice, Pen Size, on a new menu, Tools. This function is added to give the user another way to change the pen size.
The OpenFile and SaveFile functions are modified to store an array of TLine objects instead of an array of TPoint objects. By using the insertion and extraction operators, these functions change very little from their prior forms.

In addition, the Paint function is changed quite a bit, as described in the following section.

Paint function

The Paint function must now perform two iterations instead one. Instead of iterating through a single array of points, Paint must now iterate through an array of lines. For each line, it must set the pen width and then iterate through the points that compose the line.

Paint does this by first creating an iterator from Lines. This iterator goes through the array of lines. For each line, Paint queries the pen size of the current line. It sets the window's Pen to this size and selects this pen into the device context. It then creates an iterator for the current line and increments the line array iterator.

The next part of Paint looks like the Paint function from Step 7. That's because it does basically the same thing as that function-it takes the array of points and draws the line in the window.

Here is the code for the new Paint function:

void
TDrawWindow::Paint(TDC& dc, bool, TRect&)
{
  // Iterates through the array of line objects.
  TLinesIterator i(*Lines);

  while (i) {
    // Set pen for the dc to current line's pen.
    TPen pen(TColor::Black, i.Current().QueryPen());
    dc.SelectObject(pen);

    // Iterates through the points in the line i.
    TPointsIterator j(i++);
    bool first = true;

    while (j) {
      TPoint p = j++;

      if (!first)
        dc.LineTo(p);
      else {
        dc.MoveTo(p);
        first = false;
      }
    }
  }
}
       

Where to find more information

Here's a guide to where you can find more information on the topics introduced in this step:

Window classes are discussed in "Window objects" of the ObjectWindows Programmer's Guide.
The Borland C++ container class library and the TArray and TArrayIterator classes are explained in Chapter 1 of the Class Libraries Guide.
 


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