
Introduction
Many developers are comfortable with obtaining a handle to a device context (DC) and
painting to it. However there is probably alot of confusion in how all of the parts
to windows painting are structured. Confusion in what the different DCs are good for,
and how each of these DCs can be used effectively. This article will
describe in detail, how the different parts of the WIN32 paint system are combined
and how to use the common and device DCs in the paint system.
The memory and metafile DCs will be ignored in this article.
This tutorial will also explain how the SDK relates to each of the wrapper classes
for MFC and WTL. With this knowledge, developers can then make wiser decisions
on when you will create, cache, and use DCs in your Windows painting. The tutorials and
code examples are written in WTL, however, the wrapper classes are very similar for both
MFC and WTL.
There is a beginner tutorial that precedes this article. The preceding artical covers basics of
painting to a window with a DC. This article is entitled:
Guide to WIN32 Paint for Beginners.
Anatomy of a Window
A window is the fundamental object through which information is conveyed to a user in WIN32
operating systems. A window is a system resource that is managed by the operating system and
can be accessed by the host application with a window handle (HWND). There are many
pieces of information that describe a window. This section will describe the different
characteristics that relate to paint.
Physical Characteristics
The portion of a window where most of the information is displayed is called the client
area. Most often this area is represented by a rectangle. The rectangle can be retrieved
from the window with this function:
::GetClientRect(HWND hWnd, RECT *pRect);
Often times, the client area will be surrounded by a region of the window called the
non-client area. The non-client area encompasses the window borders, caption and the
menus if any are present. A rectangle that represents the non-client area can be
retrieved from the window with this function:
::GetWindowRect(HWND hWnd, RECT *pRect);
This function differs from
GetClientRect in the fact that the rectangle is
returned in screen coordinates, and the rectangle also encompasses the client area of the
window. Therefore, if an application simply wants to access the region that represents the
client area of the window, then this code must be performed:
RECT rWindow;
RECT rClient;
HRGN hRgnWindow;
HRGN hRgnClient;
HRGN hNCRgn;
::GetWindowRect(hWnd, &rWindow);
::GetClientRect(hWnd, &rClient);
POINT pt = {0,0};
::MapWindowPoints(hWnd, NULL, &pt, 1);
::OffsetRect(&rClient, pt.x, pt.y);
hRgnWindow = ::CreateRectRgnIndirect(&rWindow);
hRgnClient = ::CreateRectRgnIndirect(&rClient);
hNCRgn = ::CreateRectRgn(0,0,0,0);
::CombineRgn(hNCRgn, hWindowRgn, hClientRgn, RGN_DIFF);
...
::DeleteObject(hRgnWindow);
::DeleteObject(hRgnClient);
::DeleteObject(hNCRgn);
Here is a picture that illustrates the different regions of a window:
Update Region
The WIN32 kernel manages the state of a window. One of the most important elements
of a window is its update region. The update region remembers all of the portions of
the window that need to be redrawn. A region may need to be redrawn for a number of
reasons:
- Another window occludes the view of the target window.
- The window is resized, restored from a minimized state, or maximized.
- The window is activated or deactivated and the caption needs to be repainted.
- The application forces the window to repaint itself.
At any point in time the application can query the current update region for a window
with this code:
HRGN hUpdateRgn;
hUpdateRgn = ::CreateRectRgn(0,0,0,0);
::GetUpdateRgn(hWnd, hUpdateRgn, FALSE);
RECT rUpdateBox;
::GetUpdateRect(hWnd, &rUpdateBox, FALSE);
The update region can be very important for applications that would like to clip
their painting code to the update region in order to improve performance.
The application can modify the update region manually, either by adding a
new portion to the update region, in effect invalidating the region, or by subtracting
away from the update region, or validating that region. Here is the set of functions
that can be used to modify the update region.
InvalidateRect
The InvalidateRect function adds a rectangle to the specified window's update region.
BOOL InvalidateRect(
HWND hWnd, CONST RECT *lpRect, BOOL bErase );
InvalidateRgn
The InvalidateRgn function invalidates the client area within the specified region
by adding it to the current update region of a window.
BOOL InvalidateRgn(
HWND hWnd, HRGN hRgn, BOOL bErase );
ValidateRect
The ValidateRect function validates the client area within a rectangle by
removing the rectangle from the update region of the specified window.
BOOL ValidateRect(
HWND hWnd, CONST RECT *lpRect );
ValidateRgn
The ValidateRgn function validates the client area within a region by
removing the region from the current update region of the specified window.
BOOL ValidateRgn(
HWND hWnd, HRGN hRgn );
RedrawWindow
The RedrawWindow function updates the specified rectangle or region in a
window's client area. This function will modify the update region. This function
can also force a repaint of the window and its children based on the flags that
are set.
BOOL RedrawWindow(
HWND hWnd, CONST RECT *lprcUpdate, HRGN hrgnUpdate, UINT flags );
Here is a list of the flags that can be used with this function and a short
description of what each flag does:
- RDW_ERASE: Causes the window to receive a
WM_ERASEBKGND message when the window is repainted.
- RDW_FRAME: Causes a
WM_NCPAINT message to be sent.
- RDW_INTERNALPAINT: Causes a
WM_PAINT message to be posted to the message queue even if the update region is empty.
- RDW_INVALIDATE: Invalidates the window with either
lprcUpdate or
hrgnUpdate. If both of these are NULL, then the entire window is invalidated.
- RDW_NOERASE: Suppresses any pending
WM_ERASEBKGND message.
- RDW_NOFRAME: Suppresses any pending
WM_NCPAINT messages.
- RDW_NOINTERNALPAINT: Suppresses any internal
WM_PAINT messages that are not a result of the update region.
- RDW_VALIDATE: Validates the window with either
lprcUpdate or hrgnUpdate. If both of these are NULL, then the entire window is validated.
- RDW_ERASENOW:
WM_ERASEBKGND and WM_NCPAINT messages will be sent before the function exits.
- RDW_UPDATENOW: The window will be sent a
WM_PAINT message before the function exits.
- RDW_ALLCHILDREN: Includes any children in the repainting operations.
- RDW_NOCHILDREN: Excludes any children from the repainting operations.
Class Styles that Affect the Update Region
There are two class styles for a window that will affect the update region for
a window when the window is sized. These are CS_HREDRAW and CS_VREDRAW.
These flags can be set in any combination. If the window is resized in a horizontal
fashion and the CS_HREDRAW style is set, the entire display will be invalidated
and redrawn. Likewise, if the window is resized vertically and the CS_VREDRAW
style is set the entire display will be invalidated. These styles can be useful for
displays where the view can be scrolled.
The WM_PAINT / WM_NCPAINT Messages
A window is responsible to paint its own display. A window should paint its
display in response to a WM_PAINT message. If the non-client region should be
updated then the painting should occur in WM_NCPAINT. WM_PAINT and
WM_NCPAINT
messages are only generated when the update region is not empty. There are other
ways to force a WM_PAINT message to be generated, as stated above, the
RedrawWindow function can do this. It is not wise, however, to
send a WM_PAINT message directly because of the nature of the update region.
When the WM_PAINT and WM_NCPAINT messages are handled, they must be handled
properly. The largest risk is the mismanagement of the update region. If portions
of this region are prematurely validated, then parts of the window will never
be updated. On the otherhand, if portions of the update region are never validated,
then the system will believe that the window always needs to be updated, and
will continue to send WM_PAINT messages. THis will have a serious effect on performance.
The WM_ERASEBKGND Message
The WM_ERASEBKGND message is generated to clear a window's client area
before that window is painted. The WM_ERASEBKGND message is generated in a few
places. The most common place this message is generated is in the WM_PAINT handlers
call to BeginPaint. That is another reason why it is important to
call BeginPaint inside of the WM_PAINT handler.
The WM_ERASEBKGND message is passed a pre-initialized DC in the
wParam parameter. An application that handles WM_ERASEBKGND should use this
DC to paint the bacground, because any drawing actions that are performed will be
clipped to the current update region in order to reduce flicker on the update.
Device Contexts
There are four types of DCs. The fundamental purpose of each DC is to provide
a basis for drawing on a device. The area that can be drawn is determined
by the type of DC that is created. This section will describe the different types
of DCs that are used in painting and how they are encapsulated by MFC and WTL.
The first type of DC that will be described is the common DC. The common DC
encapsulates two types of DCs, the window DC and the client DC. Then a second section
will follow with Device DCs. These are DCs that represent any device on your machine,
that supports some form of the Windows Graphics Device Interface (GDI). Two other types
of DCs are ignored in this article, memory DCs, and metafile DCs.
A DC is an operating system resource. Because of this, it is very important that DCs
are released when they are no longer required. Do not cache DCs. Each type of DC has its own method
to release that DC. It is important to use the proper function because these functions
act like desctructors, and important shutdown operations occur in these functions for
the particular type of DC. If an application performs a lengthy operation each time
to initialize a DC, then a class or private DC can be created. That will be explained
at the end of this section.
Common DCs
Common DCs are created from a pool of DCs that is limited by the amount of memory on
the current system. Common DCs are futher classified as Client and Window DCs.
Client and Window DCs are avery similar. The only difference between the two is that
a client DC is restricted to the client area of the window, while the window DC gives the
application access to both the client and the non-client areas of the window. There are
two functions that allow access to a client DC (BeginPaint and GetDC,),
one function to create a window DC (GetWindowDC) and one master function that
will allow any sort of DC to be created for a window (GetDCEx).
Listed below is each of the functions that can be used to created a DC for a window.
Any special characteristics about each function are described, as well as special uses.
Paint DC
A paint DC is represented in MFC and WTL by the CPaintDC class. This class
is a wrapper around the BeginPaint function, which prepares the specified
window for painting and initialize the PAINTSTRUCT structure with information about
the painting.
HDC BeginPaint(
HWND hwnd, LPPAINTSTRUCT lpPaint );
BeginPaint prepares the window for painting by creating
a client DC with a clipping region that is equivalent to the update region for the window.
Then the update region is validated to prevent the generation of other WM_PAINT messages.
The clipping region is set into the client DC on repaint because when the entire display is
repainted, this has the effect of reducing a great portion of the flicker.
However, this form of clipping will not remove all forms of flicker, especially
for complicated displays that take a long period of time to repaint.
Here is the layout of the PAINTSTRUCT structure:
struct PAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
};
Here is a description of each of the fields:
- hdc: The handle to the DC that is created in
BeginPaint. This
is the same handle that is returned from BeginPaint.
- fErase: Indicates whether the application is responsible for erasing the
background of the paint display. If the call to WM_ERASEBKGND succeeds, then this parameter
will usually be
FALSE.
- rcPaint: Specifies a RECT structure that specifies the upper left and lower
right corners of the rectangle in which the painting is requested. This is basically
the bounding rectangle of the update region for the window.
- fRestore: Reserved; used internally by the system.
- fIncUpdate: Reserved; used internally by the system.
- rgbReserved: Reserved; used internally by the system.
BeginPaint performs a few other actions while preparing the window to be
painted. It will hide the caret if the current window contains a caret in order to
prevent the window from painting over it. It will send a message to WM_NCPAINT in
order to update the borders if necessary. After the DC has been initialized with the
current clipping region, then a message to WM_ERASEBKGND will be sent. WM_ERASEBKGND will
only erase the current update region.
The CPaintDC class properly disposes of the DC with a call to EndPaint.
The EndPaint function marks the end of painting in the specified window. This function is
required for each call to the BeginPaint function, but only after painting is complete.
One extra thing that EndPaint does is restores the caret to the screen that
was previously hidden in the call to BeginPaint.
BOOL EndPaint(
HWND hWnd, CONST PAINTSTRUCT *lpPaint );
The only place that BeginPaint should be called is inside of the
WM_PAINT handler.
This is because of the update region. If a DC is required outside of the WM_PAINT handler,
GetDC should be used instead.
Client DC
A DC that paints the client area, but is not a Paint DC is referred to a ClientDC in MFC and
WTL. This DC is represented by the CClientDC class. This class encapsulates a
call to GetDC. This type of DC should be used any place that requires painting to
the client region of a window except in the OnPaint handler. GetDC can also get
a DC to paint on the entire screen if NULL is passed in as the window handle.
HDC GetDC(
HWND hWnd );
After painting with a common DC, the ReleaseDC function must be called to release the DC.
Class and private DCs do not have to be released. ReleaseDC must be called from the same
thread that called GetDC. The ReleaseDC function releases a device context (DC), freeing
it for use by other applications. The effect of the ReleaseDC function depends on the type
of DC. It frees only common and window DCs. It has no effect on class or private DCs.
int ReleaseDC(
HWND hWnd, HDC hDC );
Window DC
A Window DC allows for the entire window, including title bar, menus, and scroll bars, to be
painted. A window device context permits painting anywhere in a window, because the origin
of the device context is the upper-left corner of the window instead of the client area.
MFC and WTL represent this DC with the CWindowDC class, which intenally
calls the GetWindowDC function to create the DC. GetWindowDC can also get
a DC to paint on the entire screen if NULL is passed in as the window handle.
HDC GetWindowDC(
HWND hWnd );
As with GetDC, GetWindowDC should be released with a call to
ReleaseDC, which CWindowDC takes care of automatically.
GetDCEx
GetDCEx is the function that will allow the developer to create a DC that is
associated with any part of a window. The three functions that were previously listed are
all implemented with a call to GetDCEx. GetDCEx can also get
a DC to paint on the entire screen if NULL is passed in as the window handle.
HDC GetDCEx(
HWND hWnd, HRGN hrgnClip, DWORD flags );
The flags parameter will determine which type of DC will be created, and
how the DC will be initialized. Here is a short list of the different flags that can
be used to create a DC with GetDCEx:
- DCX_WINDOW: Returns a DC that corresponds to the window rectangle rather than
the client rectangle.
- DCX_CACHE: Returns a DC from the cache, rather than the
OWNDC or
CLASSDC window.
Essentially overrides CS_OWNDC and CS_CLASSDC.
- DCX_PARENTCLIP: Uses the visible region of the parent window.
The parent's
WS_CLIPCHILDREN and CS_PARENTDC style bits are ignored. The origin is set
to the upper-left corner of the window identified by hWnd.
- DCX_CLIPSIBLINGS: Excludes the visible regions of all sibling windows above
the window identified by
hWnd.
- DCX_CLIPCHILDREN: Excludes the visible regions of all child windows below
the window identified by
hWnd.
- DCX_NORESETATTRS: Does not reset the attributes of this DC to the default
attributes when this DC is released.
- DCX_LOCKWINDOWUPDATE: Allows drawing even if there is a
LockWindowUpdate call
in effect that would otherwise exclude this window. Used for drawing during tracking.
- DCX_EXCLUDERGN: The clipping region identified by
hrgnClip is excluded
from the visible region of the returned DC.
- DCX_EXCLUDEUPDATE: Behaves the same as
DCX_EXCLUDERGN, except the update region of the
window is used as input rather than the hrgnClip parameter.
- DCX_INTERSECTRGN: The clipping region identified by
hrgnClip is intersected
with the visible region of the returned DC.
- DCX_INTERSECTUPDATE: Behaves the same as
DCX_INTERSECTRGN, except the update region of the
window is used as input rather than the hrgnClip parameter.
- DCX_VALIDATE: When specified with
DCX_INTERSECTUPDATE, causes the DC to
be completely validated. Using this function with both DCX_INTERSECTUPDATE and
DCX_VALIDATE is identical to using the BeginPaint function.
As stated earlier, BeginPaint, GetDC, and GetWindowDC are all
implemented in terms of GetDCEx. Listed below is a table of these flags, and
the proper call that should be made to GetDCEx in order to create an equivalent
DC. In all of the examples, hWnd is the handle to the target window.
- BeginPaint:
::GetDCEx(hWnd, NULL, DCX_INTERSECTUPDATE |
DCX_VALIDATE | );
- GetDC:
::GetDCEx(hWnd, NULL, NULL);
- GetWindowDC:
::GetDCEx(hWnd, NULL, DCX_WIDNOW);
Class / Private DCs
It is important not to cache the common DCs in an application. How then can an application
create a DC that does not have to be reinitialized each time it is called? This can be done
with either a class or private DC. These two DCs can be retrieved in the same manner as the
other common DCs and they do not need to be released. However it is good practice to release these
DCs as the release functions have no effect, and memory leaks will be prevented if the DCs
are ever converted back to common DCs.
Class and private DCs are persistant, therefore the state of the DC will remain the same as it
was in the previous call to that DC. This will allow a DC to be initialized once. This could
save valuable time for applications that have lengthy DC initializations.
Given below is a short description of both the class and private DC.
Class DC
A class DC is created for use for a single window class. In order to create a class DC, the
window class must have the CS_CLASSDC style set. Since it is possible to create
two windows of the same class on different threads, it is important to synchronize access to
a class DC. Simultaneous use of a class DC on two different threads has undefined behaviour.
Private DC
A private DC is created for one particular window. To create a private DC, the
CS_OWNDC style
must be set in the class of the window.
Device DCs
A device DC allows access to any device on the system that supports some portion of the
WIN32 GDI. All types of devices can be accessed like the "DISPLAY" driver, secondary monitors,
printers, plotters and any other cutting-edge device that supports GDI.
There are two steps that are required to get a handle to a device DC. 1) Get the name of the
device. 2) Call CreateDC to create the HDC for the device. When the application is
finished with the DC, DeleteDC should be used to destroy the HDC.
Obtain Device Name
Most simply, if the application would like a DC to the primary display device, then the
string "DISPLAY" can be used. However if a DC is to be created for secondary monitor then
EnumDisplayMonitors must be used in order to get the name of the target
display driver, since multi-monitor support does not exist on WIN95, this function is only
valid on WIN98 and above.
In order to get the name of a printer device, the application should use EnumPrinters.
This function will allow the application to search through all of the printers that are configured
on the system. Important information can be queried from the printer as well in order to create
a DC to it in CreateDC.
CreateDC
The CreateDC function creates a DC for a device using the specified name.
HDC CreateDC(
LPCTSTR lpszDriver, LPCTSTR lpszDevice, LPCTSTR lpszOutput, CONST DEVMODE *lpInitData );
The lpInitData, or DEVMODE structure is very important mainly to
printer device contexts. This structure is also very complicated, however, since this
article does not cover printing, that is all that will be said about DEVMODE.
CreateIC
The CreateIC function creates an information context for the specified device.
The information context provides a fast way to get information about the device
without creating a device context (DC). However, GDI drawing functions cannot accept
a handle to an information context. This function should be followed by a call
to DeleteDC when the information context is no longer needed.
HDC CreateIC(
LPCTSTR lpszDriver, LPCTSTR lpszDevice, LPCTSTR lpszOutput, CONST DEVMODE *lpdvmInit );
Demonstration
The program that has been created to demonstrate the topics covered in this article
will visualize the update region for the user. Other information will also be displayed
in either the non-client area of the main window, or directly on one of the display
drivers. One other feature of the demo application is to turn off the handling
of the WM_ERASEBKGND message in order to see what effect is created.
Because of the multi-monitor testing, this application requires WIN98, WIN200 or above.
This application is written in WTL, therefore the WTL header files will be required
to build the source project. The WTL wrapper classes (CPaintDC, CWindowDC, etc...)
will also be used in order to illustrate the use of each of the different DCs. This application
will also require the version 5.0 header and lib files to compile because it uses the
EnumDisplayDevices function which is only supported in WIN98 and WIN2000
or above.
Use if this application is very simple. Run the application, can force the main
window to redraw itself. There are many ways to do this including:
- Maximize / Restore the main window
- Resize the window Horizontally / Vertically / Diagonally
- Drag something across the window to invalidate a new region
- Place a window in the center of the window then activate the demo app
All of these methods to invalidate the window should be viewed. Then each of these
steps should be taken to view how things change with certain settings in the system.
- Modify the
CS_HREDRAW / CS_VREDRAW styles in the view menu
- Modify the handling of the
WM_ERASEBKGND message
- Change the Show Window Contents While Dragging option on the view menu.
Each time that a WM_PAINT region is received, the current update region for that window
will be framed with a new color. This will allow the user to visualize the update region
as each new WM_PAINT message occurs. There are a few style flags that can be set for the
window that will demonstrate the effects that they have on WM_PAINT messages. Here are a few
screen images of the visualized update regions:
Data Log
The data log contains the important and usable values of the PAINTSTRUCT structure. Here is
an image of what the data bar looks like when it is active.

Here is a description of what each of the parameters will demonstrate:
- HDC: The value of the HDC that is allocated in
WM_PAINT. Shows that a new HDC is created for
each message,
- fErase: This shows whether the
WM_PAINT handler is responsible for erasing the background or
not. If WM_ERASEBKGND is set on the menu, then that message will be handled properly and fErase will
appear as FALSE on the data bar. If WM_ERASEBKGND is not handled, then fErase will be TRUE. The
view window does not handle WM_ERASEBKGND ever, so you will see a weird effect, it will look like the
window is not updating properly, except for the update region will be framed properly.
- rcPaint: The four components of this rectangle will be displayed demonstrating the
bounding rectangle of the update region on the window.
Selecting No Log on the menu will hide the data bar.
If Non-client is selected on the menu, then the data bar will be painted in the border area of the
window. This is done to illustrate how painting can be accomplished in the WM_NCPAINT message handler
and using the CWindowDC.
This program will enumerate up to two display devices on your machine, and add them to the view menu.
If you select one of these items, then the databar will be displayed in the upper-left hand corner of
the monitor that the display represents. The CreateDC function is used to create the
DC with the device name for that monitor. This feature was added to demonstrate getting a DC to any
display monitor.
CS_HREDRAW / CS_VREDRAW styles
As described earlier in the article, these styles affect the update region. If one of these styles is
set, then when the window is resized the corresponding style may force the entire view to be redrawn rather
than simply the new update region. Change this style, and watch the effect that is has on the update region.
WM_ERASEBKGND
If this option is set, then the application will process the WM_ERASEBKGND message, effectively clearing
the background during each WM_PAINT message. However, if this item is unchecked, then the application
will ignore the WM_ERASEBKGND message. This will have the effect of leaving whatever invalidated the
region on top of the window, rather than refreshing the display.
Also notice the fErase field value in the data bar. When WM_ERASEBKGND is handled,
then fErase is FALSE indicating that the WM_PAINT routine is not responsible for
initializing the drawing surface. If WM_ERASEBKGND is not processed, then fErase in the
PAINTSTRUCT
will be true, informing the application that it should erase the background of the display before it
paints, which this application does not do in order to demonstrate the process.
Show Window Contents While Dragging
This will toggle the setting in the display properties that forces the window to repaint while a window
is begin dragged. Whether it is being resized, or another window is being dragged across the top
of the window. The net effect of this option being set is that more paint messages will be
generated, and the update regions will be smaller, but more frequent. This application should be
viewed with this option on and off.
Here is an example of the difference when resizing the window.

 
Short comings
There are two minor short coming in this demonstration application when the databar
is painted on one of the display adapters. The application does not try to erase or
restore the previous data when the data bar is removed. This could have been fixed
by caching the region where the data bar is painted, and restoring that region when the
data bar is removed. However that would require memory DCs, and this article ignored
their explanation.
When the data bar is displayed on one of the display adapters, but it is also painted
onto a window from a different application, it may appear that the data bar has missed a
region that is should have painted. This is actually caused by the other application
receiving its WM_PAINT message after the data bar has been painted on top of it. This could
be fixed by finding the window that is painting over the data bar and validating that
region, or by windows hooks. However this option has been left unexplored.
Conclusion
The WIN32 paint architecture has been designed to paint windows and controls
incrementally. This increases the performance and the appearance of applications.
The incremental paint process is enabled by the update region that WIN32 maintains
for each and every window. WM_PAINT messages are not pushed into the message queue
by the system. Instead, WM_PAINT messages are only generated when all of the other
messages from the queue are processed, and the window's update region is not empty.
WM_PAINT messages should be handled with the BeginPaint API, or
with a CPaintDC if MFC or WTL is used.
There are other types of common DCs that can be used to paint onto the window or
one of the displays. Each type of DC has a special purpose. The client DC is
is to paint on the client area of a window. Use the GetDC API, or
the CClientDC class. A window DC allows an application to paint to the entire window.
Use the GetWindowDC API, or CWindowDC class.
The, GetDCEx is the API function that is at the root of all of the
common DC functions. Each of the other common DCs can be created with a call to
GetDCEx and the proper set of flags. If an application requires to
paint directly on the desktop, then CreateDC can be used to create
the DC for this purpose.
There are many different ways to paint a window in WIN32. This tutorial has
presented many facts about the design, structure and process found in WIN32. Hopefully
the reader can take these facts and apply them in ways that will increase the performance
and appearance of his application.