Back to TOC Windows Programming


C++ Classes for MDI Window Management

Steve Welstead

The Windows multiple document interface may appear complicated, but it is easily managed by this set of C++ classes.


Introduction

The multiple document interface (MDI) is a familiar feature of most commercial Windows applications, including Microsoft Word and the code editors used by Microsoft Visual C++, Borland C++, and Symantec C++. It is natural for the Windows programmer to want to include this interface in his or her own Windows application. Yet Windows programming books usually relegate examples of MDI documentation to the back pages, as if to imply the effort required is beyond the reach of all but the most advanced programmers.

This article shows how to set up some simple C++ classes that will make it easy to use the MDI style in your own applications. One of the advantages of the approach presented here is that, unlike some other Windows C++ class libraries, it encourages you to mix different types of MDI child windows, each with its own menu structure, within a single MDI frame. I do not use MFC or OWL classes. I have compiled and run the code presented here with several different Borland compilers (3.1, 4.5, and 5.0) as well as Symantec C++ 7.2. The code produces either 16-bit or 32-bit executables, and you can compile it to run under Windows 3.1, Windows 95, or Windows NT.

As Figure 1 shows, a typical MDI application consists of a frame window, a client window, and multiple child windows. The frame window bounds the screen area where the child windows may appear, and also controls the menus. The client window consists of the screen area where the child windows appear, and also controls the positioning and appearance of the child windows. The child windows themselves typically do most of the work associated with the application.

Most of the behavior associated with MDI applications, such as tiling and cascading of child windows, is already built into a special MDI client window, predefined by Windows under the class name "MDICLIENT" (Windows "class," not C++ class!). This means you don't need to register this window, nor do you need to define a callback procedure for it. You get free of charge all of the behavior you would expect from a standard MDI application. It is possible to modify or augment the client window behavior, for example, to change the tiling scheme or to capture toolbar commands, through a technique called Windows "subclassing." However, for the purposes of this article, focusing on the standard MDI behavior is sufficient. In fact, I don't even need to define a C++ class for the client window.

In this article I describe how to build a small framework of C++ classes to handle the design of MDI applications. I develop a basic window class, and then derive descendant classes for the MDI frame and MDI child window management. The frame accesses a linked list structure that accommodates any number of different child window types. I illustrate the use of this frame with two different child window types, each with its own menu.

Basic Window Class

The C++ class twindow, defined in Listing 1, is the basic window class from which I derive all other windows including MDI frame windows and child windows. This class takes care of the routine tasks and behavior common to all windows. It handles window registration, and sets up the structure for responding to Windows messages. Virtual member functions respond to each command. Descendant window classes define specific functionality by overriding these virtual functions. The wonders of C++ inheritance ensure that there is no need to repeat the messaging response structure in descendant classes. You need only redefine those responses which define behavior unique to a particular descendant class.

Windows defines the structure type WNDCLASS, which contains parameters that control the window appearance and behavior. Windows reads this structure when the program calls the Windows API function RegisterClass. lpfnWndProc is a key element of this structure; it is a pointer to the procedure that defines the window behavior, that is, how the window responds to messages. In descendant classes this pointer will point to the handle_message member function of a particular instantiation of that class. The need to single out a specific object explains why this class defines a member function set_global_ptr. This function sets the particular class instantiation.

Note that twindow includes a number of respond_wm_... member functions. These functions respond to Windows messages. I have included here most of the messages commonly encountered by every window. You can, of course, add other messages (for example, scroll bar messages) in descendant classes. Here, these functions act only as placeholders. The handle_message member function terminates its operation if any of these functions responding to Windows messages returns other than zero. This prevents Windows from doing its default processing of that particular message. Note, though, that the return value of handle_message is zero. This ensures that Windows will continue with its normal processing of other messages such as mouse movements. You generally don't want to return a nonzero value from a Windows CALLBACK function, since you will probably lose some standard window behavior and get unpredictable results.

MDI Frame Window

The frame window interacts with the user through menu commands, and generally runs the show in an MDI application. It displays the menus and passes out user commands to the client window, which either responds directly to the command or sends it along to the appropriate child window. In this section, I develop a C++ class for a basic MDI frame window.

Listing 2 contains the header declarations for the MDI frame window classes. To facilitate handling a number of different child window types with a single frame window class, I define a linked list structure, called tmdi_type_list_struct. This structure ties a menu command, type_id, to a window class, the_class. When the frame window receives a menu command, it first cycles through its linked list of child types to see if the command matches any of these type IDs. If there is a match, the frame creates a new child window of that type. This linked list enables handling any number of child window types without having to create a new frame class for each application. The need to derive a new frame class arises only when the application requires some action other than opening a child window, for example, when the frame window must handle dialog input from the user.

Listing 3 shows the code for the tmdi_frame_window class member functions. tmdi_frame_window is a direct descendant of the base class twindow. Note the use of the global pointer the_frame_window, and the external function FrameWndProc, which uses this pointer. FrameWndProc is the frame window callback function. Callback functions define how particular windows respond to messages. The function FrameWndProc simply implements a particular instantiation of the handle_message member function. The member function set_global_ptr sets the value of the_frame_window pointer to the object's this pointer. C++ won't allow a member function address to be passed as an argument to another function, since this address is not known until a particular class object is instantiated at run time. This is why I use an external procedure and a global pointer to a particular class instantiation.

The frame window responds to the Windows WM_CREATE message with the function respond_wm_create, which creates the MDI client window. The frame window then optionally displays an "About" dialog box. For the example in this article, this dialog is simply a Windows Message Box. For your own applications, you can use a resource editor to make this a fancy dialog with embedded bitmaps and other embellishments to advertise your product.

User-selected menu commands go to the respond_wm_command member function. respond_wm_command checks the child window types against the tmdi_type_list_struct linked list. A match initiates the creation of a MDI child window of the appropriate class. If there is no match, the frame continues checking for other common commands, including the commands that carry out pre-defined MDI behavior, such as tiling and cascading of child windows. I accomplish this basic MDI behavior merely by sending the appropriate message to the client window. The command structure defined here is general enough to handle most MDI applications. You can accommodate specific needs, such as a frame that communicates with the user through dialog boxes, through derived frame classes.

MDI Child Window Manager

As I mentioned in the introduction, I don't define a C++ class for the MDI client window, since Windows supplies all of the functionality for this window that I need. I do, however, define a class for managing MDI child windows, which parallels some of the client window's behavior. Windows keeps track of the active MDI child windows through a list of HWND identifiers. As child windows are created and destroyed, Windows adds or deletes HWND identifiers so that this list correctly reflects the current windows that are open in the frame window. Since I represent each MDI child window with a corresponding C++ class, I need to maintain a separate list of pointers to active C++ MDI child window class objects. The tmdi_manager class, whose definition appears in Listing 4, maintains this list and takes care of routing Windows messages to the active child window.

The structure type child_window_struct simply ties a Windows HWND identifier to a pointer to a tmdi_child_window object. I maintain the list of child windows via window_list, a pointer to an object_list class. In a previous article [1] , I discuss this object_list type. object_list is simply a class for handling an array of object pointers. Complete code for this class is included with the example available from the CUJ code disk and online sources (see p. 3). object_list's class construction requires a maximum array size, which I set here to the constant MAX_NO_OF_ACTIVE_WINDOWS, defined here with a value of 20. This value determines the maximum number of each type of child window (not the total number of child windows) that can be open at one time. The value itself is arbitrary, and you can increase it for your own applications.

Listing 5 shows the code for the tmdi_manager and tmdi_child_window member functions. Note that tmdi_manager has a virtual function new_child_window that creates a new tmdi_child_window. To create child windows of a specific type, you need to derive a descendant MDI manager class that overrides this virtual function with a version that creates the desired child window type. Normally, new_child_window and set_global_ptr are the only member functions that you need to override in descendant manager classes.

The tmdi_manager member function handle_message processes WM_CREATE and WM_DESTROY messages itself, so that it can keep its active window list updated. tmdi_manager sends messages relevant to the child windows, such as paint and mouse movement messages, to the active child window. All other messages get default processing.

The member functions init_menu and set_frame_menu set up the menu and submenus for the child window, and their relationship to the frame window menu. WinMain calls these functions once, just after window registration. The identifier menu_rc_name is the string name for the window's menu as it appears in the resource file. The parameter window_submenu_pos tells Windows where to place the list of open MDI child windows. The appearance of this list in the menu is another benefit you get free of charge from Windows MDI management.

The tmdi_child_window class is a base class for specific child window types. It encapsulates behavior common to all MDI child windows. The most significant behavior it encapsulates is that of switching the frame window menus whenever a new child window is activated. Member function respond_wm_mdiactivate accomplishes this switching, in response to a WM_MDIACTIVATE message. Note the use of the two macros ACTIVATE_MDI_CHILD_WINDOW and MDI_SETMENU_MSGPARAMS here. These macros are defined in Listing 4, and they provide examples of how certain parameters are handled differently in 16-bit Windows versus 32-bit Windows. Macros such as these allow you to use one set of source code that can be compiled for either 16- or 32-bit environments.

Before closing a child window, the frame window sends a query message to the child window. For example, if the child window is an editor window, the user may have some information to save prior to closing the window. The standard response to the frame window's "query end session" processing is to display a message box to the user asking if it is okay to close the window. This message box appears in the tmdi_child_window member function respond_wm_close. You can override this query response by having this function just return zero.

An Example

The example in this section shows how to use these C++ MDI classes to build a complete application that creates multiple copies of two different MDI child window types. When you run this example, you should see an MDI window system similar to what appears in Figure 1, which shows a Windows 95 implementation. Complete source code for this example is available from CUJ code disk and online sources (see p. 3).

The purpose of this example is to illustrate the interaction among the frame window, the menus, the client window, and the child windows. For this reason, I've kept the child window behavior simple. For a more substantial example of MDI child window behavior, see Petzold's book [2] . In our example, the two types of child windows respond to a "Run" command by displaying a message box indicating the window type. Of course, this example exhibits all of the standard MDI behavior, including tiling and cascading of child windows, as well as menu switching.

Listing 6 shows the class definition for one of the child windows, ttest1_window. The second child window type, ttest2_window, has a similar definition, and is not listed here. For each child window type, you will need a corresponding window manager type. At a minimum, this descendant of tmdi_manager needs to have its own constructor, and also its own set_global_ptr and new_child_window member functions.

The ttest1_window class needs to be able to recognize the "Run" command, and then take appropriate action in response to this command. The respond_wm_command member function overrides its ancestor in tmdi_child_window by looking for this "Run" command first, and then passing control to tmdi_child_window's processing of commands. The commands themselves are constants, which also appear in the menu resource.

Note that the respond_wm_close function returns zero here, so that no message box appears when closing this window. You can contrast this behavior with that of ttest2_window, which, upon receiving a message to close, displays a message box asking if it is okay to do so.

The final step in building an application is to incorporate these C++ MDI classes into a WinMain function. Listing 7 shows how to modify the standard WinMain function to use these classes. The most obvious modification is the presence of the linked list structure child_list, which stores the different child window types. The program initializes this list with string constants to identify the different window classes and titles. These constants are arbitrary and need only be different enough to uniquely identify each window type. The constants MDI_OPEN_1 and MDI_OPEN_2 refer to the menu commands to which the frame window responds when opening the different window types. These constants supply the type_id values that tmdi_frame_window checks with its respond_wm_command member function.

Listing 7 also shows the calls to the constructors for the frame window and the two child window managers. With this code you can register a window with a single function call for each window class, rather than having to fill a large structure for each class. Each window class then initializes its menus, with calls to init_menu and, for the child windows, set_frame_menu. The menu strings, such as "MainMenu" and "Test1Menu," come from the resource file testmenu.rc. The menu position constants appear in the header file testrids.h. Both of these files are available from CUJ electronically. The remainder of the WinMain code contains the standard Windows message loop. When this loop terminates, I call the destructors for the window manager classes and the frame window class, as well as a function that frees the child window type linked list. As always with Windows programs, you must be diligent about deleting what you create, otherwise your mistakes may live on to haunt you.

Summary

I've developed some C++ classes that should help you incorporate the MDI environment into your own Windows applications. You can build your own applications with multiple MDI child window types merely by extending the child window classes defined here. By using the C++ class approach, you can deal with many of the details of MDI implementation once and for all in the base classes. Then, when developing applications, you are free to focus on what is unique to each application. o

References

[1] Stephen Welstead. "Data Object List Dialog for Windows," C/C++ Users Journal, Vol. 13, No. 9, September, 1995, pages 23-41.

[2] Charles Petzold. "Programming Windows 3.1'' (Microsft Press, 1992). This book was the starting point for my Windows programming career, and it remains today the reference I use most often. This excellent book has recently been updated to "Programming Windows 95'' (Microsoft Press, 1996).

Steve Welstead is staff scientist with COLSA Corporation in Huntsville, Alabama, and an adjunct associate professor of mathematics at the University of Alabama in Huntsville. He holds a Ph.D. degree in applied mathematics from Purdue University. He does all of his own C/C++ programming for research investigations in neural networks, fuzzy logic, chaotic dynamics, and signal processing. He has contributed two previous articles to C/C++ Users Journal, and is the author of Neural Networks and Fuzzy Logic Applications in C/C++, published by John Wiley & Sons, Inc., in 1994. He is currently working on a book on fractal imaging in C/C++ for Windows.