Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Guide to WIN32 Paint for Beginners

0.00/5 (No votes)
20 Mar 2002 2  
Beginner's guide to understanding how to paint to a window in the WIN32 SDK environment
Sample Image

Introduction

In reading the forums and answering the questions, I have found that many beginners would like to learn the fundamental concepts of painting in WIN32 programs. Developing applications for windows can be challenging and frustrating. it can also be rewarding if you know some of the basic techniques that are required to take advantage of the WIN32 operating system. This tutorial is for beginners, and will cover the basic techniques and knowledge required to get started with painting in WIN32.

The code and the concepts are all at the SDK level, as most of the other techniques that are used to paint to windows are based on the SDK. The WIN32 SDK is used inside of the MFC and ATL/WTL objects that represent those frameworks. I believe the more you know about the base technology, the SDK, the more you can take advantage of the frameworks that are written to encapsulate the technology. In order to help developers that are developing in MFC and WTL, the corresponding classes from those frameworks will be explained in the appropriate sections.

This tutorial is the first in a series of five. This one is for beginners, the next three will cover more advanced topics at the intermediate level, and the final tutorial will cover WIN32 paint internals at the advanced level.

Device Context

At the simplest level, the device context (DC) is a surface that can be painted on. However, the DC is also the operating system resource that coordinates the brushes, pens and fonts that are used to render the images on the display. It is also the layer of abstraction that the Windows Graphics Device Interface (GDI) uses to abstract the details of a display device from a developer. This means that it is possible to paint to the screen, a printer, or even a bitmap in memory with the same code, initialized with a different DC that is created for a particular purpose. This makes coding very simple compared to the development that is required to program directly to a particular video card, or printer driver.

The key to creating solid and efficient graphics in Windows, is to know how to create and utilize the DC that you want for a particular purpose. There are many flavors of DCs, here is a short description of each:

  • Client DC

    A client DC is associated with a particular window, and will give the developer access to the client area of the target window. This is the type of DC that is used most often by application programmers, and is the easiest to handle. This is the type of DC that should be used when the WM_PAINT message is handled. This is also the only DC that will be explained with any amount of detail.

  • Window DC

    A window DC is associated with a particular window, and allows the developer to draw on any part of the target window, including the borders and the caption. This is the type of DC that is sent with WM_NCPAINT message.

  • Memory DC

    A Memory DC is a DC that exists only in memory, and is not associated with any window. This is the only type of DC that can hold a user defined bitmap (HBITMAP). Memory DC's are very useful for caching images, and for use with backbuffers on complicated displays.

  • General Device DC

    For lack of a better name, this type of DC covers all of the other devices that it is possible to get a DC from. For instance, a printer or a plotter, the entire display monitor, or even custom device that a cutting-edge technology company may invent that does not even exist yet. The fact is, a DC can be created for any device that supports the required API functions defined by Microsoft.

This guide will only demonstrate the Client DC in order to get the user started with basic Windows graphics development. The tutorials later in this series will cover the other types of DC.

Obtaining a Client Device Context

While working with the WIN32 Paint API, you will always obtain a handle to a device context (HDC). Any of the types of DCs that were described earlier can be stored in a HDC. This guide will only describe how to obtain a client DC, as the other DCs are used for more advanced purposes.

The frameworks represent their DCs with a class. The base class for the DC is CDC. A CDC encapsulates the HDC itself. All of the functions that can be called for a HDC are encapsulated as member functions. There are also a few classes derived from the CDC that will allow special DCs to be created and maintained. The CPaintDC and the CClientDC will be explained later in this section.

BeginPaint

BeginPaint is the most common way to create a DC. However, this function should only be called in your WM_PAINT message handler of your window. This is because BeginPaint validates the invalid region of your window.  WM_PAINT messages are generated whenever an invalid region is created on your window. If BeginPaint is called outside of the WM_PAINT handler, any region that was previously invalid will be validated, and no WM_PAINT message will be generated for your window. This could cause major side-effects in your control, especially if someone else would like to subclass your control in the future.

It is just as important to use BeginPaint inside of the WM_PAINT handler as it is to not use it outside of WM_PAINT. This is because inside of the BeginPaint function call, Windows may generate a WM_ERASEBKGND message and a WM_NCPAINT message if it is necessary. If you do not call BeginPaint inside of your WM_PAINT handler, your windows borders may not be updated properly.

In order to get a handle to a DC with BeginPaint, you will need a handle to the target window, and a PAINTSTRUCT structure. PAINTSTRUCT holds information about the current painting session when you call BeginPaint, that is unimportant at this level. BeginPaint also returns a handle to the DC that it creates, and this is the value that we are interested in. Here is an example of how to call BeginPaint.

// assuming that hWnd is the handle to the window for which we want the DC.

PAINTSTRUCT ps;
HDC            hdc;
hdc = ::BeginPaint(hWnd, &ps);

The function that needs to be used in order to free a HDC created with BeginPaint is EndPaint. It is important to call EndPaint rather than one of the other DC destroyers, because BeginPaint calls HideCursor in order to prevent the cursor from being painted on during your paint operations, and EndPaint calls ShowCursor to make it visible again. If EndPaint is not called, after a BeginPaint then you may experience some weird anomalies with the cursor.

Here is an example of EndPaint:

// Call EndPaint with the same hWnd and PAINTSTRUCT that was used in 
// the call to BeginPaint.
::EndPaint(hWnd, &ps);

The call to BeginPaint and EndPaint are encapsulated in the class CPaintDC in MFC and WTL. By simply creating an instance of this DC on the stack, the DC will automatically be created and destroyed for the developer. Here is the code for the constructor and destructor in the MFC version of this class:

CPaintDC::CPaintDC(CWnd* pWnd)
{
    ...

    if (!Attach(::BeginPaint(m_hWnd = pWnd->m_hWnd, &m_ps)))
        AfxThrowResourceException();
}

CPaintDC::~CPaintDC()
{
    ...

    ::EndPaint(m_hWnd, &m_ps);
    Detach();
}

GetDC / GetDCEx

GetDC is the next most common way to create a DC. GetDC will obtain a device context to the target windows client area. GetDCEx will do the same thing, however it will allow you to specify a default clipping region. GetDCEx will be ignored until the next guide.

GetDC has many uses outside of the WM_PAINT handler. Use GetDC when a graphical effect needs to be created, that may not be part of the permanent data of the target window. For instance, use GetDC to create the DC for the rubber-banding effect on drawing tools, and when selecting multiple objects in a window.

Here is an example of how to create and destroy a DC with a call to GetDC.

// Assuming that hWnd is the handle to the window for which we want the DC.
HDC hdc;
hdc = GetDC(hWnd);

// Perform painting operations here.
...

// Release the DC when you are finished.  If this function succeeds it will return 1,
// otherwise it will return 0 if it fails.
::ReleaseDC(hWnd, hdc);

The call to GetDC and ReleaseDC are encapsulated in the class CClientDC in MFC and WTL. By simply creating an instance of this DC on the stack, the DC will automatically be created and destroyed for the developer. Here is the code for the constructor and destructor in the MFC version of this class:

CClientDC::CClientDC(CWnd* pWnd)
{
    ...

    if (!Attach(::GetDC(m_hWnd = pWnd->GetSafeHwnd())))
        AfxThrowResourceException();
}

CClientDC::~CClientDC()
{
    ...
    ::ReleaseDC(m_hWnd, Detach());
}

Using a Device Context

Using a DC is very simple, and it can be quite complicated, it all depends on what painting effect is to be accomplished. This guide will simply stay with the default pen and brush that are selected in the DC when it is first created. Here is an example of how to call a number of different GDI functions with the DC that we have created.

// Draw a rectangle at (100,100) with dimensions (100,200);
Rectangle(hdc, 100, 100, 200, 300);

// Draw an ellipse inside the previous rectangle.
Ellipse(hdc, 100, 100, 200, 300);

// Draw simple text string on the window.
TCHAR szMessage[] = "Paint Beginner";
UINT  nLen = _tcslen(szMessage);
TextOut(hdc, 100, 325, szMessage, nLen); 

Here is a short example demonstrating how to use the CPaintDC:

//C: Create the DC on the stack.  This will allow the class to be destroyed when
//   the stack frame disappears.
//C: WARNING: Only use this DC in your OnPaint handler for the WM_PAINT message.
CPaintDC dc;
//C: Use the DC as you would like.
dc.Rectangle(10, 10, 150, 200);
...

//C: No need to do any thing else to manage the DC, it will destroy itself.

Here is a short example demonstrating how to use the CClientDC:

//C: Create the DC on the stack.  This will allow the class to be destroyed when
//   the stack frame disappears.
CClientDC dc;
//C: Use the DC as you would like.
dc.Rectangle(10, 10, 150, 200);
...

//C: No need to do any thing else to manage the DC, it will destroy itself.

Getting Started

The short demo program that is provided will display an array of shapes that has been created by the user. The user can create a shape by clicking and dragging the mouse, the same way that objects are selected in windows explorer. The two methods for creating a client DC are demonstrated.

  • BeginPaint: This function is used to paint the shapes that the user has created.
  • GetDC: This function is used to create the rubberbanding effect that allows the user to drag and create the shapes.

The demo program is very simple in structure. It forsakes coding style and elegance for simplicity and clarity. All of the state that is required for this program is stored in global variables. There is a maximum of 5 shapes that can be created because they are stored in a statically allocated array. If more than five shapes are created, then the oldest existing shape will be replaced with the new shape.

Here is the OnPaint handler that was created to handle the WM_PAINT message:

LRESULT OnPaint       (HWND hWnd)
{
    PAINTSTRUCT ps;
    HDC            hdc;
    hdc = ::BeginPaint(hWnd, &ps);

    UINT index;
    for (index = 0; index < SHAPE_COUNT; index++)
    {
        if (ID_SHAPE_RECTANGLE == Shapes[index].shapeID)
        {
            ::Rectangle    (    
                        hdc, 
                        Shapes[index].rect.left, 
                        Shapes[index].rect.top, 
                        Shapes[index].rect.right,
                        Shapes[index].rect.bottom
                        );
        }
        else
        {
            ::Ellipse    (    
                        hdc, 
                        Shapes[index].rect.left, 
                        Shapes[index].rect.top, 
                        Shapes[index].rect.right,
                        Shapes[index].rect.bottom
                        );
        }
    }

    ::EndPaint(hWnd, &ps);

    return 0;    
}

Notice the very simple paint structure encapsulated between the calls to BeginPaint and EndPaint. More calls could be made between the BeginPaint bracket. The same principle would still apply, all painting for the main window should be done here.

The rubber banding effect is a little bit more complicated. This effect is created by modifying two of the state variables in the DC. The first state that is changed is that the current drawing mode is changed from a plain copy to a destination NOT pen, or R2_NOT. This pen will allow the first drawing instance of the pen to change all of the bits from display, making a line visible. By simply Drawing the exact same line a second time, the line will disappear.

The second change in DC state, is to select an empty brush color into the DC, so that when the shape is dragged, it does not paint the center of the shape. These two tricks are not the important point that should be noticed from this code. The important point to notice is that the DC was received from a call to GetDC and ended with a call to ReleaseDC. Same as the drawing code, any operations are contained in this DC creation bracket, and the window painting proceeds in between.

Here is the function that draws the rubberbanding effect:

void DrawRubberBand(HWND hWnd)
{
    HDC hdc;
            //C: Get a client DC.
    hdc = ::GetDC(hWnd);
            //C: Set the current drawing mode to XOR, this will allow us
            //   to add the rubber band, and later remove it by sending the
            //   exact same drawing command.
    ::SetROP2(hdc, R2_NOT);
            //C: Select a NULL Brush into the DC so that no fill is performed.
    ::SelectObject(hdc, ::GetStockObject(NULL_BRUSH));
            //C: Get the current shape mode.
    HMENU hMenu            = ::GetMenu(hWnd);
    HMENU hShapeMenu    = ::GetSubMenu(hMenu, 1);

    if (::GetMenuState(hShapeMenu, ID_SHAPE_RECTANGLE, MF_BYCOMMAND) & MF_CHECKED)
    {
        ::Rectangle(    
                    hdc, 
                    ptStart.x, 
                    ptStart.y, 
                    ptCurrent.x,
                    ptCurrent.y
                    );
    }
    else
    {
        ::Ellipse(    
                    hdc, 
                    ptStart.x, 
                    ptStart.y, 
                    ptCurrent.x,
                    ptCurrent.y
                    );
    }
            //C: Release the DC.
    ::ReleaseDC(hWnd, hdc);
}

One final point that should be explained about the demo program is the call that makes the rubber band work. This code is inside of the WM_MOUSEMOVE message handler. The handler will first check if rubberbanding is currently activated. If so it will first draw the current rectangle, effectively erasing it from the screen, it will update the current coordinates, then it will draw the new coordinates effectively updating the position of the rectangle. Here is the code:

LRESULT OnMouseMove   (HWND hWnd, UINT nCtrl, UINT x, UINT y)
{
            //C: If the current mode is not rubber band, then exit.
    if (!isRubberBand)
    {
        return 0;
    }
            //C: Undo the last rectangle shape that was drawn.
    DrawRubberBand(hWnd);
            //C: Update the current position.
    ptCurrent.x = x;
    ptCurrent.y = y;
            //C: Draw the next rubberband position.
    DrawRubberBand(hWnd);
            //C: Exit with success.
    return 0;        
}

Conclusion

Once again, this guide is to get people started down the path of painting in WIN32. This article was written completely for the WIN32 SDK. I fully believe that MFC and ATL are cleaner ways to develop code for Windows, but I also believe that if a developer can get a firm grasp of the basic concepts at the SDK level, they will be able to flourish as a framework developer, because you will be able to determine what is happening behind the scenes, especially when problems arise.

The next article I post will be at an intermediate level with more information. The next article will describe the other ways to create DCs. There will also be information on the different fields of the PAINTSTRUCT structure. Finally, the update region will be explained with a great amount of detail.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here