Borland®
Shop
Products Downloads Services Support Partners News & Events Company Community
Borland C++
Books
Awards
Documentation

Turbo Assembler
Resource Workshop

Borland C++ Support
Pre-Sales/Install Support

C++Builder

 Teach Yourself Borland C++ 4.5 in 21 Days, Second Edition, Chapter 21

Chapter 21

MDI Windows
The Multiple Document Interface (MDI) is a standard Windows interface used by many popular Windows applications and utilities, such as the Windows Program Manager, the Windows File Manager, and even the Borland C++ IDE. The MDI interface is also part of the Common User Access (CUA) standard set by IBM. Each MDI-compliant application enables you to open child windows for file-specific tasks such as editing text, managing a database, or working with a spreadsheet. In this chapter, you will learn the following topics on managing MDI windows and objects:

  • The basic features and components of an MDI-compliant application
  • Basics of building an MDI-compliant application
  • The class TMDIFrame
  • The class TMDIClient
  • Building MDI client windows
  • The class TMDIChild
  • Building MDI child windows
  • Managing messages in an MDI-compliant application

The MDI Application Features and Components
An MDI-compliant application is made up of the following objects:

  • The visible MDI frame window that contains all other MDI objects. The MDI frame window is an instance of the class TMDIFrame or its descendants. Each MDI application has one MDI frame window.
  • The invisible MDI client window that performs underlying management of the MDI child windows that are dynamically created and removed. The MDI client window is an instance of the class TMDIClient. Each MDI application has one MDI client window.
  • The dynamic and visible MDI child window. An MDI application dynamically creates and removes multiple instances of MDI child windows. An MDI child window is an instance of TMDIChild or its descendant. These windows are located, moved, resized, maximized, and minimized inside the area defined by the MDI frame window. At any given time (and while there is at least one MDI child window), there is only one active MDI child window.

When you maximize an MDI child window, it occupies the area defined by the client area of the MDI frame window. When you minimize an MDI child window, the icon of that window appears at the bottom area of the MDI frame window.


Note: The MDI frame window has a menu that manipulates the MDI child windows and their contents. The MDI child windows cannot have a menu, but they may contain controls. In any other respect, you can think of an MDI child window as an instance of TFrameWindow or its descendants.


Basics of Building an MDI Application
Before we discuss in more detail the creation of the various components that make up an MDI application, let's focus on the basic strategy involved. In the last section, you learned that the basic ingredients for an MDI application are the TMDIFrame, TMDIClient, and TMDIChild (or a TMDIChild descendant) classes. The TMDIFrame class supports the following tasks:

  • The creation and handling of the MDI client window
  • The creation and handling of the MDI child windows
  • Managing menus

The TMDIClient class focuses on the underlying management of MDI child windows. The TMDIChild class offers the functionality for the MDI child windows.

At this stage you might ask, Do I typically derive descendants for all three classes to create MDI applications? The answer is no. You normally need to derive descendants only for the TMDIFrame and TMDIChild classes. The functionality of the TMDIClient class is adequate for most MDI-compliant applications.

The TMDIFrame Class
ObjectWindows offers the TMDIFrame class, a descendant of TFrameWindow, to implement the MDI frame window of an MDI application. The declaration of the TMDIFrame class is as follows:

class _OWLCLASS TMDIFrame : virtual public TFrameWindow {
  public:
    TMDIFrame(const char far* title,
              TResId          menuResId,
              TMDIClient&     clientWnd = *new TMDIClient,
              TModule*        module = 0);


    TMDIFrame(HWND hWindow, HWND clientHWnd, TModule* module = 0);

    //
    // override virtual functions defined by TFrameWindow
    //
    BOOL         SetMenu(HMENU);
    TMDIClient*  GetClientWindow();

    //
    // find & return the child menu of an MDI frame's (or anyone's) menu
    // bar.
    //
    static HMENU FindChildMenu(HMENU);

  protected:
    //
    // call ::DefFrameProc() instead of ::DefWindowProc()
    //
    LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam);

  private:
    //
    // hidden to prevent accidental copying or assignment
    //
    TMDIFrame(const TMDIFrame&);
    TMDIFrame& operator=(const TMDIFrame&);

  DECLARE_RESPONSE_TABLE(TMDIFrame);
  DECLARE_STREAMABLE(_OWLCLASS, TMDIFrame, 1);
};

The TMDIFrame class has public, protected, and private members. The MDI frame window class has three constructors, one of which is private. The first constructor creates a class instance by specifying the title, associated menu resource ID, and reference to the associated MDI client window. The second constructor creates a class instance from an existing non-OWL window. The third constructor, which is declared private, creates an instance of class TMDIFrame using another existing instance.

The class TMDIFrame declares the public member functions SetMenu, GetClientWindow, and FindChildMenu. The function SetMenu looks for the MDI submenu in the new menu bar and updates member ChildMenuPos if the menu is found. The function searches for the MDI submenu in the menu bar and updates the position in the MDI window's top-level menu of the child window submenu. The function GetClientWindow returns a pointer to the associated MDI client window. The function FindChildMenu searches for the child menu of an MDI frame's menu bar.

The class TMDIFrame declares the single protected member function DefWindowProc. This function overrides the inherited function TWindow::DefWindowProc and invokes the Windows API function DefFrameProc. The API function provides the default processing for any incoming Windows message that is not handled by the MDI frame window.

Building MDI Frame Windows
The usual approach for creating the objects that make up an ObjectWindows application starts with creating the application instance and then its main window instance. In the case of an MDI-compliant application, the application's main window is typically a descendant of class TMDIFrame. The InitMainWindow member function of the application class creates this window. Looking at the first two TMDIFrame constructors, you can tell that creating the main MDI window involves a title and menu resource--there is no pointer to a parent window because MDI frame windows have no parent windows. The MDI frame window, unlike most descendants of class TWindow, must have a menu associated with it. This menu typically includes the items shown in Table 21.1, needed to manipulate the MDI children. In addition, the menu of the MDI frame window is dynamically and automatically updated to include the current MDI children.

The constructor of the descendant of TMDIFrame (call it the application frame class) can, in many cases, simply invoke the parent class constructor. This invocation occurs if the steps taken by the parent class are adequate for creating the MDI frame window instance. In the case where you want to modify the behavior of the application frame class, you need to include the required statements. Such statements might assign initial values to data members declared in the application frame class.

The SetupWindow member function invokes the InitClientWindow to create the TMDIClient instance. You can modify the SetupWindow function to, for example, automatically create the first child MDI window.

The TMDIClient Class
ObjectWindows offers the TMDIClient class, a descendant of TWindow, to implement the invisible MDI client window. The declaration of the TMDIClient class is as follows:

class _OWLCLASS TMDIClient : public virtual TWindow {
  public:
    LPCLIENTCREATESTRUCT  ClientAttr;

    TMDIClient(TModule* module = 0);
   ~TMDIClient();

    virtual BOOL CloseChildren();

    TMDIChild* GetActiveMDIChild();

    //
    // member functions to arrange the MDI children
    //
    virtual void ArrangeIcons();
    virtual void CascadeChildren();
    virtual void TileChildren(int tile = MDITILE_VERTICAL);

    //
    // override member functions defined by TWindow
    //
    BOOL PreProcessMsg(MSG& msg);
    BOOL Create();

    virtual TWindow* CreateChild();

    //
    // constructs a new MDI child window object. By default, constructs
    // an instance of TWindow as an MDI child window object
    //
    // will almost always be overridden by derived classes to construct
    // an instance of a user-defined TWindow derived class as an MDI
    // child window object
    //
    virtual TMDIChild* InitChild();

  protected:
    char far* GetClassName();

    //
    // menu command handlers & enabler
    //
    void CmCreateChild()
           { CreateChild(); }  // CM_CREATECHILD
    void CmTileChildren()
           { TileChildren(); }  // CM_TILECHILDREN
    void CmTileChildrenHoriz()
           { TileChildren(MDITILE_HORIZONTAL); }  // CM_TILECHILDREN
    void CmCascadeChildren()
           { CascadeChildren(); }  // CM_CASCADECHILDREN
    void CmArrangeIcons()
           { ArrangeIcons(); }  // CM_ARRANGEICONS
    void CmCloseChildren()
           { CloseChildren(); }  // CM_CLOSECHILDREN
    void CmChildActionEnable(TCommandEnabler& commandEnabler);

    LRESULT EvMDICreate(MDICREATESTRUCT far& createStruct);

  private:
    friend class TMDIFrame;
    TMDIClient(HWND hWnd, TModule*   module = 0);

    //
    // hidden to prevent accidental copying or assignment
    //
    TMDIClient(const TMDIClient&);
    TMDIClient& operator =(const TMDIClient&);

  DECLARE_RESPONSE_TABLE(TMDIClient);
  DECLARE_STREAMABLE(_OWLCLASS, TMDIClient, 1);
};

The class TMDIClient declares a public constructor and destructor. The MDI client class declares a number of member functions that handle Windows and menu command messages for activating an MDI child window; arranging the MDI child icons; cascading and tiling MDI children; closing MDI children; and creating an MDI child window. These message response functions use sibling member functions. Table 21.1 shows the predefined menu ID constants and the TMDIClient member functions that respond to them.

Table 21.1. The predefined menu command messages for manipulating MDI children.
		Responding TMDIClient Action	Menu ID Constant	Member Function
Tile	CM_TILECHILDREN	CmTileChildren
Tile Horizon	CM_TILECHILDRENHORIZ	CmTileChildrenHoriz
Cascade	CM_CASCASDECHILDREN	CmCascadeChildren
Arrange Icons	CM_ARRANGEICONS	CmArrangeIcons
Close All	CM_CLOSECHILDREN	CmCloseChildren

There are a number of member functions in the class TMDIClient that you may want to modify when you create class descendants. The list of such member functions includes CreateChild, SetupWindow, CanClose, and CloseChildren. These functions enable you to modify how to create, set up, and close MDI children.

The MDI Child Window Class
The class TMDIChild models the basic operations of all MDI child windows. The declaration for the class TMDIChild is as follows:

class _OWLCLASS TMDIChild : virtual public TFrameWindow {
  public:
    TMDIChild(TMDIClient&     parent,
              const char far* title = 0,
              TWindow*        clientWnd = 0,
              BOOL            shrinkToClient = FALSE,
              TModule*        module = 0);

    TMDIChild(HWND hWnd, TModule* module = 0);

   ~TMDIChild() {}

    //
    // override method defined by TWindow
    //
    BOOL PreProcessMsg(MSG& msg);

  protected:
    void Destroy(int retVal = 0);
    void PerformCreate(int menuOrId);
    LRESULT DefWindowProc(UINT msg, WPARAM wParam, LPARAM lParam);
    void EvMDIActivate(HWND hWndActivated,
                       HWND hWndDeactivated);

  private:
    //
    // hidden to prevent accidental copying or assignment
    //
    TMDIChild(const TMDIChild&);
    TMDIChild& operator =(const TMDIChild&);

  DECLARE_RESPONSE_TABLE(TMDIChild);
  DECLARE_STREAMABLE(_OWLCLASS, TMDIChild, 1);
};

The class TMDIChild declares three constructors (one of which is private) and a destructor. The first constructor enables you to create a class instance by specifying the parent MDI client window, MDI child window title, the client window, and whether or not the MDI child window shrinks to fit the client area. The second constructor creates a class instance using an existing non-OWL MDI child window. The third constructor, which is declared private, creates a TMDIChild class instance using an existing instance.

The MDI child window class declares the single public member function PreProcessMsg. This function preprocesses the Windows messages sent to the MDI child windows. The class TMDIChild offers a set of protected functions that create, destroy, and activate MDI child windows. In addition, the class provides its own version of function DefWindowProc to handle default Windows message processing.

Building MDI Child Windows
Building MDI child windows is very similar to building application windows in the programs presented earlier. The differences are as follows:

  • An MDI child window cannot have its own menu. The menu of the MDI frame window is the one that manipulates the currently active MDI child window or all of the MDI children.

Note: The keyboard handler must not be enabled. It actually causes the reverse effect in the MDI children and antagonizes the proper operations of the MDI application.


  • An MDI child window can have controls--this is unusual but certainly allowed.

Managing MDI Messages
The message loop directs the command messages first to the active MDI child window to allow it to respond. If that window does not respond, the message is then sent to the parent MDI frame window. Of course, the active MDI child window responds to the notification messages sent by its controls, just as any window or dialog box would.

SimpleText Viewer
Let's look at a simple MDI-compliant application. Because MDI applications are frequently used as text viewer and text editors, we present the next application that emulates a simple text viewer. We say "emulates" because the application actually displays random text, instead of text that you can retrieve from a file. This approach keeps the program simple and helps you to focus on implementing the various MDI objects. Figure 21.1 shows a sample session with the MDI1.EXE program. The MDI application has a simple menu containing the Exit and MDI Children items.

Compile and run the application. Experiment with creating MDI children. Notice that the text in odd-numbered MDI child windows is static, whereas the text in even-numbered windows can be edited. We implemented this feature to illustrate how to create a simple form of text viewer and text editor (with no Save option, to keep the example short). Try to tile, cascade, maximize, and minimize these windows. Also test closing individual MDI child windows as well as closing all of the MDI children.

Let's examine the code that implements this simple MDI application. Listing 21.1 shows the contents of the MDI1.DEF definition file. Listing 21.2 shows the source code for the MDI1.H header file. This file declares the command message constants and a control ID constant. Listing 21.3 contains the script for the MDI1.RC resource file. The file defines the menu resource required by the MDI frame window. The menu has two menu items, Exit and MDI Children. The latter menu item is a pop-up menu with several options. The commands, except the option Count Children, use predefined command message constants. Listing 21.4 shows the source code for the MDI1.CPP program file.

Figure 21.1. A sample session with the MDI1.EXE program.

	Listing 21.1. The contents of the MDI1.DEF  definition file.
  1:  NAME         MDI1
  2:  DESCRIPTION  `An OWL Windows Application'
  3:  EXETYPE      WINDOWS
  4:  CODE         PRELOAD MOVEABLE DISCARDABLE
  5:  DATA         PRELOAD MOVEABLE MULTIPLE
  6:  HEAPSIZE     1024
7: STACKSIZE 8192
	Listing 21.2. The source code for the MDI1.H  header file.
  1:  #define CM_COUNTCHILDREN 101
  2:  #define ID_TEXT_EDIT     102
3: #define IDM_COMMANDS 400
  Listing 21.3. The script for the MDI1.RC resource file.
  1:   #include <windows.h>
  2:   #include <owl\window.rh>
  3:   #include <owl\mdi.rh>
  4:   #include "mdi1.h"
  5:   IDM_COMMANDS MENU LOADONCALL MOVEABLE PURE DISCARDABLE
  6:   BEGIN
  7:     MENUITEM "E&xit", CM_EXIT
  8:     POPUP "&MDI Children"
  9:     BEGIN
 10:      MENUITEM  " c&reate", CM_CREATECHILD
 11:      MENUITEM  "&Cascade", CM_CASCADECHILDREN
 12:      MENUITEM  "&Tile", CM_TILECHILDREN
 13:      MENUITEM  "Arrange &Icons", CM_ARRANGEICONS
 14:      MENUITEM  " c&lose All", CM_CLOSECHILDREN
 15:      MENUITEM  " c&ount Children", CM_COUNTCHILDREN
 16:    END
17: END
	Listing 21.4. The source code for the MDI1.CPP program file.
  1:   /*
  2:     Program to illustrate simple MDI windows
  3:   */
  4:   #include <owl\mdi.rh>
  5:   #include <owl\applicat.h>
  6:   #include <owl\framewin.h>
  7:   #include <owl\mdi.h>
  8:   #include <owl\static.h>
  9:   #include <owl\edit.h>
 10:  #include <owl\scroller.h>
 11:  #include "mdi1.h"
 12:  #include <stdio.h>
 13:  #include <string.h>
 14:
 15:  const MaxWords = 100;
 16:  const WordsPerLine = 12;
 17:  const NumWords = 10;
 18:  char* Words[NumWords] = { "The ", "friend ", "saw ", "the ",
 19:                  "girl ", "drink ", "milk ", "boy ",
 20:                  " cake ", "bread " };
 21:
 22:  BOOL ExpressClose = FALSE;
 23:  int NumMDIChild = 0;
 24:  int HighMDIindex = 0;
 25:
 26:  class TWinApp : public TApplication
 27:  {
 28:  public:
 29:    TWinApp() : TApplication() {}
 30:
 31:  protected:
 32:    virtual void InitMainWindow();
 33:  };
 34:
 35:  class TAppMDIChild : public TMDIChild
 36:  {
 37:  public:
 38:    // pointer to the edit box control
 39:    TEdit* TextBox;
 40:    TStatic* TextTxt;
 41:
 42:    TAppMDIChild(TMDIClient& parent, int ChildNum);
 43:
 44:  protected:
 45:
 46:    // handle closing the MDI child window
 47:    virtual BOOL CanClose();
 48:  };
 49:
 50:  class TAppMDIClient : public TMDIClient
 51:  {
 52:  public:
 53:
 54:    TAppMDIClient() : TMDIClient() {}
 55:
 56:   protected:
 57:
 58:    // create a new child
 59:    virtual TMDIChild* InitChild();
 60:
 61:    // close all MDI children
 62:    virtual BOOL CloseChildren();
 63:
 64:    // handle the command for counting the MDI children
 65:    void CMCountChildren();
 66:
 67:    // handle closing the MDI frame window
 68:    virtual BOOL CanClose();
 69:
 70:    // declare response table
 71:    DECLARE_RESPONSE_TABLE(TAppMDIClient);
 72:  };
 73:
 74:  DEFINE_RESPONSE_TABLE1(TAppMDIClient, TMDIClient)
 75:    EV_COMMAND(CM_COUNTCHILDREN, CMCountChildren),
 76:   END_RESPONSE_TABLE;
 77:
 78:   TAppMDIChild::TAppMDIChild(TMDIClient& parent, int ChildNum)
 79:     : TMDIChild(parent),
 80:        TFrameWindow(&parent),
 81:        TWindow(&parent)
 82:   {
 83:     char s[1024];
 84:
 85:     // set the scrollers in the window
 86:     Attr.Style |= WS_VSCROLL | WS_HSCROLL;
 87:     // create the TScroller instance
 88:     Scroller = new TScroller(this, 200, 15, 10, 50);
 89:
 90:     // set MDI child window title
 91:     sprintf(s, "%s%i", "MDI Child #", ChildNum);
 92:     Title = _fstrdup(s);
 93:
 94:     // randomize the seed for the random-number generator
 95:     randomize();
 96:
 97:     // assign a null string to the variable s
 98:     strcpy(s, "");
 99:     // build the list of random words
100:    for (int i = 0; i < MaxWords; i++) {
101:        if (i > 0 && i % WordsPerLine == 0)
102:           strcat(s, "\r\n");
103:        strcat(s, Words[random(NumWords)]);
104:    }
105:    // create a static text object in the child window if the
106:    // ChildNum variable stores an odd number. Otherwise,
107:    // create an edit box control
108:    if (ChildNum % 2 == 0) {
109:       // create the edit box
110:       TextBox = new TEdit(this, ID_TEXT_EDIT, s,
111:                10, 10, 300, 400, 0, TRUE);
112:       // remove borders and scroll bars
113:       TextBox->Attr.Style &= ~WS_BORDER;
114:       TextBox->Attr.Style &= ~WS_VSCROLL;
115:       TextBox->Attr.Style &= ~WS_HSCROLL;
116:    }
117:    else
118:       // create static text
119:       TextTxt = new TStatic(this, -1, s, 10, 10, 300, 400,
120:                             strlen(s));
121:  }
122:
123:  BOOL TAppMDIChild::CanClose()
124:  {
125:    // return TRUE if the ExpressClose member of the
126:    // parent MDI frame window is TRUE
127:    if (ExpressClose == TRUE) {
128:      NumMDIChild--;
129:      return TRUE;
130:    }
131:    else
132:      // prompt the user and return the prompt result
133:       if (MessageBox(" close this MDI window?",
134:           "Query", MB_YESNO | MB_ICONQUESTION) == IDYES) {
135:        NumMDIChild--;
136:        return TRUE;
137:      }
138:      else
139:        return FALSE;
140:  }
141:
142:  TMDIChild* TAppMDIClient::InitChild()
143:  {
144:    ++NumMDIChild;
145:    return new TAppMDIChild(*this, ++HighMDIindex);
146:  }
147:
148:  BOOL TAppMDIClient::CloseChildren()
149:  {
150:    BOOL result;
151:    // set the ExpressClose flag
152:    ExpressClose = TRUE;
153:    // invoke the parent class CloseChildren() member function
154:    result = TMDIClient::CloseChildren();
155:    // clear the ExpressClose flag
156:    ExpressClose = FALSE;
157:    NumMDIChild = 0;
158:    HighMDIindex = 0;
159:    return result;
160:  }
161:
162:  //  display a message box that shows the number of children
163:  void TAppMDIClient::CMCountChildren()
164:  {
165:    char msgStr[81];
166:
167:    sprintf(msgStr, "There are %i MDI child windows", NumMDIChild);
168:    MessageBox(msgStr, "Information", MB_OK | MB_ICONINFORMATION);
169:  }
170:
171:  BOOL TAppMDIClient::CanClose()
172:  {
173:    return MessageBox(" close this application?", "Query",
174:                     MB_YESNO | MB_ICONQUESTION) == IDYES;
175:  }
176:
177:  void TWinApp::InitMainWindow()
178:  {
179:    MainWindow = new TMDIFrame("Simple MDI Text Viewer",
180:                       TResId(IDM_COMMANDS),
181:                       *new TAppMDIClient);
182:  }
183:
184:  int OwlMain(int /* argc */, char** /*argv[] */)
185:  {
186:    TWinApp app;
187:    return app.Run();
188:  }
189:

The program in Listing 21.4 declares a set of global constants used in generating the random text in each MDI child window. The global array of pointer Words contains the program's somewhat restricted vocabulary. The listing also declares the global variables ExpressClose, NumMDIChild, and HighMDIindex. These variables provide a simple solution for sharing information between the descendants of branched-out OWL classes. The variable ExpressClose assists in closing all of the child MDI windows in one swoop. The variable NumMDIChild maintains the actual number of MDI child windows. The variable HighMDIindex stores the index of the last MDI child window created.

The program listing declares three classes: the application class, TWinApp, in line 26; the MDI client class, TAppMDIClient, in line 50; and the MDI child window class, TAppMDIChild, in line 35. We will discuss these classes in order.

The code for the application class looks very much like the ones in previous programs, with one exception. The InitMainWindow member function, defined in lines 177 to 182, creates an instance of the stock MDI frame class, TMDIFrame. The TMDIFrame constructor call has the following arguments: title of the application; the name of the menu resource, COMMANDS; and the pointer to the dynamically allocated instances of TAppMDIClient.

The TAppMDIClient class declares a constructor and a group of protected member functions. The member functions are as follows:

1. The member function InitChild (defined in lines 142 to 146) initializes an MDI child window. The function increments the global variable NumMDIChild and then returns a dynamically allocated instance of TAppMDIChild. The arguments of creating this instance are *this (a reference to the object itself) and ++HighMDIindex. The second argument pre- increments the global variable HighMDIindex, which keeps track of the highest index for an MDI child window.

2. The member function CloseChildren (defined in lines 148 to 160) alters the behavior of the inherited CloseChildren function. The new version performs the following tasks:

n Assigns TRUE to the global variable ExpressClose (see line 152).

Invokes the parent class version of CloseChildren and stores the result of that function call in the local variable result.

Assigns FALSE to the variable ExpressClose in line 156.

Assigns 0 to the global variable NumMDIChild in line 157.

Assigns 0 to the global variable HighMDIindex in line 157. This task resets the value in variable HighMDIindex when you close all of the MDI child windows.

Returns the value stored in the variable result.

3. The member function CMCountChildren (defined in lines 163 to 168) responds to the Windows command message CM_COUNTCHILDREN generated by the menu option Count Children. The function displays the number of MDI child windows in a message dialog box. The function first builds the string msgStr to contain the formatted image of the global variable NumMDIChild. Then, the function invokes the member function MessageBox to display the sought information.

4. The virtual member function CanClose (defined in lines 171 to 175) prompts you to confirm closing the MDI-compliant application.

The MDI child window class, TAppMDIChild, declares the TextBox and TextTxt data members, a constructor, and the CanClose member function. The member TextBox is the pointer to the TEdit instance created to store the random text in one kind of the MDI child windows. The member TextTxt is the pointer to the TStatic instance created to store random text in the other kind of MDI child windows.

The TAppMDIChild constructor (defined in lines 78 to 121) performs a variety of tasks, as follows:

  • Sets the window style to include the vertical and horizontal scroll bars (see line 86).
  • Creates an instance of TScroller to animate the window's scroll bars (see line 88).
  • Sets the window title to include the MDI child window number, using the statements in lines 91 and 92.
  • Randomizes the seed for the random-number generator function, random.
  • Creates the random text and stores it in the local string variable s. This task uses the for loop in lines 100 to 104.
  • If the MDI child window number is even, creates a multiline instance of TEdit in lines 110 and 111. This instance contains a copy of the text stored in variable s. In addition, the constructor disables the border, vertical scroll bar, and horizontal scroll bar styles (see the statements in lines 113 to 115). These scroll bars are not needed because the MDI child window itself has scroll bars. In the case of an odd-numbered MDI child window number, the constructor creates static text using the characters in variable s (see the statement in lines 119 and 120).

The CanClose member function regulates closing an MDI child window. When you close such a window using the Close option in its own system menu, the function requires your confirmation. If the request to close comes from the Close All menu command in the parent window, the MDI child window closes without confirmation. The function decrements the global variable NumMDIChild in two cases: first, when the global variable ExpressClose is TRUE; and second, when the function MessageBox, which prompts you to confirm closing the window, returns IDYES.

Revised Text Viewer
Let's expand on the MDI1.EXE program to illustrate other aspects of managing MDI windows. The next application also creates MDI children that contain edit box controls with random text. However, each MDI child window has the following additional controls:

  • An ->UpperCase pushbutton control that converts the text in the MDI child window into uppercase.
  • A ->LowerCase pushbutton control that converts the text in the MDI child window into lowercase.
  • A Can Close check box. Using this box replaces using the confirmation dialog box that appears when you want to close the MDI child window. The check box enables you to predetermine whether or not the MDI child window can be closed.

The application menu adds a new pop-up menu item, Current MDI Child. This menu item has options that work on the current MDI child window. The commands enable you to clear, convert to uppercase, convert to lowercase, or rewrite the characters in the MDI child window. The new pop-up menu shows how you can manipulate MDI children with custom menus.

Compile and run the application. Create a few MDI children and use their pushbutton controls to toggle the case of characters in these windows. Also use the Current MDI Child commands to further manipulate the text in the currently active MDI child window. Try to close the MDI children with the Can Close check box marked and unmarked. Only the MDI children with the Can Close control checked close individually. Use the Close All option in the MDI Children pop-up menu and watch all of the MDI children close, regardless of the check state of the Can Close control. Figure 21.2 shows a sample session with the MDI2.EXE program.

Listing 21.5 shows the contents of the MDI2.DEF definition file. Listing 21.6 shows the source code for the MDI2.H header file. The file contains the constants for the menu commands and the control IDs. Listing 21.7 contains the script for the MDI2.RC resource file and shows the resource for the expanded menu. Listing 21.8 contains the source code for the MDI2.CPP program file.

Figure 21.2. A sample session with the MDI2.EXE program.

	Listing 21.5. The contents of the MDI2.DEF definition file.
  1:  NAME         MDI2
  2:  DESCRIPTION  `An OWL Windows Application'
  3:  EXETYPE      WINDOWS
  4:  CODE         PRELOAD MOVEABLE DISCARDABLE
  5:  DATA         PRELOAD MOVEABLE MULTIPLE
  6:  HEAPSIZE     1024
7: STACKSIZE 8192
	Listing 21.6. The source code for the MDI2.H header file.
  1:   #define CM_COUNTCHILDREN 101
  2:   #define CM_CLEAR         102
  3:   #define CM_UPPERCASE     103
  4:   #define CM_LOWERCASE     104
  5:   #define CM_RESET         105
  6:   #define ID_TEXT_EDIT     106
  7:   #define ID_CANCLOSE_CHK  107
  8:   #define ID_UPPERCASE_BTN 108
  9:   #define ID_LOWERCASE_BTN 109
10: #define IDM_COMMANDS 400
	Listing 21.7. The script for the MDI2.RC resource file.
  1:   #include <windows.h>
  2:   #include <owl\window.rh>
  3:   #include <owl\mdi.rh>
  4:   #include "mdi2.h"
  5:   IDM_COMMANDS MENU LOADONCALL MOVEABLE PURE DISCARDABLE
  6:   BEGIN
  7:     MENUITEM "E&xit", CM_EXIT
  8:     POPUP "&MDI Children"
  9:     BEGIN
 10:      MENUITEM  " c&reate", CM_CREATECHILD
 11:      MENUITEM  "&Cascade", CM_CASCADECHILDREN
 12:      MENUITEM  "&Tile", CM_TILECHILDREN
 13:      MENUITEM  "Arrange &Icons", CM_ARRANGEICONS
 14:      MENUITEM  " c&lose All", CM_CLOSECHILDREN
 15:      MENUITEM  " c&ount Children", CM_COUNTCHILDREN
 16:    END
 17:    POPUP "&Current MDI Child"
 18:    BEGIN
 19:      MENUITEM  "&Clear", CM_CLEAR
 20:      MENUITEM  "&Uppercase", CM_UPPERCASE
 21:      MENUITEM  "&Lowercase", CM_LOWERCASE
 22:      MENUITEM  "&Reset", CM_RESET
 23:    END
24: END
	Listing 21.8. The source code for the MDI2.CPP program file.
  1:   /*
  2:     Program to demonstrate MDI windows with controls
  3:   */
  4:   #include <owl\mdi.rh>
  5:   #include <owl\applicat.h>
  6:   #include <owl\framewin.h>
  7:   #include <owl\button.h>
  8:   #include <owl\edit.h>
  9:   #include <owl\checkbox.h>
 10:  #include <owl\scroller.h>
 11:  #include <owl\mdi.h>
 12:  #include "mdi2.h"
 13:  #include <stdio.h>
 14:  #include <string.h>
 15:
 16:  // declare constants for sizing and spacing the controls
 17:  // in the MDI child window
 18:  const Wbtn = 50 * 3;
 19:  const Hbtn = 30;
 20:  const BtnHorzSpacing = 20;
 21:  const BtnVertSpacing = 10;
 22:  const Wchk = 200 * 3;
 23:  const Hchk = 20;
 24:  const ChkVertSpacing = 10;
 25:  const Wbox = 400 * 3;
 26:  const Hbox = 200 * 3;
 27:
 28:  // declare the constants for the random text that appears
 29:  // in the MDI child window
 30:  const MaxWords = 200;
 31:  const WordsPerLine = 10;
 32:  const NumWords = 10;
 33:  const BufferSize = 1024;
 34:  char AppBuffer[BufferSize];
 35:  char* Words[NumWords] = { "The ", "friend ", "saw ", "the ",
 36:                  "girl ", "drink ", "milk ", "boy ",
 37:                  " cake ", "bread " };
 38:
 39:
 40:  BOOL ExpressClose = FALSE;
 41:  int NumMDIChild = 0;
 42:  int HighMDIindex = 0;
 43:
 44:  class TWinApp : public TApplication
 45:  {
 46:  public:
 47:    TWinApp() : TApplication() {}
 48:
 49:  protected:
 50:    virtual void InitMainWindow();
 51:  };
 52:
 53:  class TAppMDIChild : public TMDIChild
 54:  {
 55:  public:
 56:
 57:
 58:    TAppMDIChild(TMDIClient& parent, int ChildNum);
 59:
 60:  protected:
 61:
 62:    TEdit* TextBox;
 63:    TCheckBox* CanCloseChk;
 64:
 65:    // handle the UpperCase button
 66:    void HandleUpperCaseBtn()
 67:      { CMUpperCase(); }
 68:
 69:    // handle the LowerCase button
 70:    void HandleLowerCaseBtn()
 71:      { CMLowerCase(); }
 72:
 73:    // handle clear the active MDI child
 74:    void CMClear()
 75:      { TextBox->Clear(); }
 76:
 77:    // handle converting the text of the active
 78:    // MDI child to uppercase
 79:    void CMUpperCase();
 80:
 81:    // handle converting the text of the active
 82:    // MDI child to lowercase
 83:    void CMLowerCase();
 84:
 85:    // handle resetting the text of the active MDI child
 86:    void CMReset();
 87:
 88:    // reset the text in an MDI child window
 89:    void InitText();
 90:
 91:     // handle closing the MDI child window
 92:     virtual BOOL CanClose();
 93:
 94:     // declare response table
 95:     DECLARE_RESPONSE_TABLE(TAppMDIChild);
 96:   };
 97:
 98:   DEFINE_RESPONSE_TABLE1(TAppMDIChild, TMDIChild)
 99:     EV_COMMAND(ID_UPPERCASE_BTN, HandleUpperCaseBtn),
100:    EV_COMMAND(ID_LOWERCASE_BTN, HandleLowerCaseBtn),
101:    EV_COMMAND(CM_CLEAR, CMClear),
102:    EV_COMMAND(CM_UPPERCASE, CMUpperCase),
103:    EV_COMMAND(CM_LOWERCASE, CMLowerCase),
104:    EV_COMMAND(CM_RESET, CMReset),
105:  END_RESPONSE_TABLE;
106:
107:  class TAppMDIClient : public TMDIClient
108:  {
109:  public:
110:
111:   TAppMDIClient() : TMDIClient() {}
112:
113:   protected:
114:
115:    // create a new child
116:    virtual TMDIChild* InitChild();
117:
118:    // close all MDI children
119:    virtual BOOL CloseChildren();
120:
121:    // handle the command for counting the MDI children
122:    void CMCountChildren();
123:
124:    // handle closing the MDI frame window
125:    virtual BOOL CanClose();
126:
127:    // declare response table
128:    DECLARE_RESPONSE_TABLE(TAppMDIClient);
129:  };
130:
131:  DEFINE_RESPONSE_TABLE1(TAppMDIClient, TMDIClient)
132:    EV_COMMAND(CM_COUNTCHILDREN, CMCountChildren),
133:  END_RESPONSE_TABLE;
134:
135:  TAppMDIChild::TAppMDIChild(TMDIClient& parent, int ChildNum)
136:    : TMDIChild(parent),
137:      TFrameWindow(&parent),
138:      TWindow(&parent)
139:  {
140:    char s[41];
141:    int x0 = 10;
142:    int y0 = 10;
143:    int x = x0;
144:    int y = y0;
145:
146:    // set the scrollers in the window
147:    Attr.Style |= WS_VSCROLL | WS_HSCROLL;
148:    // create the TScroller instance
149:    Scroller = new TScroller(this, 200, 15, 10, 50);
150:
151:    // set MDI child window title
152:    sprintf(s, "%s%i", " child #", ChildNum);
153:    Title = _fstrdup(s);
154:
155:    // create the push button controls
156:    new TButton(this, ID_UPPERCASE_BTN, "->UpperCase",
157:                x, y, Wbtn, Hbtn, TRUE);
158:    x += Wbtn + BtnHorzSpacing;
159:    new TButton(this, ID_LOWERCASE_BTN, "->LowerCase",
160:                x, y, Wbtn, Hbtn, FALSE);
161:
162:    x = x0;
163:    y += Hbtn + BtnVertSpacing;
164:    CanCloseChk = new TCheckBox(this, ID_CANCLOSE_CHK, " can Close",
165:                                x, y, Wchk, Hchk, NULL);
166:    y += Hchk + ChkVertSpacing;
167:    InitText();
168:    // create the edit box
169:    TextBox = new TEdit(this, ID_TEXT_EDIT, AppBuffer,
170:                        x, y, Wbox, Hbox, 0, TRUE);
171:    // remove borders and scroll bars
172:    TextBox->Attr.Style &= ~WS_BORDER;
173:    TextBox->Attr.Style &= ~WS_VSCROLL;
174:    TextBox->Attr.Style &= ~WS_HSCROLL;
175:  }
176:
177:  void TAppMDIChild::CMUpperCase()
178:  {
179:    TextBox->GetText(AppBuffer, BufferSize);
180:    strupr(AppBuffer);
181:    TextBox->SetText(AppBuffer);
182:  }
183:
184:  void TAppMDIChild::CMLowerCase()
185:  {
186:    TextBox->GetText(AppBuffer, BufferSize);
187:    strlwr(AppBuffer);
188:    TextBox->SetText(AppBuffer);
189:  }
190:
191:  void TAppMDIChild::CMReset()
192:  {
193:    InitText();
194:    TextBox->SetText(AppBuffer);
195:  }
196:
197:  BOOL TAppMDIChild::CanClose()
198:  {
199:    // return TRUE if the ExpressClose member of the
200:    // parent MDI frame window is TRUE
201:    if (ExpressClose == TRUE) {
202:      NumMDIChild--;
203:      return TRUE;
204:    }
205:    else
206:    // do not close the MDi child window if the Can Close is
207:    // not checked
208:    if (CanCloseChk->GetCheck() == BF_UNCHECKED)
209:      return FALSE;
210:    else {
211:      NumMDIChild--;
212:       return TRUE;
213:    }
214:  }
215:
216:  void TAppMDIChild::InitText()
217:  {
218:    // randomize the seed for the random-number generator
219:    randomize();
220:
221:    // assign a null string to the buffer
222:    AppBuffer[0] = `\0';
223:    // build the list of random words
224:    for (int i = 0;
225:         i < MaxWords && strlen(AppBuffer) <= (BufferSize - 10);
226:         i++) {
227:      if (i > 0 && i % WordsPerLine == 0)
228:        strcat(AppBuffer, "\r\n");
229:      strcat(AppBuffer, Words[random(NumWords)]);
230:    }
231:  }
232:
233:  TMDIChild* TAppMDIClient::InitChild()
234:  {
235:    ++NumMDIChild;
236:    return new TAppMDIChild(*this, ++HighMDIindex);
237:  }
238:
239:  BOOL TAppMDIClient::CloseChildren()
240:  {
241:    BOOL result;
242:    // set the ExpressClose flag
243:    ExpressClose = TRUE;
244:    // invoke the parent class CloseChildren() member function
245:    result = TMDIClient::CloseChildren();
246:    // clear the ExpressClose flag
247:    ExpressClose = FALSE;
248:    NumMDIChild = 0;
249:    HighMDIindex = 0;
250:    return result;
251:  }
252:
253:  //  display a message box that shows the number of children
254:  void TAppMDIClient::CMCountChildren()
255:  {
256:    char msgStr[81];
257:
258:    sprintf(msgStr, "There are %i MDI children", NumMDIChild);
259:    MessageBox(msgStr, "Information", MB_OK | MB_ICONINFORMATION);
260:  }
261:
262:  BOOL TAppMDIClient::CanClose()
263:  {
264:    return MessageBox(" close this application?",
265:               "Query", MB_YESNO | MB_ICONQUESTION) == IDYES;
266:  }
267:
268:  void TWinApp::InitMainWindow()
269:  {
270:    MainWindow = new TMDIFrame("Simple MDI Text Viewer (version 2)",
271:                       TResId(IDM_COMMANDS),
272:                       *new TAppMDIClient);
273:  }
274:
275:  int OwlMain(int /* argc */, char** /*argv[] */)
276:  {
277:    TWinApp app;
278:    return app.Run();
279: }

The program in Listing 21.8 declares two sets of constants. The first set is used for sizing and spacing the controls of each MDI child window. The second set of constants is used to manage the random text. The program also declares variable AppBuffer as a single 1KB text buffer. We chose to make the buffer global instead of a class data member mainly to reduce the buffer space--the application classes need only one shared buffer at any time. The program listing also declares the global variables ExpressClose, NumMDIChild, and HighMDIindex--another set of components carried over from the program in file MDI1.CPP.

The new application maintains the same three classes described in the last program. However, the MDI child class has different members in this program. The new members manage the response to the control notification messages as well as the Current MDI Child menu command messages.

The TAppMDIChild constructor (defined in lines 135 to 175) performs the following tasks:

  • Sets the window style to include the vertical and horizontal scroll bars, using the statement in line 147.
  • Creates an instance of TScroller to animate the window's scroll bars, using the statement in line 149.
  • Sets the window title to include the MDI child window number using the statements in lines 152 and 153.
  • Creates the ->LowerCase and ->UpperCase pushbutton controls using the statements in lines 156 to 160.
  • Creates the Can Close check box control using the statements in lines 162 to 165.
  • Calls the InitText member function to generate random text in the application buffer AppBuffer.
  • Creates a multiline instance of TEdit in statement located in lines 169 and 170. This instance contains a copy of the text stored in the application buffer.
  • Disables the border, vertical scroll bar, and horizontal scroll mbar styles of the edit control. This task uses the statements in lines 172 to 174.

The member function CMUpperCase (defined in 177 lines to 182) responds to the command message emitted by the UpperCase command. The function copies the text in the MDI child window to the application's buffer, converts the characters in the buffer to uppercase, and then writes the buffer back to the MDI child window.

The member function CMLowerCase (defined in lines 184 to 189) responds to the command message emitted by the Lowercase command. The function performs similar steps to those in CMUpperCase--except the text is converted into lowercase.

The member function CanClose (defined in lines 197 to 214) responds to the WM_CLOSE message emitted by the Close option in the system menu available in each MDI child window. If the MDI frame window's ExpressClose variable is TRUE, the function decrements the global variable NumMDIChild and then returns TRUE. Otherwise, the function returns FALSE if the Can Close check box is unchecked, or it decrements the global variable NumMDIChild and then returns FALSE if the control is not checked.

The member function InitText (defined in lines 233 to 237) is an auxiliary routine that fills the application buffer with random text. The function creates up to MaxWords words or enough that the buffer limit is closely reached (within 10 bytes). Checking the number of characters in the buffer ensures that the program does not corrupt the memory while attempting to add MaxWords words to the buffer.

The member functions HandleUpperCaseBtn and HandleLowerCase respond to the notification messages sent by the pushbuttons of an MDI child window. These functions perform the same tasks of CMUpperCase and CMLowerCase, respectively. Therefore, the notification response functions call their respective command-message response member functions.

The member function CMClear (defined in lines 74 and 75) responds to the command message emitted by the Clear command in the Current MDI Child menu item. The function simply invokes the TextBox->Clear() function call.

The member function CMReset (defined in lines 191 to 195) responds to the command message emitted by the Reset command in the Current MDI Child menu item. The function calls the InitText member function to create a new batch of random text and then copies the buffer's text to the edit control of the MDI child window.


Note: The Current MDI Child pop-up menu has four options that manipulate the currently active MDI child window. The command messages emitted by these options are handled by the MDI child window instances and not the MDI frame instance--which is what a window instance normally does regarding its own menu commands. This order of handling the command messages is preferred and makes use of the fact that the menu-based messages do reach the currently active MDI child window first. You can rewrite the program such that the functions CMClear, CMUpperCase, CMLowerCase, and CMReset appear as member functions of class TAppMDIFrame.


Summary
This chapter presented the Multiple Document Interface (MDI), which is an interface standard in Windows. The chapter discussed the following subjects:

  • The basic features and components of an MDI-compliant application. These components include the MDI frame window, the invisible MDI client window, and the dynamically created MDI child windows.
  • Basics of building an MDI application.
  • The TMDIFrame class, which manages the MDI client window, the MDI child windows, and the execution of the menu commands.
  • Building MDI frame windows as objects that are owned by the application and that own the MDI client window.
  • The TMDIClient class, which owns the MDI child windows.
  • Building MDI child windows as an instance of a TWindow descendant and using customized client windows.
  • Managing messages in an MDI-compliant application. The currently active MDI child window has a higher priority for handling menu-based command message than its parent, the MDI frame window.

Q&A

Q Should each MDI child window have an ID?

A Yes. Associating each MDI child window with an ID gives you more control over managing these windows, especially if they vary in relevance. Thus, you can use the ID to exclude special MDI child windows from collective operations.

Q Can I hide MDI child windows?

A Yes, you can use the inherited member function Twindow::ShowWindow to show and hide one MDI child window or more.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. Try to understand the quiz and exercise answers before continuing on to the bonus chapters. Answers are provided in Appendix A, "Answers."

Quiz

1. True or false? MDI child windows can have their own menus.

2. True or false? MDI child windows can be moved outside the area of the frame window.

3. True or false? The MFC library supports nested MDI child windows.

4. True or false? This is the last quiz question in this book!

Exercises

1. Experiment with the expanding vocabulary of programs MDI1.EXE and MDI2.EXE.

2. Add a control that inserts the date and time in MDI child windows of program MDI1.EXE.


Return to C++ Bibliography
 
Site Map Search Contact