GDI

Win32++

Graphics Device Interface (GDI)

About the GDI

The Graphics Device Interface (GDI) is part of the Windows operating system. It provides applications with a means of sending graphics information to devices such as the video display and printer.

When using the GDI, the graphics device is represented as a device context (DC). All drawing calls are made through a device-context object, which encapsulates the Windows APIs for drawing lines, shapes, and text. Device contexts allow device-independent drawing in Windows. GDI objects such as bitmaps, brushes, palettes and pens are selected into the device context before they can be used to display the graphic information. Fonts can also be selected into the device context to manage the rendering of text.

The GDI classes in Win32++

The set of GDI classes provided by Win32++ are as follows:

The CDC classes (CDC, ClientDC, CMemDC, CMetaFileDC, CPaintDC, CWindowDC) provides a GDI device context. The CDC classes can also be used to create the other GDI resources such as Bitmaps, Brushes, Fonts etc. These are automatically selected into the device context when they are created. These are also deleted when the CDC goes out of scope.

With their ability to create device contexts as well as other GDI resources, the CDC classes are sufficient for most GDI programming needs. Sometimes however, we need to have the GDI resources separated from the device context. Wrapper classes for GDI resources are provided for this purpose. These classes are CBitmap, CBrush, CFont, CPalette, CPen and CRgn.

Using the GDI classes

Using the Windows API without CDC

This code demonstrates how to use the Windows API functions directly to draw a line with the GDI.

void DrawLine()
{
  HDC hdcClient = ::GetDC(m_hWnd);
  HDC hdcMem = ::CreateCompatibleDC(hdcClient);
  HBITMAP hBitmap = ::CreateCompatibleBitmap(hdcClient, cx, cy);
  HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hdcMem, hBitmap);
  HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(255,0,0);
  HPEN hOldPen = (HPEN)::SelectObject(hdcMem, hPen);
  ::MoveToEx(hdcMem, 0, 0, NULL);
  ::LineTo(hdcMem, 50, 50);
  ::BitBlt(hdcClient, 0, 0, cx, cy, hdcMem, 0, 0);
  ::SelectObject(hdcMem, hOldPen);
  ::DeleteObject(hPen);
  ::SelectObject(hdcMem, hOldBitmap);
  ::DeleteObject(hBitmap);
  ::DeleteDC(hdcMem);
  ::ReleaseDC(m_hWnd, hdcClient);
}
Using CDC classes alone

This code performs the same task as shown above using the CDC class.

void DrawLine()
{
  CClientDC dcClient(this)
  CMemDC dcMem(&dcClient;);
  dcMem.CreateCompatibleBitmap(&dcClient;, cx, cy);
  CMemDC.CreatePen(PS_SOLID, 1, RGB(255,0,0);
  CMemDC.MoveTo(0, 0);
  CMemDC.LineTo(50, 50);
  dcClient.BitBlt(0, 0, cx, cy, &CMemDC;, 0, 0);
}
Using CDC classes with CPen

This code uses a CDC and a separate CPen to draw the line.

void DrawLine()
{
  CClientDC dcClient(this)
  CMemDC dcMem(&dcClient;);
  dcMem.CreateCompatibleBitmap(&dcClient;, cx, cy);
  CPen MyPen(PS_SOLID, 1, RGB(255,0,0));
  CPen* pOldPen = dcMem.SelectObject(&MyPen;);
  dcMem.MoveTo(0, 0);
  dcMem.LineTo(50, 50);
  dcClient.BitBlt(0, 0, cx, cy, &dcMem;, 0, 0);
}

Notes:

  • When the CDC object drops out of scope, it's destructor is called, cleaning up any GDI resources it created, as well as the device context.
  • The device context is returned to its initial state before deletion, removing the need to select the OldPen back into the device context.
  • When the CPen object drops out of scope, it's destructor is called, deleting its associated GDI object (HPEN).
Creating a Window DC

A window DC is a device context which represents the entire window, including the non-client area. We can create a window DC as follows:

CWindowDC dcWindow(this);
Creating a Window Client DC

The following code creates a device context for the client area of the window. The following code shows how we can create a DC for the window's client area.

CClientDC dcClient(this);
Creating a Memory DC

As the name suggests, a memory DC is created in memory. Memory DCs are typically used for double buffering. With this programming technique we perform the drawing tasks to a memory DC, and then copy the bitmap to our window.

When creating a memory DC we provide a pointer to the CWindowDC or CClientDC that the memory DC is compatible with. We then create the compatible bitmap that the memory DC will draw to. The following code demonstrates how this is done.

// Create our memory client DC
CRect rc = GetClientRect();
CClientDC dcClient(this);

// Create our memory DC and compatible bitmap
CMemDC dcMem(&dcClient);
dcMem.CreateCompatibleBitmap(&dcClient, rc.Width(), rc.Height());

// Draw some stuff on the memory DC
dcMemC.CreatePen(PS_SOLID, 1, RGB(255,0,0);
dcMem.MoveTo(0, 0);
dcMem.LineTo(50, 50);

// Copy the Memory DC's bitmap to the window's client DC
dcClient.BitBlt(0, 0, rc.Width(), rc.Height(), &dcMem, 0, 0, SRCCOPY);

The use of a memory DC doesn't necessarily make the graphics any faster, but can reduce or even eliminate annoying flicker.

Handling OnDraw and OnEraseBkgnd

The OnDraw and OnEraseBkgnd functions provide a pointer to the CDC for us to use. The following code demonstrates how these functions can be used.

void CView::OnDraw(CDC* pDC)
{
  CRect rc = GetClientRect();

  // Centre some text in our view window
  pDC->DrawText(_T("View Window"), -1, rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
BOOL CView::OnEraseBkgnd(CDC* pDC)
{
  CBrush MyBrush(RGB(255,255,230)); // this could also be a member variable
  CRect rc = GetClientRect();
  FillRect(rc, MyBrush);
}
Notes:
  • A device context assigned to a CDC object will be released or deleted when the CDC is destroyed, unless it is detached.
  • A GDI resource created by one of the CDC member functions will be deleted when the CDC object is destroyed.
  • The GDI resources selected into the CDC with SelectObject are not deleted when the CDC goes out of scope. Only GDI resources created by the CDC are automatically deleted.
  • GDI resources belonging to the various GDI wrapper classes (eg. CPen) are automatically deleted when the class object is destroyed, unless they are detached .
  • A bitmap GDI object can only be selected into one device context at a time.
  • Set the region's shape before selecting it into a DC.