CHAPTER 13
PAINTING THE WINDOW CLIENT AREA.
OVERVIEW:
The client rectangle of a program's window is the rectangle inside the window frame, which is the region of a window that a program, i.e. a 'client', actually uses. The window frame itself, including the title bar, is the non-client area, which is taken care of by the operating system. The non-client area is not entirely excluded from modification by a program, and provision is made for intercepting, and hence modifying, painting messages to the non-client area. Here, however, we will deal with painting of the client area.
A menu bar, if there is one, is also part of the non-client area of a window, and is drawn by the system. A toolbar, however, if it is a child window, is
drawn in the client area of the window, but also has its own client area, and its own window handle. As a matter of policy, I avoid using child windows, and prefer to simply divide up the main window client area into different regions, when necessary. We shall see more about this later on, when dealing with the creation of toolbars.
As far as painting the client area of a window is concerned, there are two distinct methods available. One method uses the WM_PAINT message, whereas the other uses a device context to paint the client area immediately, without sending, or waiting for, a WM_PAINT message.
USING THE WM_PAINT MESSAGE:
The principle involved in using the WM_PAINT message is that a program prepares its data for painting in the client area of the window, but does not immediately paint the result into the window. Instead, it sends a WM_PAINT message and then waits for the message to be collected from the message queue, after which it paints the window as a
response to the WM_PAINT message. The operating system ordinarily puts the WM_PAINT message last in the message queue, so that a program can carry our a series of operations, but have the results painted into the window only when the operations are all completed.
VALIDATION, INVALIDATION, AND THE UPDATE REGION:
In the context of the WM_PAINT message, the API documentation refers to the 'update region' of a window, and to 'validation' and 'invalidation' of parts of the client area. A reader unacquainted with these concepts may find, as I did, that their role and relevance are often not clearly explained to a reader unfamiliar with them, and thus not easy to understand.
In order to paint onto the screen, the API functions involved must do so in association with a display 'device context'. This refers to a data structure within the operating system that the operating system uses to transfer data from a program to the screen. This structure is not available to the program, other than as a handle by which the program can refer to it. Its parameters will include the colour specification of the display, the current font for displaying text, the area on the screen to be painted, which may not include the whole of the client area of a window, and so on.
Thus, instead of simply the entire client area of a window, we will have what is called its 'update
region', which may involve only one or more rectangular portions of the client area. Any rectangular portion of the update region is said to be 'invalidated' to mean that it is available for painting. The update region is thus made up of all the rectangles in the client area that have been 'invalidated'. Now if, in response to a WM_PAINT message, you try to use an API function to paint the whole of the client area, you will find that only the rectangles that have been invalidated will actually be painted. These can include a number of distinct and separate rectangles in different parts of the client area. In this way, the device context will be associated with the update region only, and will allow painting only within that region.
It is the program itself that determines the nature of the update region, and specifies the rectangles that are invalidated, using a number of alternative API functions. The program code can also always invalidate the whole of the client area and, by this means, effectively ignore the possibility of creating a more restricted update region.
SENDING THE WM_PAINT MESSAGE:
When a program code has prepared its data to be painted to the screen, it sends a WM_PAINT message by using API functions that also invalidate the relevant portion of the client area. Alternative API functions, as shown below, allow the code to exercise less or more control over when a WM_PAINT message will be sent (in the following W.Handle is short for WINDOW.Handle, the handle number, or system identifier, of the window):
api InvalidateRect,[W.Handle],RECT,(erase flag)
or
api InvalidateRect,[W.Handle],0,(erase flag)
This function merely invalidates a rectangular portion of the screen, which becomes part of the update region. When all messages in the queue have been processed, Windows always sends a WM_PAINT message if the update region is not zero. If the RECT parameter (the address of the invalidated rectangle data structure) is set to zero, as in the second example, the whole client area is invalidated.
The InvalidateRect function can be called more than once, to add a different rectangle to the update region each time. The update region can thus collect a number of separate rectangles to be painted before any painting actually occurs.
api RedrawWindow,[W.Handle],RECT,0,(flgs)
or
api RedrawWindow,[W.Handle],0,[U.R.Handle],(flgs)
or
api RedrawWindow,[W.Handle],0,0,(flgs)
(flgs is short for 'flags'). This function achieves the same result as InvalidateRect, but the flags parameter allows greater control over when a WM_PAINT message is posted to the queue (here U.R.Handle is short for UPDATE.REGION.Handle). If both parameters are zero, as in the third example, the whole of the client area is invalidated.
api UpdateWindow,[W.Handle]
This function sends a WM_PAINT message directly to the callback function, WndProc, and altogether bypasses the message queue. However, the WM_PAINT message is actually sent only if the update region is not empty. Otherwise nothing happens.
If, for some reason, the program code needs to prevent an invalidated portion of the screen being painted, the following API fuction can be called:
api ValidateRect,[W.Handle],RECT
or
api ValidateRect,[W.Handle],0
In the first example, only the area specified by the RECT parameter (a small data structure) is validated. In the second example, where the same parameter is set to zero, the whole client area is validated, and the update region becomes empty, and no painting will occur.
THE WM_PAINT MESSAGE RESPONSE:
When a WM_PAINT message is received, the response, which is written into the beginning of the WndProc part of your code, takes the following form:
start.if
if.d '=',[MESSAGE], …WM_PAINT…
then.jmp …W_.PAINT…
end.if
then.jmp W_.PAINT jumps to the W_.PAINT line in the WndProc code, which will contain the following code:
W_.PAINT:
api BeginPaint,[WINDOW.Handle],PAINTSTRUCT
call PaintingProcedure
api EndPaint,[WINDOW.Handle],PAINTSTRUCT
xor eax,eax
jmp EXIT_CALLBACK
The BeginPaint function creates a device context associated with the window update region, which is used with API functions that paint the screen, and, at the same time, validates and destroys the update region, which is now associated with the device context. The program then uses its own code (PaintingProcedure) to actually paint the window.
API functions such as RedrawWindow, or UpdateWindow, mentioned in the previous section, despite their names, thus don't actually do any painting themselves, but are involved only in setting up the WM_PAINT message. It is the program code itself that must call the API functions that actually carry out the painting of the client area.
After painting the window, the program code must always call the EndPaint API function, as a
counterpart to the BeginPaint function.
PAINTSTRUCT is a data structure provided by your program, but designed according to Windows System specifications, as follows, which receives painting information from BeginPaint that the program can use for the actual painting:
Only the first three parameters are useful.
The first parameter gives the display device context created by the BeginPaint function, which is needed for use with API painting functions. The second indicates whether or not the program wants the background to be erased, as specified by the InvalidateRect function. The third indicates the rectangle within which painting will be able to occur. If the update region is made up of several distinct rectangles, it indicates the smallest overall rectangle able to include all of them. This doesn't mean, of course, that all of this rectangle can be painted. It only enables the program to avoid dealing with a larger region of the client area than necessary. If you send a WM_PAINT message to the default procedure, DefWindowProc, without doing any painting, or calling the BeginPaint/EndPaint functions, DefWindowProc will validate, and thus discard, the update region.
USING A DEVICE CONTEXT INSTEAD OF WM_PAINT:
As mentioned previously, there are two methods available for painting within the client area of a window.
In the context of the WM_PAINT message, we saw that the BeginPaint function provides a device context which painting functions require to paint to the screen. It is possible, however, to obtain a device context directly, at any time, and use this to paint to the screen immediately, without using any WM_PAINT message at all. The following API function is used to get a display device context directly:
api GetDC,[WINDOW.Handle]
mov [DISPLAY.DC],eax
painting code here
api ReleaseDC,[WINDOW.Handle],[DISPLAY.DC]
Where a direct device context is used to enable painting operations, concepts like validation, invalidation, and the update region are no longer relevant, and the GetDC (get a device context) API function will enable painting anywhere in the client area of the window, and will ignore the existence, or otherwise, of an update region In this case it is to a matter for the program code to decide where to paint and where not to paint.
This method is useful for painting in the client area immediately, without having to send a WM_PAINT
message and then wait for it to be received.
It is very important to call the ReleaseDC function after painting, as shown, as only a few 'common' display device contexts can exist at once, and failure to release them after painting will affect the ability of other programs to use them.
If, however, you have assigned a device context to your program window when registering the window class, via the class data structure WNDCLASSEX, it is not necessary to call ReleaseDC. This is because the device context data structure within the system is assigned to your window rather than to the system itself. When the user exits your program, the device context data structure is automatically destroyed.
The device context can be assigned to your window by assigning a CS_CLASSDC or CS_OWNDC flag to the .lpfnWndProc element of the WNDCLASSEX data structure.
I have always preferred to use the GetDC method of painting to the client area, rather than the WM_PAINT method, as I find it simpler, and find it easier to control the painting operation in this way. It is really a matter of personal preference, however, as to which method you use, or if you want to sometimes use one method, and sometimes the other in the same program.
THE ACTUAL PAINTING OPERATION:
What have been described above are the preparatory steps necessary to carry out a painting operation. The Windows operating system requires a 'device context' in order to implement the API functions used for the actual painting.
In general, a device context is a hidden data structure Windows creates to use with the API functions applicable to the device (screen, printer etc.) A programmer uses the device context only via its identifier number, or 'handle'.
Once you have a device context for the screen, you can use API functions to paint to the screen.
The simplest API function is SetPixel, which sets a single pixel at particular x and y (horizontal and vertical) screen coordinates to a specified colour. If you want to set numerous pixels to different colours, you can call this function repeatedly for each pixel. This method, however, is extremely slow by comparison with other methods, using other API functions, which set whole blocks of pixels, so it is really not usable except when you want to set only a reasonably small number of pixels
There is a general difficulty with printing directly to the screen memory in that the actual painting process will be noticeable to the viewer. To avoid this it is better to create a virtual copy of the screen, or part of the screen, in RAM, in the form of a bitmap
and print a complete image to this first. Then an API function can be used to transfer the completed bitmap to the screen memory in one action, thus avoiding making updating the screen visible to the viewer.
The API function CreateDIBSection (DIB stands for Device Independent Bitmap) can be used to create a bitmap in memory which will give the memory address of the bitmap bits (pixels). A program can write directly to this bitmap and use it just as if it were the video screen memory. The bitmap thus acts as a 'virtual' screen. Then the API function BitBlt can be used to transfer the bitmap, or part of it, to the screen memory in a single step, updating the real screen from the virtual screen.
What follows is an example of the sequence of API function calls used to transfer a rectangle from a bitmap to the screen. First we get a display device context for the program window, and then a 'compatible' device context for the bitmap, or virtual screen, which is compatible with the screen device context. Next the bitmap, as an object, is 'selected' into the compatible device context, and the BitBlt API function is used to transfer a rectangle, as described by the RECT data structure, from the bitmap to the screen. Then the bitmap is deselected from the compatible device context, and both device contexts are deleted.
api GetDC,[WINDOW.Handle]
mov [DISPLAY.DC],eax
api CreateCompatibleDC,[DISPLAY.DC]
mov [COMP.DC],eax
api SelectObject,[COMP.DC],[BITMAP.Handle]
mov [BITMAP.Handle.sv],eax
mov esi,RECT
api BitBlt,[DISPLAY.DC],[esi],[esi+4],[RECT.Width],[RECT.Height],[COMP.DC],[esi],[esi+4],SRCCOPY
api SelectObject,[COMP.DC],[BITMAP.Handle.sv]
mov [BITMAP.Handle.sv],dword 0
api DeleteDC,[COMP.DC]
mov [COMP.DC],dword 0
api ReleaseDC,[WINDOW.Handle],[DISPLAY.DC]
mov [DISPLAY.DC],dword 0
Note that the same RECT data is used twice in the BitBlt function. This supposes that a bitmap has been created which is the same size in memory as the screen itself, so that any rectangle has the same coordinates in both the virtual screen, or bitmap, and the screen itself. After the bitmap data is printed to the screen, the bitmap is deselected from the compatible device context, using the SelectObject API function. There is no DeSelectObject function. Instead, SelectObject is used to reselect the saved object in [BITMAP.Handle.sv], which replaces the selected bitmap handle and thus automatically
deselects the bitmap. When the compatible device context was created, it contained no bitmap object. Nevertheless, the operating system gives it a 'placeholder' pseudo-object, with a handle, so that this can be reselected back into the compatible device context in order to deselect the bitmap that replaced it. The compatible device context is then deleted, and the screen device context is 'released' (not 'deleted'). All this deleting of device contexts is necessary to avoid internal system data structures gradually consuming the system memory resources assigned to them.
Part I: Part I Introduction
Chapter 1: Binary numbers, code, and procedures
Chapter 3: Assembly Language
Chapter 4: Assembly Language Code example
Chapter 5: Macros and HLA
Chapter 6: HLA code example
Chapter 7: The Windows operating system
Chapter 8: Data Structures
Chapter 9: How to Create a Windows Program
Chapter 10: How to Create an Exe File
Chapter 11: Structured Programming and OOP
Part II: Part II Introduction
Chapter 12: Debugging a Windows Program Code
Chapter 13: Painting the Window Client Area
Chapter 14: Creating Window Menus
Chapter 15: How to Create Toolbars
Chapter 16: How to Create Popup Menus
Chapter 17: About the Windows Clipboard
Chapter 18: How to Create Bitmaps
Chapter 19: Icons and the Ico Format
Chapter 20: Common Dialog Boxes
Chapter 21: Working with Files
Chapter 22: Scrollbars and Scrolling
Chapter 23: How to Send Data to the Printer
Chapter 24: Miscellaneous Topics
© Alen, June 2014
alen@alenspage.net
Material on this page may be reproduced
for personal use only.