|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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 that 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 beginner's, 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 ContextAt 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 utilized the DC that you want for a particular purpose. There are many flavors of DCs, here is a short description of each:
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 ContextWhile working with the WIN32 Paint API, you will always obtain a handle to
a device context ( The frameworks represent their DCs with a class. The base class for the DC is BeginPaint
It is just as important to use In order to get a handle to a DC with // 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 Here is an example of // Call EndPaint with the same hWnd and PAINTSTRUCT that was used in // the call to BeginPaint. ::EndPaint(hWnd, &ps); The call to
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
Here is an example of how to create and destroy a DC with a call to // 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
CClientDC::CClientDC(CWnd* pWnd)
{
...
if (!Attach(::GetDC(m_hWnd = pWnd->GetSafeHwnd())))
AfxThrowResourceException();
}
CClientDC::~CClientDC()
{
...
::ReleaseDC(m_hWnd, Detach());
}
Using a Device ContextUsing 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 //C: Create the DC on the stack. This will allow the class to be destroyed when // the stack frame dissappears. //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 //C: Create the DC on the stack. This will allow the class to be destroyed when // the stack frame dissappears. 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 StartedThe 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.
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
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 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 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 notive is that the DC was
received from
a call to 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
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;
}
ConclusionOnce 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 | ||||||||||||||||||||