Introduction
Perhaps one of the most important means of communication in windows is Messages. The traditional program starts at your main()
function, moves down line-by-line in your code, and eventually exits. The Windows concept is different. The way you program in windows is by responding to events. These events are called messages.
Messages can signal many events, caused by the user, the operating system, or another program. An event could be caused by a mousemove, a key-press, or by your window getting resized. There are two kinds of messages: a window message, or a thread message. Since Threads are an advanced issue, I'll refer only to window messages.
Window Messages
In general, a message must be sent to a window. All the messages sent to you are stored in a Message Queue, a place in the memory which stores messages which are transferred between applications.
Message Loop
The way you retrieve messages from the Message Queue is by creating a Message Loop. A Message Loop is a loop that checks for messages in the Message Queue. Once a message is received, the Message Loop dispatches the message by calling a Message Handler, a function designed to help the Message Loop at processing the message.
The Message Loop will end when a WM_QUIT
message is received, signaling the application to end. This message could be sent because the user selected Exit from your File menu, clicked on the close button (the X small button in the upper right corner of your window), or pressed Alt+F4. Windows has default Message Handlers for almost all the messages, giving your window the default window behavior. In fact, all the standard controls are simply windows with Message handlers. Take a Button
for example. When it gets a WM_PAINT
message, it will draw the button. When you Left-click the button, it gets a WM_LBUTTONDOWN
message, and it draws the pressed-button. When you let go of the mouse button, it receives a WM_LBUTTONUP
message, and respectively draws the button.
Windows defines many different message types (which are stored as UINTs). They usually begin with the letters "WM
" and an underscore, as in WM_CHAR
and WM_SIZE
. The names of the message are usually a good indicator of what they represent. WM_SIZE
for sizing messages, WM_CHAR
for character entry messages and so on. The naming convention in MFC for message handler functions is to take away the "WM_
" and replace it with "On
", so the message handler for WM_SIZE
is usually called OnSize
.
A message comes with 2 parameters that give you more information about the event. Each parameter is a 32-bit value: lParam
and wParam
. For example: WM_MOUSEMOVE
will give you the mouse coordinates in one parameter, and in the other, some flags indicating the state of the ALT, Shift, CTRL and mouse buttons.
A Message
may also return a value which allows you to send data back to the sending program. For example, the WM_QUERYENDSESSION
message sent by windows before the computer is shutdown, expects you to return a Boolean value. If your application can terminate conveniently, it should return TRUE
; otherwise, it should return FALSE
. Other message such as the WM_CTLCOLOR
messages expect you to return an HBRUSH
.
Note: In the rest of the tutorial, I will focus on MFC for simplicity reasons. All the information above applies to both SDK programs, and MFC programs.
Message Handlers
Fortunately, MFC will give all the code needed for the message loop, One of the CWinApp
member functions called by WinMain
—Run
—provides the message loop that pumps messages to the application's window. The only thing you need to do so you can receive messages is to create Message Handlers, and inform MFC of them. So, how do you create a Message Handler? Once you have an MFC C++ class that encapsulates a window, you can easily use ClassWizard
to create Message Handlers.
Using ClassWizard to Create Message Handlers
Press Ctrl+W to start the ClassWizard
, or right click the Add button and select ClassWizard
from the context menu. Open ClassWizard
, select Message Maps tab. In Class name, select the name of your C++ class. on Object IDs select either the ID of a menu item (for messages caused by the user interacting with a menu), the ID of a control (for messages caused by the user interacting with a control), or the first option to handle messages other messages. Choose the message from the Messages list, WM_SIZE
for example, and Click on Add Function. Click OK, then click Edit Code. ClassWizard
will write a new empty function (OnSize
for example) with the proper prototype in the class header. The code generated should look similar to this:
void CAboutWindow::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
}
That's it, now you can handle messages. If you want to handle a message and then let the default message handler handle the message, you should call the base class member function that corresponds with the message. Take the following WM_CLOSE
Message Handler as an example:
void CAboutWindow::OnClose()
{
}
If you want windows to get a shot at the message, you should call the base class member function OnClose
:
void CAboutWindow::OnClose()
{
MessageBox(_T("Closing the window!"))
CWnd::OnClose()
}
You could use this behavior to screen-out events. For example, a program that prompts the user if he is sure that he wants to close the window:
void CAboutWindow::OnClose()
{
int Ret = MessageBox(_T("Are you sure you want to close the window?"),
_T("Close Window?"), MB_YESNO);
if(Ret == IDYES){
CWnd::OnClose()
}
else{
}
}
Sending Messages
Besides receiving messages, you will often find your self sending messages. You might want to send messages to communicate between to windows in your program, or to communicate between different programs. In order to send a message, you need a pointer to a C++ window class. This can be retrieved using various functions, including CWnd::FindWindow
, GetDlgItem()
, GetParent()
, and more. The CWnd
class has a SendMessage()
member function which allows you to send messages to its window. For example, let’s say you have a CWnd
pointer to the Calculator
, and you want to close it. What you should do is send a WM_CLOSE
message, which will notify the Calculator
that it should close. You can use the following code. In order to get a pointer to Calculator
, I use the static CWnd::FindWindow()
function and pass the title of the window, which in our case is "Calculator
".
CWnd *pCalc;
pCalc = CWnd::FindWindow(NULL, _T("Calculator));
if(pCalc == NULL){
//Couldn't find Calculator
}
else{
pCalc->SendMessage(WM_CLOSE);
//Presto! The Calculator should close.
}
More to Come
History
- 8th June, 2000: Initial version
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.