
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
Windows Programming Concepts
The exact contents of the wParam and lParam members are dependent on what kind of message it is. The message ID in the member message is an integer value and can be one of a set of values that are predefined in the header file, windows.h, as symbolic constants. They all start with WM_ and typical examples are WM_PAINT to redraw the screen and WM_QUIT to end the program. The function GetMessage()always returns TRUE unless the message is WM_QUIT to end the program, in which case the value returned is FALSE, or unless an error occurs, in which case the return value is -1. Thus, the while loop continues until a quit message is generated to close the application or until an error condition arises. In either case, you need to end the program by passing the wParam value back to Windows in a return statement.
The second argument in the call to GetMessage() is the handle of the window for which you want to get messages. This parameter can be used to retrieve messages for one window separately from another. If this argument is 0, as it is here, GetMessage()retrieves all messages for an application. This is an easy way of retrieving all messages for an application regardless of how many windows it has. It is also
the safest way because you are sure of getting all the messages for your application. When the user of your Windows program closes the application window, for example, the window is closed before the WM_QUIT message is generated. Consequently, if you only retrieve messages by specifying a window handle to the GetMessage() function, you cannot retrieve the WM_QUIT message and your program is not able to terminate properly.
The last two arguments to GetMessage() are integers that hold minimum and maximum values for the message IDs you want to retrieve from the queue. This allows messages to be retrieved selectively. A range is usually specified by symbolic constants. Using WM_MOUSEFIRST and WM_MOUSELAST as these two arguments would select just mouse messages, for example. If both arguments are zero, as you have them here, all messages are retrieved.
Multitasking
If there are no messages queued, the GetMessage() function does not come back to your program. Windows allows execution to pass to another application and you only get a value returned from calling GetMessage() when a message appears in the queue. This mechanism was fundamental in enabling multiple applications to run under older versions of Windows and is referred to as cooperative multitasking because it depends on concurrent applications giving up their control of the processor from time to time. After your program calls GetMessage(), unless there is a message for your program, another application is executed and your program gets another opportunity to do something only if the other application releases the processor, perhaps by a call to GetMessage()when there are no messages queued for it, but this is not the only possibility.
With current versions of Windows, the operating system can interrupt an application after a period of time and transfer control to another application. This mechanism is called pre-emptive multitasking because an application can be interrupted in any event. With pre-emptive multitasking, however, you must still program the message loop in WinMain() using GetMessage() as before, and make provision for relinquishing control of the processor to Windows from time to time in a long running calculation (this is usually done using the PeekMessage() API function). If you don’t do this, your application may be unable to respond to messages to repaint the application window when these arise. This can be for reasons that are quite independent of your application — when an overlapping window for another application is closed, for example.
629

Chapter 11
The conceptual operation of the GetMessage() function is illustrated in Figure 11-3.
|
Windows |
|
GetMessages() |
|
|
Message? |
N |
|
|
|
|
Y |
|
|
Get the Message |
|
|
Y |
|
|
WM_QUIT? |
|
|
N |
|
|
|
Your Program |
run |
|
another |
|
return |
WinMain() |
application |
|
||
TRUE |
|
|
return |
while(GetMessages( ) ) |
|
FALSE |
{ |
|
|
|
|
|
... |
|
|
} |
|
Figure 11-3 |
|
|
Within the while loop, the first function call to TranslateMessage() requests Windows to do some conversion work for keyboard related messages. Then the call to the function DispatchMessage() causes Windows to dispatch the message, or in other words, to call the WindowProc() function in your program to process the message. The return from DispatchMessage() does not occur until WindowProc() has finished processing the message. The WM_QUIT message indicates that the program should end, so this results in FALSE being returned to the application that stops the message loop.
630

Windows Programming Concepts
A Complete WinMain() Function
You have looked at all the bits that need to go into the function WinMain(). So now you can assemble them into a complete function:
// Listing OFWIN_1
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX WindowClass; |
// Structure to hold our window’s attributes |
|
static LPCTSTR szAppName = L”OFWin”; |
// Define window class name |
|
HWND hWnd; |
|
// Window handle |
MSG msg; |
|
// Windows message structure |
WindowClass.cbSize = sizeof(WNDCLASSEX); |
// Set structure size |
|
// Redraw the window if the size changes |
|
|
WindowClass.style = CS_HREDRAW | CS_VREDRAW; |
||
// Define the message handling function |
|
|
WindowClass.lpfnWndProc = WindowProc; |
|
WindowClass.cbClsExtra = 0; // No extra bytes after the window class WindowClass.cbWndExtra = 0; // structure or the window instance
WindowClass.hInstance = hInstance; |
// Application instance handle |
//Set default application icon WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);
//Set window cursor to be the standard arrow WindowClass.hCursor = LoadCursor(0, IDC_ARROW);
//Set gray brush for background color WindowClass.hbrBackground =
static_cast<HBRUSH>(GetStockObject(GRAY_BRUSH));
WindowClass.lpszMenuName = 0; |
// No menu |
WindowClass.lpszClassName = szAppName; |
// Set class name |
WindowClass.hIconSm = 0; |
// Default small icon |
//Now register our window class RegisterClassEx(&WindowClass);
//Now we can create the window hWnd = CreateWindow(
szAppName, |
// the window class name |
L”A Basic Window the Hard Way”, // The window title |
|
WS_OVERLAPPEDWINDOW, |
// Window style as overlapped |
CW_USEDEFAULT, |
// Default screen position of upper left |
CW_USEDEFAULT, |
// corner of our window as x,y... |
CW_USEDEFAULT, |
// Default window size |
CW_USEDEFAULT, |
// .... |
0, |
// No parent window |
0, |
// No menu |
631

Chapter 11
hInstance, |
// Program Instance handle |
|
0 |
// No window creation data |
|
); |
|
|
ShowWindow(hWnd, nCmdShow); |
// Display the window |
|
UpdateWindow(hWnd); |
// Cause window client area to be drawn |
|
// The message loop |
|
|
while(GetMessage(&msg, 0, 0, 0) == TRUE) |
// Get any messages |
|
{ |
|
|
TranslateMessage(&msg); |
|
// Translate the message |
DispatchMessage(&msg); |
|
// Dispatch the message |
} |
|
|
return static_cast<int>(msg.wParam); |
// End, so return to Windows |
}
How It Works
After declaring the variables you need in the function, all the members of the WindowClass structure are initialized and the window is registered.
The next step is to call the CreateWindow() function to create the data for the physical appearance of the window, based on the arguments passed and the data established in the WindowClass structure that was previously passed to Windows using the RegisterClassEx() function. The call to ShowWindow() causes the window to be displayed according to the mode specified by nCmdShow, and the UpdateWindow() function signals that a message to draw the window client area should be generated.
Finally, the message loop continues to retrieve messages for the application until a WM_QUIT message is obtained, whereupon the GetMessage() function returns FALSE and the loop ends. The value of the wParam member of the msg structure is passed back to Windows in the return statement.
Message Processing Functions
The function WinMain() contained nothing that was application-specific beyond the general appearance of the application window. All of the code that makes the application behave in the way that you want is included in the message processing part of the program. This is the function WindowProc() that you identify to Windows in the WindowClass structure. Windows calls this function each time a message for your main application window is dispatched.
This example is simple, so you will be putting all the code to process messages in the one function, WindowProc(). More generally, though, the WindowProc() function is responsible for analyzing what a given message was and which window it was destined for and then calling one of a whole range of functions, each of which would be geared to handling a particular message in the context of the particular window concerned. However, the overall sequence of operations, and the way in which the function WindowProc() analyses an incoming message, is much the same in most application contexts.
632

Windows Programming Concepts
The WindowProc() Function
The prototype of our WindowProc() function is:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
The return type is LRESULT, which is a type defined by Windows and is normally equivalent to type long. Because the function is called by Windows through a pointer (you set the pointer up in WinMain() in the WNDCLASSEX structure), you need to qualify the function as CALLBACK. This is another specifier defined by Windows that determines how the function arguments are handled. The four arguments that are passed provide information about the particular message causing the function to be called. The meaning of each of these arguments is described in the following table:
Argument |
Meaning |
|
|
HWND hWnd |
A handle to the window in which the event causing the message |
|
occurred. |
UINT message |
The message ID, which is a 32-bit value indicating the type of message. |
WPARAM wParam |
A 32-bit value containing additional information depending on what sort |
|
of message it is. |
LPARAM lParam |
A 32-bit value containing additional information depending on what sort |
|
of message it is. |
|
|
The window that the incoming message relates to is identified by the first argument, hWnd, that is passed to the function. In this case, you have only one window, so you can ignore it.
Messages are identified by the value message that is passed to WindowProc(). You can test this value against predefined symbolic constants, each of which relates to a particular message. They all begin with WM_, and typical examples are WM_PAINT, which corresponds to a request to redraw part of the client area of a window, and WM_LBUTTONDOWN, which indicates that the left mouse button was pressed. You can find the whole set of these by searching for WM_ in the MSDN Library.
Decoding a Windows Message
The process of decoding the message that Windows is sending is usually done using a switch statement in the WindowProc() function, based on the value of message. Selecting the message types that you want to process is then just a question of putting a case statement for each case in the switch. The typical structure of such a switch statement, with arbitrary cases included, is as follows:
switch(message)
{
case WM_PAINT:
// Code to deal with drawing the client area
break;
case WM_LBUTTONDOWN:
// Code to deal with the left mouse button being pressed
633

Chapter 11
break;
case WM_LBUTTONUP:
// Code to deal with the left mouse button being released break;
case WM_DESTROY:
// Code to deal with a window being destroyed break;
default:
// Code to handle any other messages
}
Every Windows program has something like this somewhere, although it may be hidden from sight in the Windows programs that you will write later using MFC. Each case corresponds to a particular value for the message ID and provides suitable processing for that message. Any messages that a program does not want to deal with individually are handled by the default statement, which should hand the messages back to Windows by calling DefWindowProc(). This is the Windows API function providing default message handling.
In a complex program dealing specifically with a wide range of possible Windows messages, this switch statement can become large and rather cumbersome. When you get to use the Application wizard to generate a Windows application, you won’t have to worry about this because it is all taken care of for you and you never see the WindowProc() function. All you need to do is to supply the code to process the particular messages in which you are interested.
Drawing the Window Client Area
Windows sends a WM_PAINT message to the program to signal that the client area of an application should be redrawn. So in your example, you need to draw the text in the window in response to the
WM_PAINT message.
You can’t go drawing in the window willy-nilly. Before you can write to the application window, you need to tell Windows that you want to do so, and get Windows’ authority to go ahead. You do this by calling the Windows API function BeginPaint(), which should only be called in response to a WM_PAINT message. It is used as follows:
HDC hDC; |
// A display context handle |
PAINTSTRUCT PaintSt; |
// Structure defining area to be redrawn |
hDC = BeginPaint(hWnd, &PaintSt); |
// Prepare to draw in the window |
|
|
The type HDC defines what is called a display context, or more generally a device context. A device context provides the link between the device-independent Windows API functions for outputting information to the screen or a printer, and the device drivers which support writing to the specific devices attached to your PC. You can also regard a device context as a token of authority which is handed to you on request by Windows and grants you permission to output some information. Without a device context, you simply can’t generate any output.
The BeginPaint() function provides you with a display context as a return value and requires two arguments to be supplied. The window to which you want to write is identified by the window handle,
634

Windows Programming Concepts
hWnd, which you pass as the first argument. The second argument is the address of a PAINTSTRUCT variable PaintSt, in which Windows places information about the area to be redrawn in response to the WM_PAINT message. I will ignore the details of this because you are not going to use it. You will just redraw the whole of the client area. You can obtain the coordinates of the client area in a RECT structure with the statements:
RECT aRect; |
// A working rectangle |
GetClientRect(hWnd, &aRect); |
|
The GetClientRect() function supplies the coordinates of the upper-left and lower-right corners of the client area for the window specified by the first argument. These coordinates are stored in the RECT structure aRect, which is passed through the second argument as a pointer. You can then use this definition of the client area for your window when you write the text to the window using the DrawText() function. Because your window has a gray background, you should alter the background of the text to be transparent, to allow the gray to show through; otherwise, the text appears against a white background. You can do this with this API function call:
SetBkMode(hDC, TRANSPARENT); |
// Set text background mode |
The first argument identifies the device context and the second sets the background mode. The default option is OPAQUE.
You can now write the text with the statement:
DrawText(hDC, |
// Device context handle |
|
L”But, soft! What light through yonder window breaks?”, |
||
-1, |
// Indicate null terminated string |
|
&aRect, |
// Rectangle in which text is to be drawn |
|
DT_SINGLELINE| |
// Text format - single line |
|
DT_CENTER| |
// |
- centered in the line |
DT_VCENTER |
// |
- line centered in aRect |
); |
|
|
|
|
|
The first argument to the DrawText() function is your certificate of authority to draw on the window, the display context hDC. The next argument is the text string that you want to output. You could equally well have defined this in a variable and passed the pointer to the text as the second argument in the function call. The next argument, with the value -1, signifies that your string is terminated with a null character. If it weren’t, you would put the count of the number of characters in the string here. The fourth argument is a pointer to a RECT structure defining a rectangle in which you want to write the text. In this case it is the whole client area of the window defined in aRect. The last argument defines the format for the text in the rectangle. Here you have combined three specifications with a bitwise OR (|). The string is written as a single line with the text centered on the line and the line centered vertically within the rectangle. This places it nicely in the center of the window. There are also a number of other options, which include the possibility to place text at the top or the bottom of the rectangle, and to left or right justify it.
After you have written all that you want to display, you must tell Windows that you have finished drawing the client area. For every BeginPaint() function call, there must be a corresponding EndPaint() function call. Thus, to end processing the WM_PAINT message, you need the statement:
EndPaint(hWnd, &PaintSt); |
// Terminate window redraw operation |
635

Chapter 11
The hWnd argument identifies your program window, and the second argument is the address of the PAINTSTRUCT structure that was filled in by the BeginPaint() function.
Ending the Program
You might assume that closing the window closes the application, but to get this behavior you actually have to add some more code. The reason that the application won’t close by default when the window is closed is that you may need to do some clearing up. It is also possible that the application may have more than one window. When the user closes the window by double-clicking the title bar icon or clicking the Close button, this causes a WM_DESTROY message to be generated. Therefore, to close the application, you need to process the WM_DESTROY message in the WindowProc() function. You do this by generating a WM_QUIT message with the following statement:
PostQuitMessage(0);
The argument here is an exit code. This Windows API function does exactly what its name suggests — it posts a WM_QUIT message in the message queue for your application. This results in the GetMessage() function in WinMain() returning FALSE and ending the message loop, so ending the program.
A Complete WindowProc() Function
You have covered all the elements necessary to make up the complete WindowProc() function for your example. The code for the function is as follows:
// Listing OFWIN_2
LRESULT WINAPI WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC; |
// Display context handle |
PAINTSTRUCT PaintSt; |
// Structure defining area to be drawn |
RECT aRect; |
// A working rectangle |
switch(message) |
// Process selected messages |
{ |
|
|
case WM_PAINT: |
|
// Message is to redraw the window |
hDC = BeginPaint(hWnd, &PaintSt);// Prepare to draw the window |
||
// Get upper left and lower right of client area |
||
GetClientRect(hWnd, &aRect); |
|
|
SetBkMode(hDC, TRANSPARENT); |
|
// Set text background mode |
// Now draw the text in the window client area |
||
DrawText( |
|
|
hDC, |
// Device context handle |
|
L”But, soft! What light through yonder window breaks?”, |
||
-1, |
// Indicate null terminated string |
|
&aRect, |
// Rectangle in which text is to be drawn |
|
DT_SINGLELINE| |
// Text format - single line |
|
DT_CENTER| |
// |
- centered in the line |
DT_VCENTER); |
// |
- line centered in aRect |
EndPaint(hWnd, &PaintSt); |
// Terminate window redraw operation |
636

|
Windows Programming Concepts |
|
|
return 0; |
|
case WM_DESTROY: |
// Window is being destroyed |
PostQuitMessage(0); |
|
return 0; |
|
default: |
// Any other message - we don’t |
|
// want to know, so call |
|
// default message processing |
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
How It Works
The entire function body is just a switch statement. A particular case is selected, based on the message ID that is passed to the function through the message parameter. Because this example is simple, you need to process only two different messages: WM_PAINT and WM_DESTROY. You hand all other messages back to Windows by calling the DefWindowProc() function in the default case for the switch. The arguments to DefWindowProc() are those that were passed to the function, so you are just passing them back as they are. Note the return statement at the end of processing each message type. For the messages you handle, a zero value is returned.
A Simple Windows Program
Because you have written WinMain() and WindowProc() to handle messages, you have enough to create a complete source file for a Windows program using just the Windows API. The complete source file simply consists of an #include directive for the windows.h header file, a prototype for the WindowProc function and the WinMain and WindowProc functions that you have already seen:
// Ex11_01.cpp Native windows program to display text in a window #include <windows.h>
LRESULT WINAPI WindowProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam);
//Insert code for WinMain() here (Listing OFWIN_1)
//Insert code for WindowProc() here (Listing OFWIN_2)
Of course, you’ll need to create a project for this program, but instead of choosing Win32 Console Application as you’ve done up to now, you should create this project using the Win32 Project template. You should elect to create it as an empty project and then add the Ex11_01.cpp file to hold the code.
Try It Out |
A Simple Windows API Program |
If you build and execute the example, it produces the window shown in Figure 11-4.
637

Chapter 11
Figure 11-4
Note that the window has a number of properties provided by the operating system that require no programming effort on your part to manage. The boundaries of the window can be dragged to resize it, and the whole window can be moved about onscreen. The maximize and minimize buttons also work. Of course, all of these actions do affect the program. Every time you modify the position or size of the window, a WM_PAINT message is queued and your program has to redraw the client area, but all the work of drawing and modifying the window itself is done by Windows.
The system menu and Close button are also standard features of your window because of the options that you specified in the WindowClass structure. Again, Windows takes care of the management. The only additional effect on your program arising from this is the passing of a WM_DESTROY message if you close the window, as previously discussed.
Windows Program Organization
In the previous example you saw an elementary Windows program that used the Windows API and displayed a short quote from the Bard. It’s unlikely to win any awards, being completely free of any useful functionality, but it does serve to illustrate the two essential components of a Windows program: the WinMain() function that provides initialization and setup, and the WindowProc() function that services Windows messages. The relationship between these is illustrated in Figure 11-5.
It may not always be obvious in the code that you will see, but this structure is at the heart of all Windows programs. Understanding how Windows applications are organized can often be helpful when you are trying to determine why things are not working as they should be in an application. The WinMain() function is called by Windows at the start of execution of the program and the WindowProc() function, which you’ll sometimes see with the name WndProc(), is called by the operating system whenever a message is to be passed to your application’s window. In general there typically is a separate WindowProc() function in an application for each window in an application.
638