กก

กก

Drawing with Pens and Painting with Brushes


Download source code for this tutorial (courtesy Laszlo Radanyi)
 
Like a painter, you will need pens and brushes to create artwork on your canvas. When you call Canvas::Line or Canvas::Rectangle, Windows uses the currently attached pen to draw the lines and the currently attached brush to fill the insides of the shapes.

When you attach an object to the Canvas, you have to remember to detach it after you're done. Unless... you let C++ remember about that. Just use a local object whose constructor attaches, and destructor (called automatically when exiting the scope) detaches the object (see the Resource Management page for more details on this methodology). Notice that the following objects take HDC (handle to Device Context) as an argument in their constructors. However, you should simply pass them a Canvas object instead. Remember that Canvas can be automatically cast to HDC.


class StockObject
{
public:
    StockObject (HDC hdc, int type)
      : _hdc(hdc)
    {
        _hObjOld = SelectObject (_hdc, GetStockObject (type));
    }

    ~StockObject ()
    {
        SelectObject (_hdc, _hObjOld);
    }
private:
    HGDIOBJ  _hObjOld;
    HDC      _hdc;
};

Windows comes with a set of pre-defined pens and brushes. It is enough to attach them to your canvas for the time you want to use them.


class WhitePen : public StockObject
{
public:
    WhitePen (HDC hdc): StockObject (hdc, WHITE_PEN) {}
};

// example
void Controller::Paint (HWND hwnd)
{
    PaintCanvas canvas (hwnd);
    WhitePen pen (canvas);
    canvas.Line (0, 10, 100, 10);
    // destructor of WhitePen
    // destructor of PaintCanvas
}

If your program keeps using a small set of non-stock pens, you might want to create them up-front (e.g. by embedding them in the View object) and use a PenHolder object to temporarily attach them to your Canvas.


class Pen
{
public:
    Pen (COLORREF color)
    {
        _hPen = CreatePen (PS_SOLID, 0, color);
    }
    ~Pen ()
    {
        DeleteObject (_hPen);
    }
    operator HPEN () { return _hPen; }
private:
    HPEN    _hPen;
};

class PenHolder
{
public:
    PenHolder (HDC hdc, HPEN hPen)
        : _hdc (hdc)
    {
        _hPenOld = (HPEN)SelectObject (_hdc, hPen);
    }
    ~PenHolder ()
    {
        SelectObject (_hdc, _hPenOld);
    }
private:
    HDC     _hdc;
    HPEN    _hPenOld;
};

class View
{
public:
    View ()
        : _penGreen (RGB (0, 255, 128))
    {}

    void Paint (Canvas & canvas)
    {
        PenHolder holder (canvas, _penGreen);
        canvas.Line (0, 10, 100, 10);
        // destructor of PenHolder
    }
private:
    Pen     _penGreen;
};

Finally, if your program needs colored pens on-demand, i.e., you can't possibly preallocate them for all the colors you'll need, you should use colored pens. When you define an automatic ColorPen object, its constructor both creates and attaches the pen. When the destructor is called, at the end of the scope, it detaches the pen and deletes it.


class ColorPen
{
public:
    ColorPen (HDC hdc, COLORREF color)
        : _hdc (hdc)
    {
        _hPen = CreatePen (PS_SOLID, 0, color);
        _hPenOld = (HPEN)SelectObject (_hdc, _hPen);
    }
    ~ColorPen ()
    {
        SelectObject (_hdc, _hPenOld);
        DeleteObject (_hPen);
    }
private:
    HDC     _hdc;
    HPEN    _hPen;
    HPEN    _hPenOld;
};

You deal with Brushes in exactly the same way (there are, however, more types of brushes supported by Windows). For instance, here's a definition of a ColorBrush.


class ColorBrush
{
public:
    ColorBrush (HDC hdc, COLORREF color)
        : _hdc (hdc)
    {
        _hBrush = CreateSolidBrush (color);
        _hBrushOld = (HBRUSH)SelectObject (_hdc, _hBrush);
    }
    ~ColorBrush ()
    {
        SelectObject (_hdc, _hBrushOld);
        DeleteObject (_hBrush);
    }
private:
    HDC     _hdc;
    HBRUSH  _hBrush;
    HBRUSH  _hBrushOld;
};

As always, you are encouraged to experiment on your own.

And now for something completely different: Threads