[Contents] [Prev] [Next]

Step 8: Adding multiple lines

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:

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
    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);
    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;
  // 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:

The following functions are modified or added:

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:

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());

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

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

      if (!first)
      else {
        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:

[Contents] [Prev] [Next]