[Contents] [Prev] [Next]

Step 9: Changing pens

You can find the source for Step 9 in the files STEP09.CPP and STEP09.RC in the directory EXAMPLES\OWL\TUTORIAL. In Step 9, you'll add a TColor member to the TLine class, letting the user draw with lines of different widths and different colors. To change the color of the line, you'll add the CmPenColor function. This function handles the CM_PENCOLOR menu command. CmPenColor uses the TChooseColorDialog class to let the user change colors. It also adds some helper functions to deal with changes to the width and color and give external classes access to information about the line.

Along with adding color to the pen, Step 9 adds functionality to the streaming operators to deal with the new attributes of the TLine class. It also adds a Draw function to the TLine class to make the class more self-sufficient and to make the Paint function simpler.

Changes to the TLine class

A number of changes to the TLine class declaration are required to accommodate the new functionality:

Here's how the new TLine class declaration should look:

class TLine : public TPoints {
  public:
    // Constructor to allow construction from a color and a pen size.
    // Also serves as default constructor.
    TLine(const TColor &color = TColor(0), int penSize = 1)
      : TPoints(10, 0, 10), PenSize(penSize), Color(color) {}
    // Functions to modify and query pen attributes.
    int QueryPenSize() { return PenSize; }
    TColor& QueryColor() { return Color; }
    void SetPen(TColor &newColor, int penSize = 0);
    void SetPen(int penSize);

    // TLine draws itself. Returns true if everything went OK.
    virtual bool Draw(TDC &) const;

    // 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;
    TColor Color;
};

Pen access functions

In Step 8, the QueryPen function could be used both to access the current size of the pen and to set the size of the pen. The new TLine query functions-QueryPenSize and QueryColor-can't be used to modify the pen attributes. These functions only return pen attributes.

To set pen attributes, there are two new functions called SetPen. The first SetPen sets just the pen size. The other SetPen can be used to set the color, size, and style of the pen. But by letting the second and third parameters take on their default values, you can use the second constructor to set just the color. Here's the code for these functions:

void
TLine::SetPen(int penSize)
{
  if (penSize < 1)
    PenSize = 1;
  else
    PenSize = penSize;
}

void
TLine::SetPen(TColor &newColor, int penSize)
{
  // If penSize isn't the default (0), set PenSize to the new size.
  if (penSize)
    PenSize = penSize;

  Color = newColor;
}

Draw function

The Draw function draws the line in the window, taking that functionality from the window's Paint function. This functionality is moved because the TLine object can now dictate how it gets painted onscreen. Take a look at the code for the Draw function below and compare this to the Paint function from Step 8. From a certain point, the two bits of code are nearly identical:

bool
TLine::Draw(TDC &dc) const
{
  // Set pen for the dc to the values for this line
  TPen pen(Color, PenSize);
  dc.SelectObject(pen);

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

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

    if (!first)
      dc.LineTo(p);
    else {
      dc.MoveTo(p);
      first = false;
    }
  }
  dc.RestorePen();
  return true;
}
After putting all this code into the TLine class, the TDrawWindow::Paint function is greatly simplified:

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

  while (i)
    i++.Draw(dc);
}

Insertion and extraction operators

There also some changes to the insertion and extraction operators that are necessary to handle the revised TLine class.

Changes to the TDrawWindow class

There are a few fairly minor changes to the TDrawWindow class to accommodate the revised TLine class:

CmPenColor function

The CmPenColor function opens a TChooseColorDialog for the user to select a color from. Like TFileOpenDialog and TFileSaveDialog, TChooseColorDialog is an encapsulation of one of the Windows common dialog boxes.

Also like TFileOpenDialog and TFileSaveDialog, the TChooseColorDialog constructor can take up to five parameters, but in this case you need only two. The last three all have default values. The two parameters you need to provide are a pointer to the parent window and a reference to a TChooseColorDialog::TData object. In this case, the pointer to the parent window is simply the this pointer. The TChooseColorDialog::TData object is provided by colors.

Setting the Color member of colors to a particular color makes that color (or its closest equivalent displayed in the dialog box) the default color in the dialog box. By setting Color to the color of the current pen, you ensure that the Color dialog box reflects the current state of the application.

Setting the CustColors member of the colors object to some array of TColor objects sets those colors in the Custom Colors section of the Color dialog box. You can use whatever colors you want for the CustColors array. The values that are used in the tutorial produce a range of monochrome colors that goes from black to white.

Creating and executing a TChooseColorDialog works exactly the same as for a TFileOpenDialog or TFileSaveDialog. Although the Color dialog box has an extra button (the Define Custom Colors button), that button is handled by the Windows part of the common dialog box. Therefore there are only two possible results for the Execute function, IDOK and IDCANCEL. If the user selects Cancel, you ignore any changes from the dialog box.

On the other hand, if the user selects OK, you need to change the pen color to the new color chosen by the user. The TChooseColorDialog places the color chosen by the user into the Color member of the colors object. Color is a TColor, which fits nicely into the SetPen function of a TLine object.

Here's the code for the CmPenColor function:

void
TDrawWindow::CmPenColor()
{
  TChooseColorDialog::TData colors;
  static TColor custColors[16] =
  {
    0x010101L, 0x101010L, 0x202020L, 0x303030L,
    0x404040L, 0x505050L, 0x606060L, 0x707070L,
    0x808080L, 0x909090L, 0xA0A0A0L, 0xB0B0B0L,
    0xC0C0C0L, 0xD0D0D0L, 0xE0E0E0L, 0xF0F0F0L
  };

  colors.Flags = CC_RGBINIT;
  colors.Color = TColor(Line->QueryColor());
  colors.CustColors = custColors;
  if (TChooseColorDialog(this, colors).Execute() == IDOK)
    Line->SetPen(colors.Color);
}

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]