Introduction
Many developers are comfortable with obtaining a handle to a device context (DC) and painting to it. However, there is probably a lot 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 article 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 other hand, 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 destructors, 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 further classified as Client and Window DCs. Client and Window DCs are very 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 initializes 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 restore 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 internally 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 persistent, 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.
- Get the name of the device.
- 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 it 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.
Shortcomings
There are two minor short comings 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 it 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 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.