Step 9: Changing pens

Back Home Up Next

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:

There is a new protected data member, Color (a TColor object). Color and PenSize make up the attributes necessary to construct a TPen object.
The constructor signature has changed from
TLine(int penSize = 1);
               

to


TLine(const TColor &color = (TColor) 0, int penSize = 1);
               

The constructor itself changes to set PenSize to the constructor's second parameter and to create a new TColor object and assign it to Color. If no parameters are specified and the first parameter takes on its default value, TColor::Black is used as the pen color.

The two QueryPen functions are abandoned in favor of two new functions: QueryPenSize, which returns the pen size as an int and QueryColor, which returns the pen color as a TColor.
Instead of using the query functions to set the pen attributes, there are two new functions called SetPen. One takes a single int parameter and the other takes a TColor & and an int. The pen query and set functions are discussed in the next section.
A Draw function is added so that the TLine class dictates how it is drawn. This function is virtual so that it can be easily overridden in a derived class.

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.

The insertion operator is modified to write out the PenSize and Color member. It then writes out the points just as it did before.
The extraction operator reads in the data and uses the PenSize and Color data in the SetPen function. Each point is read in from the file and added to the object.

Changes to the TDrawWindow class

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

The Pen data member is constructed from the size and color of the current line.
The SetPenSize function is removed. The function GetPenSize opens a TInputDialog for the user to enter a new pen size in. GetPenSize then calls the function Line->SetPen to actually set the pen size.
The CmPenColor function is added to handle the CM_PENCOLOR event. This event is sent from the new Tools menu choice Pen Color.

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:

The TPen and TColor classes are discussed in "Graphics objects" in the ObjectWindows Programmer's Guide.
Dialog boxes, including the TChooseColorDialog class, are discussed in "Dialog box objects" in the ObjectWindows Programmer's Guide.


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