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 2 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
message 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 paramter,
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
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 it's 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