Processing Messages - C++

After creating a window, the next step is to handle all the incoming events. As you have learned, you use WndProc to do this. However, there is a little more to it than that.

CodeWarrior provides the following code when you generate a Windows application:

Although it might not look like it, this code segment is the heart of your program. After the window is created, the program enters this while loop and does not exit until the program finishes.

The main function that keeps this loop going is GetMessage. This message gets the next message that needs to be processed from Windows. If there are no more messages (the program has ended), GetMessage returns 0, and the while loop ends.

Here is the function declaration for GetMessage:

This function fills lpMsg with the next message. You can ignore the other three parameters. They are not very important (except for advanced programming).

For the first parameter, you must pass a reference to an MSG structure (no, not the stuff that’s bad for you). Here is the MSG structure:

First, you create a MSG object; then you pass it to GetMessage. Here is the code so far:

You now have a working event loop. This is a very important step. Now that you have the messages, you probably want to do something with them. You do so with the TranslateMessage and DispatchMessage functions.

These functions take a reference to the MSG object as an argument. Translate Message prepares all input that was received. Don’t worry too much about how TranslateMessage works; just remember that you have to call it.

DispatchMessage sends the message away to WndProc to be handled. Take a look at the code now:

That’s message processing in a nutshell. Windows programming isn’t so bad, is it? You just have to get used to it.

Handling Events

Once the messages (indicating an event) are sent to WndProc, you must handle them in some fashion. The usual way to do so is to create a switch statement. Then you can handle a different event for each case.

Earlier in this chapter (refer to the section “Investigating WndProc”), you learned that the messg argument of WndProc holds the ID of the message (with things such as WM_PAINT). Having the message identifier means that you can use this argument in the switch statement. Here is the basic structure:

Now you can test messg against each kind of message to figure out which is being sent. This procedure works fine, except for one flaw: There are too many different kinds of messages. You could spend years programming what to do for every case, and your code would be huge.

Fortunately, you can ask Windows to handle messages that you don’t want to handle. You do so by calling the function DefWindowProc. You are saying, “I don’t want to deal with this; can you? Here’s all the information you need.” The arguments that DefWindowProc takes are the same as those that WndProc takes, so you can just pass those arguments along. Here’s how the call to DefWindowProc looks:

Although you want Windows to handle most of the messages, you want to handle some of them yourself. To do so, you put the call to DefWindowProc in the default case of the switch statement, like this:

The return value of DefWindowProc works the same way as the return value for WndProc, so you can just return what DefWindowProc returns. Here is the finished version:

Now you are ready to learn how to handle some of the different messages.

WM_CREATE occurs when a window is first created. You can use this message to perform all initialization tasks that need to be done (this is the official, proper place to initialize things).

WM_DESTROY occurs when a window is about to be destroyed. Usually, this means that the application must end (you must end it yourself). To end the application, you call the function PostQuitMessage. This sends a WM_QUIT message (where you can put more code) and ends the program. Here is an example:

Being on Time

Often you want to do something at a particular interval in time. For example, you might want to refresh the screen every 1/26 second. Fortunately, Windows provides a convenient way for you to do this.

You must create what is called a timer. A timer sends a WM_TIMER message to your program after a certain interval. In fact, you can have more than one timer. One might have a 2-second delay, and one might have a 3-second delay.

To create a timer, you call the function SetTimer. This function takes four arguments. Here is the function declaration:

hWnd is the handle to the window of which this timer is a part. nIDevent is the ID of the timer. The timer ID can be any integer. You use it to identify the timer. nElapse is the delay that you want your timer to have. This argument is measured in milliseconds (1000 milliseconds = 1 second). The last parameter, lpTimerFunc, is the name of a function that is called every time the timer goes off. If you don’t want to use a function, you can use the WM_TIMER message instead, and you can set this parameter to NULL.

The best place to put the call to SetTimer is in the WM_CREATE message.

After you create the timer, you can respond whenever it goes off by using the WM_TIMER event. The WM_TIMER event occurs whenever a timer goes off. The WndProc argument, wParam, stores the ID of the timer so that you respond differently to different timers.

Finally, when you are done with a timer (most likely when the program ends), you can destroy it by calling KillTimer. Here is the declaration to do so:

The first argument is the handle to the window, and the second one is the ID of the timer. You will most likely call this function in the WM_DESTROY message (before the call to PostQuitMessage).

Here is an example of how you might use a timer:

Painting in the Window

You can use the WM_PAINT message to draw things inside your window. You can draw all kinds of things. Of course, you don’t have to draw things inside the WM_PAINT event. You can do it anywhere.

The WM_PAINT event occurs whenever your window needs to be redrawn. This can be because it was resized, because it used to be behind another window, or for any number of reasons.

You can call two main functions in order to draw. You can use either BeginPaint or GetDC. You use BeginPaint when you are drawing in the WM_PAINT event, and you use GetDC when you are painting anywhere else.

Both these functions create what is called a device context. Device contexts are complicated. For now, all you need to know is that they give permission to draw. You can’t draw anything without one.

When you are done with a device context, you must release it, with EndPaint (if you used BeginPaint) or with ReleaseDC (if you used GetDC).

BeginPaint takes two parameters, a handle to the window and a reference to a PAINTSTRUCT object. This function fills the PAINTSTRUCT object with information about what needs to be repainted. You really don’t need to worry about this. You can just repaint the whole window if you want (although this is a bit inefficient). Here is how you call BeginPaint:

EndPaint takes the exact same arguments. Here is how you call it:

GetDC takes a handle to the window as a parameter. Here is how you call it:

Finally, ReleaseDC takes two arguments. One is the handle to the window, and the other is the device context. Here is how you call it:

This is how the code for the WM_PAINT message might look:

This is how painting in other events might look:

Now that you have the preparation out of the way, it’s time for the fun part. You get to draw things.

The easiest thing to draw is a line. First, you set the starting position. Then you tell Windows where to draw the line. Here is an example:

The first parameter for these functions is the device context with which to draw. The next two are the x (x position starts at 0 and increases as you move right on the screen or device context) and y (y starts at 0 and increases as you move down the screen or device context) positions. The last NULL on MoveToEx is for storing where you started (don’t worry about it for now). The upper-left corner of the window is at position (0,0).

When you call the function LineTo, it moves your current position. So, if you call LineTo again, it will draw a new line starting where the last one ended.

The next thing you can draw onscreen is a rectangle. You do this by calling the Rectangle function. Here is the declaration:

x1 and y1 denote the position of the upper-left corner, and x2 and y2 denote the position of the lower-right corner. Here is an example:

This function call draws a square. You can also draw a filled rectangle that doesn’t have an outline with the FillRect function. Instead of taking the coordinates of the rectangle as parameters, FillRect takes a pointer to a RECT structure. A RECT structure stores four integer values: top, left, right, and bottom. The last parameter is a handle to a BRUSH object (which specifies how the rectangle should be filled). Here is an example:

The last thing you can draw is an ellipse with the Ellipse function. To draw an ellipse, you provide the coordinates of the rectangle that bound it. Windows automatically figures out how the ellipse should look. The parameters for the Ellipse function are actually exactly the same as for the Rectangle function. Here is an example:

This function call creates a circle with a center at (25,25) and a radius of 5.

Well, that’s about it for our crash course in drawing. Drawing in Windows is pretty easy once you get used to it.

Reading Keyboard Input

You can use the WM_KEYDOWN message to read keyboard input. The wParam parameter of WndProc stores the virtual key code, which identifies the key being pressed. Table 12.10 lists the virtual key codes for a standard keyboard.

Virtual Key Codes

Virtual Key Codes

First, you convert wParam to an integer like this:

Then you can use a switch statement on virtualCode to decide how to respond. That’s really about all there is to it.

All rights reserved © 2020 Wisdom IT Services India Pvt. Ltd Protection Status

C++ Topics