If there is one thing that sets Windows programming apart from other kinds of programming, it is messages. Most DOS progams, for example, relied on watching (sometimes called polling) possible sources of input like the keyboard or the mouse to await input from them. A program that wasn't polling the mouse would not react to mouse input. In contrast, everything that happens in a Windows program is mediated by messages. A message is a way for the operating system to tell an application that something has happened-for example, the user has typed, clicked, or moved the mouse, or the printer has become available. A window (and every screen element is a window) can also send a message to another window, and typically most windows react to messages by passing a slightly different message along to another window. MFC has made it much easier to deal with messages, but you must understand what is going on under the surface.
Messages are all referred to by their names, though the operating system uses integers to refer to them. An enormous list of #define statements connects names to numbers and lets Windows programmers talk about WM_PAINT or WM_SIZE or whatever message they need to talk about. (The WM stands for Window Message.) A message knows what window it is for, and can have up to two parameters. (Often several different values are packed into these parameters, but that's another story.)
Different messages are handled by different parts of the operating system or your application. For example, when the user moves the mouse over a window, the window gets a WM_MOUSEMOVE message, which it almost certainly passes to the operating system to deal with. The operating system redraws the mouse cursor at the new location. When the left button is clicked over a button, the button (which is a window) gets a WM_LBUTTONDOWN message and handles it, often generating another message to the window that contains the button, saying, in effect, "I was clicked."
MFC has allowed many programmers to completely ignore low-level messages like WM_MOUSEMOVE and WM_LBUTTONDOWN. They deal only with higher-level messages that mean things like "the third item in this list box has been selected" or "the Submit button has been clicked." All these kinds of messages move around in your code and the operating system code in the same way as the lower-level messages. The only difference is what piece of code "chooses" to handle them. MFC makes it much simpler to announce at the individual classes level which messages each class can handle. The old C way, which you will see in the next section, made those announcements at a higher level and interfered with the object-oriented approach to Windows programming, which involves hiding implementation details as much as possible inside objects.
The heart of any Windows program is the message loop, typically contained in a WinMain() routine. The WinMain() routine is, like the Main() in DOS or the main() in UNIX, the function called by the operating system when you run the program. You won't write any WinMain() routines; that sort of thing is now hidden away in the code that AppWizard generates for you. Still, it is happening, just as it is in Windows C programs. Listing 5.1 shows a typical WinMain().
Listing 5.1 Typical WinMain() Routine
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; if (! InitApplication (hInstance)) return (FALSE); if (! InitInstance (hInstance, nCmdShow)) return (FALSE); while (GetMessage (&msg, NULL, 0, 0)){ TranslateMessage (&msg); DispatchMessage (&msg); } return (msg.wParam); }
In a Windows C program like this, InitApplication() typically calls RegisterWindow(), and InitInstance() typically calls CreateWindow(), as you saw in Chapter 4 "Overview: What's in an Application?" Then comes the message loop, the while loop that calls GetMessage(). The API function GetMessage() fills msg with a message destined for this application and almost always returns True, so this loop runs over and over until the program is finished. The only thing that makes GetMessage() return False is if the message it gets is WM_QUIT.
TranslateMessage() is an API function that streamlines dealing with keyboard messages. Most of the time, you don't need to know "the A key just went down," "the A key just went up," and so on. It's enough to know "the user pressed A." TranslateMessage() deals with that. It catches the WM_KEYDOWN and WM_KEYUP messages, and sends, in most cases, and WM_CHAR message in their place. Of course, with MFC, most of the time you don't care that the user pressed A. The user types into an edit box or similar control, and you can get the entire string out of it later when the user has clicked OK. So don't worry too much about TranslateMessage().
The API function DispatchMessage() calls the WindProc for the window that the message is headed for. The WindProc for a Windows C function is a huge switch statement with one case for each message the programmer planned to catch, like the one in Listing 5.2.
Listing 5.2 Typical WndProc() Routine
LONG APIENTRY MainWndProc (HWND hWnd, // window handle UINT message, // type of message UINT wParam, // additional information LONG lParam) // additional information { switch (message) { case WM_MOUSEMOVE: //handle mouse movement break; case WM_LBUTTONDOWN: //handle left click break; case WM_RBUTTONDOWN: //handle right click break; case WM_PAINT: //repaint the window break; case WM_DESTROY: // message: window being destroyed PostQuitMessage (0); break; default: return (DefWindowProc (hWnd, message, wParam, lParam)); } return (0); }
As you can surely imagine, these WindProcs get very long in a hurry. Program maintenance can be a nightmare. MFC to the rescue!
Message maps are part of the MFC approach to Windows programming. Instead of writing a WinMain() function that sends messages to your WindProc and then writing a WindProc that checks which kind of message this is and then calls another of your functions, you just write the function that will handle the message, and add a message map to your class that says, in effect, "I will handle this sort of message." The framework handles whatever routing is required to get that message to you.
TIP |
If you've worked in Visual Basic, you should be familiar with event procedures, which handle specific events like a mouse click. The message-handling functions you will write in C++ are equivalent to event procedures. The message map is the way that events are connected to their handlers. |
Message maps come in two parts: one in the .h file for a class and one in the corresponding .cpp. Typically they are generated by wizards, although in some circumstances you will add entries yourself. Listing 5.3 shows the message map from the header file of one of the classes in a simple application called ShowString, presented in Chapter 17, "Building Menus and Dialogs."
Listing 5.3 Message Map from CHAP17\showstring.h
//{{AFX_MSG(CShowStringApp) afx_msg void OnAppAbout(); // NOTE - the ClassWizard will add and remove member functions here. // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG DECLARE_MESSAGE_MAP()
This declares a function called OnAppAbout(). The specially formatted comments around the declarations help ClassWizard keep track of which messages are caught by each class. DECLARE_MESSAGE_MAP() is a macro, expanded by the C++ compiler's preprocessor, that declares some variables and functions to set up some of this magic message catching.
The message map in the source file is, as shown in Listing 5.4, quite similar:
Listing 5.4 Message Map from CHAP17\showstring.cpp
BEGIN_MESSAGE_MAP(CShowStringApp, CWinApp) //{{AFX_MSG_MAP(CShowStringApp) ON_COMMAND(ID_APP_ABOUT, OnAppAbout) // NOTE - the ClassWizard will add and remove mapping macros // DO NOT EDIT what you see in these blocks of generated code! //}}AFX_MSG_MAP // Standard file based document commands ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew) ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen) // Standard print setup command ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup) END_MESSAGE_MAP()
BEGIN_MESSAGE_MAP and END_MESSAGE_MAP are macros that, like DECLARE_MESSAGE_MAP in the include file, declare some member variables and functions that the framework can use to navigate the maps of all the objects in the system. There are a number of macros used in message maps, including these:
In addition to these, there are about 100 macros, one for each of the more common messages, that direct a single specific message to a member function. For example, ON_CREATE delegates the WM_CREATE message to a function called OnCreate(). You cannot change the function names in these macros. Typically these macros are added to your message map by ClassWizard, as demonstrated in Chapter 17, "Building Menus and Dialogs."
The message maps presented in listings 5.3 and 5.4 are for the CShowStringApp class of the ShowString application. This class handles application-level tasks like opening a new file or displaying the About box. The entry added to the header file's message map can be read as "there is a function called OnAppAbout() that takes no parameters. The entry in the source file's map means "when an ID_APP_ABOUT message arrives, call OnAppAbout()." It shouldn't be a big surprise that the OnAppAbout() member function displays the About box for the application.
But how do message maps really work? Every application has an object that inherits from CWinApp, and has a member function called Run(). That function calls CWinThread::Run(), which is far longer than the simple WinMain() presented earlier, but has the same message loop at its heart: call GetMessage(), call TranslateMessage(), call DispatchMessage(). Almost every window object uses the same old-style windows class, and the same WindProc, called AfxWndProc(). The WindProc, as you've already seen, knows the handle, hWnd, of the window the message is for. MFC keeps something called a handle map, a table of window handles and pointers to objects, and the framework uses this to get a pointer to the C++ object, a CWnd*. Next it calls WindowProc(), a virtual function of that object. Buttons or views might have different WindowProc() implementations, but through the magic of polymorphism, the right function gets called.
NOTE |
Virtual functions and polymorphism are important C++ concepts for anyone working with MFC. They only arise when you are using pointers to objects, and when the class of objects to which the pointers are pointing is derived from another class. Consider as an example a class called CDerived that is derived from a base class called CBase, with a member function called Function() that is declared in the base class and overridden in the derived class. There are now two functions: one has the full name CBase::Function() and the other is CDerived::Function(). If your code has a pointer to a base object, and sets that pointer equal to the address of the derived object, it can then call the function, like this: CDerived derivedobject; In this case, CBase::Function() will be called. But there are times when that is not what you want, when you have to use a CBase pointer but you really want CDerived::Function() to be called. To indicate this, in CBase, Function() is declared to be virtual. Think of it as an instruction to the compiler to override this function if there is any way to do it. Once Function() is declared to be virtual in the base class, CBase, the code fragment above would actually call CDerived::Function() as desired. That's polymorphism, and that shows up again and again in using MFC classes. You use a pointer to a window, a CWnd*, that really points to a CButton or a CView or some other class derived from CWnd, and when a function like WindowProc() is called, it will be the derived function, CButton::WindowProc() for example, that is called. |
WindowProc() calls OnWndMsg(), the C++ function that really handles messages. First, it checks to see if this is a message, a command, or a notification. Assuming it's a message, it looks in the message map for the class, using the member variables and functions that were set up by DECLARE_MESSAGE_MAP, BEGIN_MESSAGE_MAP, and END_MESSAGE_MAP. Part of what those macros arrange is to allow access to the message map entries of the base class by the functions that search the message map of the derived class. That means if a class inherits from CView, and doesn't catch a message normally caught by CView, then that message will still be caught by the same CView function as inherited by the derived class. This message map inheritance parallels the C++ inheritance but is independent of it, and saves a lot of trouble carrying virtual functions around.
Bottom line: you add a message map entry and when a message arrives, the functions called by the hidden message loop look in these tables to decide which of your objects, and which member function of the object, should handle the message. That's what's really going on behind the scenes.
The other great advantage of MFC is that the classes already catch most of the common messages and do the right thing, without any coding on your part at all. For example, you don't need to catch the message that tells you the user has chosen File, Save As-MFC classes catch it, put up the dialog box to get the new filename, handle all the behind-the-scenes work, and finally call one of your functions (which must be named Serialize()-App Wizard typically makes you an empty one to fill in) to actually write out the document. You only need to add message map entries for behavior that is not common to all applications.
Message maps may not be simple to read, but they are simple to create if you use ClassWizard. Figure 5.1 shows the main ClassWizard dialog box, reached by choosing View, ClassWizard or clicking the ClassWizard button on the Standard toolbar (the icon looks like a magic wand trailing stars over a class hierarchy), or by pressing Ctrl+W. ClassWizard is a tabbed dialog box, and Figure 5.1 shows the Message Map tab. At the top of the dialog box are two drop down list boxes, one that reminds you which project you are working on (ShowString in this case) and the other that reminds you which class owns the message map you are editing. In this case, it is the CShowStringApp class, whose message map you have already seen.
Figure 5.1 : ClassWizard makes catching messages simple.
Below those single line boxes are a pair of multi-line boxes. The one on the left lists the class itself and all the commands that the user interface can generate. Commands are discussed in the "Commands" subsection of this section. With the class name highlighted, the box on the right lists all the Windows messages this class might catch. It also lists a number of virtual functions that catch common messages.
To the right of those boxes are buttons where you can add a new class to the project, add a function to the class to catch the highlighted message, remove a function that was catching a message, or open the source code for the function that catches the highlighted message. Typically, you select a class, select a message, and click Add Function to catch the message. Here's what the Add Function button sets in motion:
After you add a function, clicking Edit Code makes it simple to start filling in the behavior of that function. If you prefer, double-click the function name in the Member Functions list box.
Below the Object IDs and Messages boxes is a list of the member functions of this class that are related to messages. This class has two such functions:
The InitInstance function is called whenever an application first starts. You do not need to understand this function to see that ClassWizard reminds you the function has been overridden.
Finally, under the Member Functions box is a reminder of the meaning of the highlighted message. Called to implement wait cursors is a description of the DoWaitCursor virtual function.
The only tricky part of message maps and message handling is deciding which class should catch the message. That's a decision you can't make until you understand all the different message and command targets that make up a typical application. The choice is usually among the following:
Views, documents, and frames are discussed in the next chapter, "The Document/View Paradigm."
There are almost 900 Windows messages, so you won't find a list of them all in this chapter. Usually, you arrange to catch messages with Class Wizard, and are presented with a much shorter list that is appropriate for the class you are catching messages with. Not every kind of window can receive every kind of message. For example, only classes that inherit from CListBox receive list box messages like LB_SETSEL, which directs the list box to move the highlight to a specific list item. The first component of a message name indicates the kind of window this message is destined for, or coming from. These window types are listed in Table 5.1.
Prefix | Window Type |
ABM, ABN | Appbar |
ACM, ACN | Animation Control |
BM, BN | Button |
CB, CBN | Combo Box |
CDM, CDN | Common Dialog |
CPL | Control Panel application |
DBT | Any application (device change message) |
DL | Drag List Box |
DM | Dialog |
EM, EN | Edit box |
FM, FMEVENT | File Manager |
HDM, HDN | Header Control |
HKM | HotKey control |
IMC, IMN | IME window |
LB, LBN | List Box |
LVM, LVN | List View |
NM | Any parent window (notification message) |
PBM | Progress bar |
PBT | Any application (battery power broadcast) |
PSM, PSN | Property Sheet |
SB | Status Bar |
SBM | Scroll Bar |
STM, STN | Static control |
TB, TBN | Tool Bar |
TBM | Track Bar |
TCM, TCN | Tab Control |
TTM, TTN | Tool Tip |
TVM, TVN | Tree View |
UDM | Up Down control |
WM | Generic Window |
What's the difference between, say, a BM message and a BN message? A BM message is a message to a button, such as "act as though you were just clicked." A BN message is a notification from a button to the window that owns it, such as "I was clicked." The same pattern holds for all the prefixes that end with M or N in the preceding table.
Sometimes the "message" prefix does not end with M; for example CB is the prefix for a message to a combo box while CBN is the prefix for a notification from a combo box to the window that owns it. For example, CB_SETCURSEL is a message to a combo box directing it to select one of its strings, while CBN_SELCHANGE is a message sent from a combo box notifying its parent that the user has changed which string is selected.
So what is a command? It is a special type of message. Windows generates a command whenever a user chooses a menu item, clicks a button, or otherwise tells the system to do something. In older versions of Windows, both menu choices and button clicks generated a WM_COMMAND message; these days you get a WM_COMMAND for a menu choice and a WM_NOTIFY for a control notification like button clicking or listbox selecting. Commands and notifications get passed around by the operating system just like any other message until they get into the top of OnWndMsg(). At that point, Windows message passing stops and MFC command routing starts.
Command messages all have, as their first parameter, the resource ID of the menu item that was chosen or the button that was clicked. These resource IDs are assigned according to a standard pattern-for example, the menu item File, Save has the resource ID ID_FILE_SAVE.
Command routing is the mechanism OnWndMsg() uses to send the command (or notification) to objects that can't receive messages. Only objects that inherit from CWnd can receive messages, but all objects that inherit from CCmdTarget, including CWnd and CDocument, can receive commands and notifications. (If the inheritance tree for MFC isn't uppermost in your mind, check Figure 4.2 in Chapter 4 "Overview: What's in an Application? ") That means a class that inherits from CDocument can have a message map. There won't be any entries in it for messages, only for commands and notifications, but it's still called a message map. How on earth do the commands and notifications get to the class, though? By command routing. This gets messy, so if you don't want the inner details, skip this paragraph and the next. OnWndMsg() calls CWnd::OnCommand() or CWnd::OnNotify(). OnCommand() checks all sorts of petty stuff (like whether this menu item was grayed after the user selected it but before this piece of code started to execute) and then calls OnCmdMsg(). OnNotify() checks different conditions and then it, too, calls OnCmdMsg(). OnCmdMsg() is virtual, which means that different command targets have different implementations. The implementation for a frame window sends the command to the views and documents it contains.
That's how something that started out as a message can end up being handled by a member function of an object that is not a window, and therefore can't really catch messages.
Commands can be routed to classes that cannot catch messages.
Do you care about this? Well, a little bit. Even if you don't care how it all happens, you do care that you can arrange for the right class to handle whatever happens within your application. If the user resizes the window, a WM_SIZE message is sent, and you may have to rescale an image or do some other work inside your view. If the user chooses a menu item, a command is generated, and that means your document can handle it if that's more appropriate. You see examples of these decisions at work in the next chapter, "The Document/View Paradigm."
This under-the-hood tour of just how MFC connects user actions like window resizing or menu choices to your code is almost complete. All that's left is to handle the graying of menus and buttons, a process called command updating.
Imagine you are designing an operating system, and you know it's a good idea to have some menu items grayed to show they can't be used right now. There are two ways you can go about implementing this.
One is to have a huge table with one entry for every menu item, and a flag to indicate whether it's available or not. Whenever you have to display the menu, you can quickly check the table. Whenever the program does anything that makes the item available or unavailable, it updates the table. This is called the continuous-update approach.
The other way is not to have a table, but to check all the conditions just before your program displays the menu. This is called the update-on-demand approach and is the approach taken in Windows. In the old C way of doing things-to check whether each menu option should be grayed or not-the system sent a WM_INITMENUPOPUP message, which means "I'm about to display a menu." The giant switch in the WindProc caught that message and quickly enabled or disabled each menu item. This wasn't very object oriented, though. In an object-oriented program, different pieces of information are stored in different objects and are not generally made available to the entire program.
When it comes to updating menus, different objects "know" whether or not each item should be grayed. For example, the document knows whether or not it has been modified since it was last saved, so it can decide whether File, Save should be grayed or not; but, only the view knows whether or not some text is currently highlighted, so it can decide whether Edit, Cut and Edit, Copy should be grayed. This means that the job of updating these menus should be parceled out to various objects within the application rather than handled within the WindProc.
The MFC approach is to use a little object called a CCmdUI, a command user interface, and give this object to whomever catches a CN_UPDATE_COMMAND_UI message. You catch those messages by adding (or getting ClassWizard to add) an ON_UPDATE_COMMAND_UI macro in your message map. If you want to know what's going on behind the scenes, it's this: the operating system still sends WM_INITMENUPOPUP, then the MFC base classes like CFrameWnd take over. They make a CCmdUI, set its member variables to correspond to the first menu item, and call one of that object's own member functions, DoUpdate(). DoUpdate() in turn sends out the CN_COMMAND_UPDATE_UI message with a pointer to itself as the CCmdUI object the handlers use. Then the same CCmdUI object is reset to correspond to the second menu item, and so on, until the entire menu is ready to be displayed. The CCmdUI object is also used to gray and ungray buttons and other controls in a slightly different context. CCmdUI has these member functions:
It's usually pretty straightforward to determine which member function you want to use. Here is a shortened version of the message map from an object called CWhoisView, a class derived from CFormView that is showing information to a user. This form view contains several edit boxes and the user may wish to paste text into one of them. The message map contains an entry to catch the update for the ID_EDIT_PASTE command, like this:
BEGIN_MESSAGE_MAP(CWhoisView, CFormView) ... ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste) ... END_MESSAGE_MAP()
The function that catches the update, OnUpdateEditPaste(), looks like this:
void CWhoisView::OnUpdateEditPaste(CCmdUI* pCmdUI) { pCmdUI->Enable(::IsClipboardFormatAvailable(CF_TEXT)); }
This calls the API function ::IsClipboardFormatAvailable() to see if there is text in the Clipboard. Other applications may be able to paste in images or other non-text Clipboard contents, but this application cannot, and grays the menu item if there is no text available to paste. Most command update functions look just like this: they call Enable() with a parameter that is a call to a function that returns True or False, or perhaps a simple logical expression. Command update handlers must be fast, because 5 to 10 of them must run after the user clicks to display the menu and before the menu is actually displayed.
The ClassWizard dialog box shown in Figure 5.1 has the class name highlighted in the box labeled Object IDs. Below that are resource IDs of every resource (menu, toolbar, dialog box controls, and so on) that can generate a command or message when this object (view, dialog, and so on) is on the screen. If you highlight one of those, the list of messages associated with it is much smaller, as you see in Figure 5.2.
Figure 5.2 : ClassWizard allows you to catch or update commands.
There are only two messages associated with each resource ID: COMMAND and UPDATE_COMMAND_UI. The first allows you to add a function to handle the user selecting the menu option or clicking the button-that is, to catch the command. The second allows you to add a function to set the state of the menu item, button, or other control just as the operating system is about to display it-that is, to update the command.
Clicking Add Function to add a function that catches or updates a command involves an extra step. ClassWizard gives you a chance to change the default function name, as shown in Figure 5.2. This is almost never appropriate. There is a regular pattern to the suggested names, and experienced MFC programmers come to count on function names that follow that pattern. Command handler functions, like message handlers, have names that start with On. Typically, the remainder of the function name is formed by removing the ID and the underscores from the resource ID, and capitalizing each word. Command update handlers have names that start with OnUpdate and use the same conventions for the remainder of the function name. For example, the function that catches ID_APP_EXIT should be called OnAppExit(), and the function that updates ID_APP_EXIT should be called OnUpdateAppExit().
Not every command needs an update handler. The framework does some very nice work graying and ungraying for you automatically. Say you have a menu item, Network, Send, whose command is caught by the document. When there is no open document, this menu item is grayed by the framework, without any coding on your part. For many commands, it's enough that an object that can handle them exists, and no special updating is necessary. For others, you may want to check that something is selected or highlighted, or that no errors are present before making certain commands available. That's when you use command updating. If you'd like to see an example of command updating at work, there's one in Chapter 17, "Building Menus and Dialogs," in the "Command Updating" section.
This chapter has provided the theory of message handling and command routing and a behind-the-scenes look at the ways MFC implements these for you. To see this theory in action, check out the following chapters: