Reliable Software Logo

Canvas, or Windows Device Context

To paint, draw or print in a window you need a device context, DC for short. A DC is a resource that you borrow from Windows and you're supposed to return it immediately after you're done. That's where the Canvas object comes. The Canvas' constructor obtains the DC and the Canvas' destructor releases it. It is important that you create Canvas objects as automatic (stack) variables. That will guarantee that their destructor is always called when the program exits the local scope where they are defined (this class is an example of a more general Resource Management methodology). The typical use of the Canvas object is as follows (as you've already seen in the Generic program):

void Controller::Paint ()
{
    // prepare the canvas and let View do the rest
    PaintCanvas canvas (_hwnd);
    _view.Paint (canvas, _model);
    // Notice: The destructor of PaintCanvas called automatically!
}

All the various types of Canvas share the common ancestor-- the Canvas class. Notice that Canvas' constructor is protected. You can't actually instantiate an object of this class. However, the derived classes are free to provide their own constructors. By the way, you can keep adding new methods to Canvas as the need arises.

class Canvas
{
public:
    // operator cast to HDC
    // (used when passing Canvas to Windows API)
    operator HDC () { return _hdc; }

    void Point (int x, int y, COLORREF color)
    {
        ::SetPixel (_hdc, x, y, color);
    }

    void MoveTo (int x, int y)
    {
        ::MoveToEx (_hdc, x, y, 0);
    }

    void Line ( int x1, int y1, int x2, int y2 )
    {
        MoveToEx (_hdc, x1, y1, 0);
        LineTo (_hdc, x2, y2);
    }

    void Rectangle (int left, int top, int right, int bottom)
    {
        // draw rectangle using current pen
        // and fill it using current brush
        ::Rectangle (_hdc, left, top, right, bottom);
    }

    void GetTextSize (int & cxChar, int & cyChar)
    {
        TEXTMETRIC tm;
        GetTextMetrics (_hdc, & tm);
        cxChar = tm.tmAveCharWidth;
        cyChar = tm.tmHeight + tm.tmExternalLeading;
    }

    void Text (int x, int y, char const * buf, int cBuf)
    {
        ::TextOut ( _hdc, x, y, buf, cBuf );
    }

    void Char (int x, int y, char c)
    {
        TextOut (_hdc, x, y, & c, 1);
    }

    void SelectObject (void* pObj)
    {
        ::SelectObject (_hdc, pObj);
    }

protected:

    Canvas (HDC hdc): _hdc (hdc) {}

    HDC  _hdc;
};

In response to the WM_PAINT message one should construct the PaintCanvas object. Notice the way PaintCanvas obtains and releases the DC.

class PaintCanvas: public Canvas
{
public:
    // Constructor obtains the DC
    PaintCanvas (HWND hwnd)
    : Canvas (BeginPaint (hwnd, & _paint)),
      _hwnd (hwnd)
    {}

    // Destructor releases the DC
    ~PaintCanvas ()
    {
        EndPaint (_hwnd, & _paint);
    }

protected:

    PAINTSTRUCT _paint;
    HWND        _hwnd;
};

Another important example is the UpdateCanvas which is used for graphical operations outside of the context of WM_PAINT processing. Of course, your program may always force the repainting by calling InvalidateRect, but in many cases it would be an overkill. If your program keeps painting new things as it executes or in response to user actions, you can update the window using UpdateCanvas.

class UpdateCanvas: public Canvas
{
public:
    UpdateCanvas (HWND hwnd)
    :   Canvas (GetDC (hwnd)),
        _hwnd(hwnd)
    {}

    ~UpdateCanvas ()
    {
        ReleaseDC (_hwnd, _hdc);
    }

protected:

    HWND _hwnd;
};

There are other types of Canvas: DrawItemCanvas used for owner-draw controls, MemCanvas for drawing on a piece of memory, etc.

Use pens and brushes to draw and paint on your canvas.