The Device Context Classes

In Chapter 3 and Chapter 4, the view class's OnDraw member function was passed a pointer to a device context object. OnDraw selected a brush and then drew an ellipse. The Microsoft Windows device context is the key GDI element that represents a physical device. Each C++ device context object has an associated Windows device context, identified by a 32-bit handle of type HDC.

Microsoft Foundation Class (MFC) Library version 6.0 provides a number of device context classes. The base class CDC has all the member functions (including some virtual functions) that you'll need for drawing. Except for the oddball CMetaFileDC class, derived classes are distinct only in their constructors and destructors. If you (or the application framework) construct an object of a derived device context class, you can pass a CDC pointer to a function such as OnDraw. For the display, the usual derived classes are CClientDC and CWindowDC. For other devices, such as printers or memory buffers, you construct objects of the base class CDC.

The "virtualness" of the CDC class is an important feature of the application framework. In Chapter 19, you'll see how easy it is to write code that works with both the printer and the display. A statement in OnDraw such as

pDC->TextOut(0, 0, "Hello");

sends text to the display, the printer, or the Print Preview window, depending on the class of the object referenced by the CView::OnDraw function's pDC parameter.

For display and printer device context objects, the application framework attaches the handle to the object. For other device contexts, such as the memory device context that you'll see in Chapter 11, you must call a member function after construction in order to attach the handle.

The Display Context Classes CClientDC and CWindowDC

Recall that a window's client area excludes the border, the caption bar, and the menu bar. If you create a CClientDC object, you have a device context that is mapped only to this client area—you can't draw outside it. The point (0, 0) usually refers to the upper-left corner of the client area. As you'll see later, an MFC CView object corresponds to a child window that is contained inside a separate frame window, often along with a toolbar, a status bar, and scroll bars. The client area of the view, then, does not include these other windows. If the window contains a docked toolbar along the top, for example, (0, 0) refers to the point immediately under the left edge of the toolbar.

If you construct an object of class CWindowDC, the point (0, 0) is at the upper-left corner of the nonclient area of the window. With this whole-window device context, you can draw in the window's border, in the caption area, and so forth. Don't forget that the view window doesn't have a nonclient area, so CWindowDC is more applicable to frame windows than it is to view windows.

Constructing and Destroying CDC Objects

After you construct a CDC object, it is important to destroy it promptly when you're done with it. Microsoft Windows limits the number of available device contexts, and if you fail to release a Windows device context object, a small amount of memory is lost until your program exits. Most frequently, you'll construct a device context object inside a message handler function such as OnLButtonDown. The easiest way to ensure that the device context object is destroyed (and that the underlying Windows device context is released) is to construct the object on the stack in the following way:

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
    CRect rect;

    CClientDC dc(this);  // constructs dc on the stack
    dc.GetClipBox(rect); // retrieves the clipping rectangle
} // dc automatically released

Notice that the CClientDC constructor takes a window pointer as a parameter. The destructor for the CClientDC object is called when the function returns. You can also get a device context pointer by using the CWnd::GetDC member function, as shown in the following code. You must be careful here to call the ReleaseDC function to release the device context.

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
    CRect rect;

    CDC* pDC = GetDC();    // a pointer to an internal dc
    pDC->GetClipBox(rect); // retrieves the clipping rectangle
    ReleaseDC(pDC);        // Don't forget this
}

You must not destroy the CDC object passed by the pointer to OnDraw. The application framework handles the destruction for you.

The State of the Device Context

You already know that a device context is required for drawing. When you use a CDC object to draw an ellipse, for example, what you see on the screen (or on the printer's hard copy) depends on the current "state" of the device context. This state includes the following:

You have already seen, for example, that choosing a gray brush prior to drawing an ellipse results in the ellipse having a gray interior. When you create a device context object, it has certain default characteristics, such as a black pen for shape boundaries. All other state characteristics are assigned through CDC class member functions. GDI objects are selected into the device context by means of the overloaded SelectObject functions. A device context can, for example, have one pen, one brush, or one font selected at any given time.

The CPaintDC Class

You'll need the CPaintDC class only if you override your view's OnPaint function. The default OnPaint calls OnDraw with a properly set up device context, but sometimes you'll need display-specific drawing code. The CPaintDC class is special because its constructor and destructor do housekeeping unique to drawing to the display. Once you have a CDC pointer, however, you can use it as you would any other device context pointer.

Here's a sample OnPaint function that creates a CPaintDC object:

void CMyView::OnPaint()
{
    CPaintDC dc(this);
    OnPrepareDC(&dc); // explained later
    dc.TextOut(0, 0, "for the display, not the printer");
    OnDraw(&dc);      // stuff that's common to display and printer
}

For Win32 Programmers

The CPaintDC constructor calls BeginPaint for you, and the destructor calls EndPaint. If you construct your device context on the stack, the EndPaint call is completely automatic.