I've already explained some of the things messages are used for in Windows. Now I'll go over how to make use of them. The prototype for a message handling function looks like this:
The LRESULT type of the return value is used specfically for message processing functions like the one we're going to write. I talked about the CALLBACK convention a little bit earlier. The parameters are very simple:
HWND hwnd: This is the handle of the window that sent the message currently being processed.
UINT msg: This is a message identifier. The values for this parameter are constants beginning with WM_ (for "Windows message"). The number of different messages that can be sent is ridiculously high, but here are some important ones:
WPARAM wparam, LPARAM lparam: The exact use of these parameters depends on which message is being sent, but they are used to further specify the meaning of the message.
If you had to write code to handle every message that your window might receive, you'd probably go insane. I know I would! Thankfully, Windows provides a default message handler. If you don't have any special instructions for handling certain messages, you can always call DefWindowProc(). With that in mind, here is the simplest, fully functional message handler that you could possibly write:
Simple, hey? Usually you'll want to handle some of these messages yourself. In that case, you can write your own code, and return 0 to tell the program that you've dealt with the message. Here's an example of a message handler that calls an initialization function when the window is created, and calls the default handler for anything else.
Your message handler will probably end up being a big switch statement to accommodate the messages you want to handle manually, followed by a call to the default handler for everything else. Now there's just one more thing I need to show you before everything is working smoothly, and that's how to make sure your message handler is getting called when it has work to do.
Reading the Message Queue
Near the beginning of your program's main loop, you need to see if the message queue -- where all pending messages are stored -- has anything waiting for you. If so, there are a few things you need to do in order for your handler to do its job correctly. The function you need here is PeekMessage(). Here is its prototype:
The return type, BOOL, is really just an int, but it takes only two values: TRUE or FALSE. If a message is waiting on the queue, the function returns TRUE. Otherwise, it returns FALSE. The parameters are pretty straightforward:
LPMSG lpMsg: This is a pointer to a variable of type MSG. If a message is waiting, this variable will be filled with the message information.
HWND hWnd: The handle of the window whose queue you want to check.
UINT wMsgFilterMin, wMsgFilterMax: The indices of the first and last messages in the queue to check. Most of the time, you'll only be interested in the first message on the queue, so you would set both of these parameters to 0.
UINT wRemoveMsg: Generally this takes only two values, PM_REMOVE or PM_NOREMOVE. Use the former if you want to remove the message from the queue after reading it, and the latter if you want to leave the message on the queue. Usually, if a message is waiting, you'll prepare it to be handled right away, in which case you should use PM_REMOVE.
If a message is waiting, you need to do a few things to get your handler to kick in. Don't worry, it's only two simple calls: one to TranslateMessage() and one to DispatchMessage(). Their prototypes are very similar:
The first call performs a bit of translation on the message, as you may have guessed, and the second call invokes your message handler and sends it the appropriate information from the MSG structure. That's all you need to know! With every iteration of your main loop, if a message is waiting, you call these two functions and your MsgHandler() function takes care of the rest. Here's a code example:
No problem! Now you can write a Windows program that creates and registers a window class, and creates a window with a valid message handler. That wasn't so bad, was it? There are just a few more things I'd like to mention before I wrap this article up. While we're on the topic of messages, there will come a time when you want to send messages manually. Here's how.
There are actually two ways to go about doing this. You can either call PostMessage() or SendMessage(). Their prototypes are very similar:
The parameters are the same as those taken by the MsgHandler() function we wrote, so I won't go over them again. The only thing you need to know is the difference between the two functions, so I'll go over each one briefly.
PostMessage() is used when you simply want to add a message to the queue and let your program logic take care of it. The function returns a nonzero value (TRUE) if it succeeds, or zero (FALSE) if it fails. It simply adds the message you specify to the queue, and returns immediately. In most cases, a call to PostMessage() will get the job done.
SendMessage() is a bit different. Notice that it returns an LRESULT, which is used for message processing functions. That's because SendMessage() doesn't post a message to the queue -- it translates the message and invokes the message handler immediately, and doesn't return until the message handler has finished processing the message. SendMessage() is used rather than PostMessage() for events of higher priority that need to occur quickly. Use it when you want something done immediately.
Now that you know that, the topic of messages leads into the last topic I need to mention for now, and that is a major difference between Windows programming and DOS programming.