WM_KICKIDLE for Updating MFC Dialog Controls
Daniel Howard
Windows programs often have menu items, toolbar buttons, and dialog controls that should dynamically reflect some program state. For example, many programs provide clipboard cut-and-paste ability. A good user interface will disable the Edit-Paste menu item (or the toolbar button used for pasting) when the clipboard is empty, or enable it when the clipboard receives data. You could add a function call everywhere in your code that you manipulate the clipboard, enabling or disabling all the user interface elements that depend on the clipboard. However, that can make the user interface harder to change and more susceptible to errors that creep in during maintenance. MFC provides an alternative structure, one in which the user interface elements query the appropriate program state as needed to provide an up-to-date view.
You can take advantage of MFCs automatic updating by placing an ON_UPDATE_COMMAND_UI() macro in the message map of the corresponding user interface class (menu, toolbar, etc.). The macro takes two arguments: the ID of the menu item or control that might need updating, and an update handler (which must be a member function in the same class as the command handler). MFC will call your update handler at runtime, passing it a CCmdUI argument that it can use to enable or disable the corresponding menu item or control.
Knowing the power of this technique, I am always looking for new places to apply it. For me, dialog boxes are an obvious first choice. MFC programmers typically implement lots of dialog boxes. Dialog boxes contain many controls, particularly buttons, that need to be enabled and disabled according to the applications status. Using ON_UPDATE_COMMAND_UI() macros to enable and disable buttons in a dialog box would be a much better solution than spreading calls to CWnd::EnableWindow() throughout my code. This article describes how I applied this technique to automatically update dialog controls.
What Doesnt Work
Just putting an ON_UPDATE_COMMAND_UI() macro into the message map for an ordinary dialog box does not work. MFC does not automatically set up this update mechanism for dialog boxes. Even worse, the Visual C++ documentation contains little advice on how to do this for dialog boxes. In Tech Note 21 (TN021), Microsoft says that calling CWnd::UpdateDialogControls() will update the buttons. However, it can only suggest to call this member function directly in the code. Spreading CWnd::UpdateDialogControls() calls throughout the code is as repulsive as spreading CWnd::EnableWindow() calls. Visual C++ does not suggest how to create an update mechanism that will automatically and regularly call CWnd::UpdateDialogControls() so that the controls appear to stay up-to-date.
When I considered implementing such a dialog box update, I looked specifically for an object-oriented solution. If all the code for the update mechanism could be encapsulated into one class, the dialog box update mechanism would be as easy to use in new MFC programs as the one that MFC provides for menu items and toolbars.
WM_KICKIDLE
To write an update mechanism for dialog boxes, I had to go through the MFC source code. Deep in the modal dialog box code for MFC, I found the key: the undocumented MFC message WM_KICKIDLE.
To catch WM_KICKIDLE, add an ON_MESSAGE(WM_KICKIDLE, OnKickIdle) macro invocation to the message map of your dialog, and supply an OnKickIdle() member function. At some point, a WM_KICKIDLE message gets sent to that dialog and your OnKickIdle() member function gets called. Your function should return TRUE to signal the caller to emit another WM_KICKIDLE message, or FALSE to signal the caller to enter the idle state. In the latter case, no more WM_KICKIDLE messages will be sent until a new message arrives.
The basic idea is that MFC sends a WM_KICKIDLE to your dialog window when it looks like there are no more messages to process, meaning there is probably nothing much else going on. You can then perform lower-priority tasks from your OnKickIdle(). If the tasks you want to perform at idle time consume significant time, you may want to break them up into pieces and keep returning TRUE to indicate you want to get another WM_KICKIDLE message if there are still no incoming messages. That helps ensure that your idle-time processing will not keep the application from responding to new events (such as mouse/keyboard input, painting events, etc.). CDialog::DoModal() calls CWND::RunModalLoop(), which sends the WM_KICKIDLE messages at appropriate times.
WM_KICKIDLE has been around since Visual C++ 4.0 and is still supported in Visual C++ 5.0. Since this message is undocumented, your source code must include afxpriv.h in order to use it.
Using WM_KICKIDLE for Updating
idledlg.h (Listing 1) and idledlg.cpp (Listing 2) contain the source code for a reusable class called CIdleDlg that implements the dialog box update mechanism. CIdleDlg is derived from CDialog so it is a plug-in replacement for it. By deriving from CIdleDlg instead of CDialog, any dialog box in any MFC program can use ON_UPDATE_COMMAND_UI() macros for buttons in the dialog without any extra code.
With WM_KICKIDLE, implementing the dialog box update mechanism is trivial. Placing an ON_MESSAGE(WM_KICKIDLE, OnKickIdle) macro in the CIdleDlg message map sets CIdleDlg::OnKickIdle() as the handler for WM_KICKIDLE messages. CDialog sends this message to itself right before it goes idle. CIdleDlg will catch the WM_KICKIDLE and update the dialog buttons status before the dialog goes completely into a waiting state. Since dialogs go into a waiting state often (several times per second even if the user is using the dialog), the general update mechanism will be very effective even though it is only activated in response to this one message.
The message handler, CIdleDlg::OnKickIdle(), looks like this:
LRESULT CIdleDlg::OnKickIdle( WPARAM /*wParam*/, LPARAM /*lParam*/) { UpdateDialogControls(this, TRUE); return FALSE; }
Calling UpdateDialogControls(this, TRUE) enables and disables the buttons in the dialog, depending on the implementation of the corresponding ON_UPDATE_COMMAND_UI() handlers in the message map. The second parameter is set to TRUE to disable any buttons that do not have a corresponding ON_COMMAND() macro. Changing this parameter to FALSE is harmless; buttons without ON_COMMAND() handlers would be enabled instead of disabled.
Returning FALSE from the handler tells the dialog that it should not send itself any more WM_KICKIDLE messages until it becomes active again. If TRUE were returned, the dialog box would send itself another WM_KICKIDLE message unless a new message had appeared in its event queue.
General Idle Processing
You can also use WM_KICKIDLE for general idle-time processing. Idle processing or idle code is any action or calculation that a program executes only when it is waiting for user input. Normally, MFC programs implement idle processing by overriding CWinApp::OnIdle() or using a custom message processing loop, but CWinApp::OnIdle() can be inconvenient to use in a dialog, and writing a custom message processing loop is not an easy task. On the other hand, handling WM_KICKIDLE is much easier and requires a lot less code. Catching WM_KICKIDLE and putting idle processing code in its handler can be a much cleaner and simpler way of implementing the same thing in a dialog box.
There is only one gotcha to handling WM_KICKIDLE and using it to execute some code before a dialog goes idle. To protect against it, you must ensure that your code eventually allows the dialog box to become idle. If the dialog box never becomes idle, the program will absorb all the CPU time that it can get (even though it has completed its idle processing) and slow down any other programs on the computer. Usually, this problem occurs because the code executing right before the idle causes the dialog box to become active again. The dialog box gets stuck in a loop, making work for itself, continually flip-flopping between active and idle as the idle code reactivates the dialog box. A dialog box nearly always becomes active when a new message appears in its event queue.
You can test your dialog box for this problem by looking at the CPU usage monitor that comes with your version of Windows. In Windows 95, you can check the CPU usage by using the System Monitor tool to show the Kernel: Processor Usage chart. In Windows NT, you can check the CPU usage by looking at the CPU Usage gauge and CPU Usage History chart under the Performance tab in the Task Manager. If the CPU usage zooms up and stays at 100 percent while the program is running, the program is trying to monopolize the CPU and is not being a good Windows citizen.
There are two common causes for this problem. If the WM_KICKIDLE handler always returns TRUE, the program is signaling MFC to keep sending WM_KICKIDLE messages. An endless and continuous stream of messages, even WM_KICKIDLE messages, will keep a dialog active. At some point, the program should return FALSE. When WM_KICKIDLE returns FALSE, the dialog box will go idle and stop using CPU time until it gets a new message. Make sure your handler returns FALSE at some point to avoid this problem.
If the CPU usage is still stuck at 100 percent when the dialog box is running, the WM_KICKIDLE handler probably sends or posts a message to itself (either directly or indirectly via SendMessage() or PostMessage()). Removing and replacing this message code with equivalent code that does not use messages will allow the dialog box to become idle and stop dragging down the other programs.
Conclusion
The code archive contains a program that demonstrates the use of WM_KICKIDLE, CIdleDlg, and the other concepts explained in this article. By watching the behavior of the dialog, it is easy to see how often and when the update mechanism is called. By handling WM_KICKIDLE, CIdleDialog neatly encapsulates all the code necessary to enable and disable buttons in a dialog. It is an easily understood, object-oriented, and nearly ideal solution for reducing the amount of code needed to program a dialog box in MFC. Also, knowing about WM_KICKIDLE lets you implement any kind of idle-time processing in a dialog box without resorting to the awkwardness and inefficiency of using CWinApp::OnIdle() or implementing a custom message processing loop.
Considering the number of dialog boxes in the typical MFC program, saving even a small amount of time in coding each one can add up to significant time savings at the end of the development. The undocumented MFC message WM_KICKIDLE can be the key to freeing up some of that time. Knowing the pace of most development projects, I am sure that you can figure out plenty of uses for it.
Daniel Howard is a software engineer at Macromedia in Redwood City, CA. His interests include graphics, multimedia, and user interface design. He works on Authorware, the premier interactive multimedia tool for creating Web sites, computer-based training, kiosks, and on-line learning.
Get Source Code