2        Presentation and Control

2.1        Overview

An application's user interface (UI) is the subsystem responsible for presentation and control. UI control mechanisms route user input commands such as menu selections or button clicks to internal functions that may update the application's data. UI presentation components such as view windows or edit boxes are responsible for displaying the application's data after each update.

This chapter presents common MFC presentation and control mechanisms in the context of  a single example: a calculator. The calculator has a simple graphical user interface that features a custom menu, toolbar, and control panel. MFC's various visual resource editors will be employed to build these components. Readers will see how MFC's Class Wizard is used to wire these components to internal member functions and variables that define the calculator's logic.

2.1.1       Prerequisites

Several UML class diagrams appear in this chapter. These are reviewed in Appendix 1. Most of the advanced C++ code is mercifully hidden from readers by the various MFC wizards and resource editors.

2.2        Example: A Simple Calculator

A form is a simple type of graphical user interface (GUI) in which the application window contains a number of input and output controls (buttons, boxes, meters, sliders, etc.). Forms are not only appropriate for representing real forms such as tax forms, registration forms, order forms, and questionnaires, but also control panels, property sheets, database browsers, and calculators.

Our first MFC application is a simple desktop calculator. A user will type two numbers, arg1 and arg2, into the [Argument #1] and [Argument #2] boxes, respectively. There are four ways for the user to display arg1 * arg2 in the [Result] box: press the [Mul] button, press the [X] button on the toolbar, choose the [Calculate]/[Mul] menu item, or type [Alt]-[C]-[M].

Similar procedures can be used to display arg1 + arg2, arg1 – arg2, and arg1/arg2. However, attempting to divide by 0 produces the warning dialog box:

2.3        The MFC App Wizard

We begin by using a Visual C++ tool called the MFC App Wizard to create a workspace, project, and partially completed program:

Step 1:

i. Start Visual C++.

ii. Choose [MFC AppWizard (.exe)] from the [File]/[New ...]/[Projects] dialog box.

iii. Name the project "Calc" and choose an acceptable parent directory for the workspace that will be created.

iv. Click the [OK] button.

The App Wizard is a code generator. It presents a series of six dialog boxes that allow programmers to choose various program features. The bottom of each dialog box has four buttons labeled [<Back], [Next>], [Finish], and [Cancel]. The [<Back] button allows programmers to return to earlier dialog boxes to re-think hastily selected features. When the [Finish] button is pressed a [New Project Information] dialog box appears that displays all of the selected features. Once the [OK] button of this dialog box is pressed, it will be difficult to change the selected features.

Step 2:

i. In the [Step 1] dialog choose [Single document].

ii. Accept all of the default features suggested by the [Step 2 of 6] through [Step 5 of 6] dialogs.

iii. The [Step 6 of 6] dialog displays the classes and files that will be generated by the App Wizard. It also displays the base class of each class. Select the CCalcView class. Notice that its base class is CView. Use the drop down list to change this to CFormView, then click [Finish].

iv. A [New Project Information] dialog appears that describes the selected features of the application. Clicking the [Cancel] button is the programmer's last chance to change any of these features. Click the [OK] button.

Here is a snapshot of the [Step 6 of 6] dialog:

2.3.1       The Calc Classes

The App Wizard generates five class declarations in five separate header files:

class CCalcApp;      // in Calc.h
class CCalcView;     // in CalcView.h
class CCalcDoc;      // in CalcDoc.h
class CMainFrame;    // in MainFrm.h
class CAboutDlg;     // in Calc.cpp

The elaborate MS naming convention is already beginning to reveal itself. A typical class name starts with the letter "C", standing for "class", followed by the name of the application, "Calc", followed by the nick name of the base class. MFC programming conventions are described in the Backgrounders/Visual Tools/MS Visual C++/MFC Development Guidelines volume of the MSDN library, which includes a section on "Hungarian Notation", MFC's naming convention. For example, the prefix of an MFC member variables has the form m_xxx, where xxx indicates the variable's type and content. Thus, m_nLength holds an integer representing a length, m_chResponse holds a character representing the response to a question, and m_lpszName holds a long pointer to a zero-terminated C string representing a name. In this book we avoid using Hungarian Notation unless forced upon us by the framework.

We can see these classes listed in the [Class View] tree control in the [Workspace] window:

Try expanding the nodes of this control to see the member functions and variables inside each class. Note that double clicking on a member automatically opens the file containing the definition of the member.

The [Workspace]/[File View] tree control shows all of the files that belong to the project. These are divided into source (.cpp), header (.h), and resource files:

Double clicking a file name automatically opens the file.

The [Workspace]/[Resource View] tree control shows the project's menus, toolbars, dialog boxes, and other resources. Double clicking on a resource automatically opens the resource file in a special graphical editor:

Our application will only create a single, global instance of the CCalcApp class:

CCalcApp theApp;  // in Calc.cpp

This is our application's message dispatcher. There will also only be a single instance of the CMainFrame class, which represents the frame around the application window that contains the form, menu bar, toolbar, and status bar. We'll delay any discussion of CCalcDoc objects until we start using them in Chapter 3. Each time we select [Help]/[About Calc ...] from the application menu an instance of the CAboutDlg class is created representing the [About Calc] dialog box that appears:

The CCalcView class is derived from the CFormView, which is derived from CView, which is ultimately derived from CWnd, the class of all windows (MFC class icons are shown in boldface):

Obviously CCalcView objects are the form views mentioned earlier. Fortunately, this is the only class we will need to edit.

2.3.2       Build and Test!

We can build and test the application at this point! Choose [Build]/[Build Calc.exe] or press the [F7] key. Notice the compiler and linker messages that appear in the output window. After the magical phrase "Calc.exe - 0 error(s), 0 warning(s)" appears, choose [Build]/[Execute Calc.exe].

Of course our calculator doesn't do much calculating at this stage. It just displays the message:

TODO: Place form controls on this dialog.

But notice that the calculator isn't featureless. It can be maximized, minimized, resized, moved, and scrolled. It has title, menu, tool, and status bars. The menu bar contains [File], [Edit], [View], and [Help] menus. Except for the [View] menu, all of menu selections work, as do the corresponding toolbar buttons! Spend a few minutes playing with the calculator, then close it.

2.4        The Dialog Editor

The Dialog Editor is one of many editors included in Visual C++. In fact, Visual C++ has probably anticipated your next move by opening the calculator (such as it is) in a Dialog Editor window. If not, double click [IDD_CALC_FORM] node in the [Workspace]/[Resource View]/[Dialog] tree control.

The Dialog Editor is an example of one of the visual programming environments discussed in the Programming Notes section at the end of this chapter. The Dialog Editor consists of a [Layout] menu, a corresponding [Dialog] toolbar, a [Controls] toolbar, and possibly one or more edit windows, each containing a form or dialog box. The general idea is to drag controls from the [Controls] toolbar onto a form, customize the properties of each added control using its property sheet, then use the [Dialog] toolbar to tidy the layout.

Our goal will be to use the Dialog Editor to create the calculator control panel:

Ignoring the menu and toolbar for now, note that the calculator consists of three labeled edit boxes (one is read-only) and four buttons.

Step 3:

i. Delete the "TODO: ... " string in the calculator by selecting it with the mouse, then pressing the [Delete] key.

ii. Drag three edit boxes, three static text controls (i.e., labels), and four buttons from the [Controls] toolbar onto the calculator form. (When the mouse passes over an icon in the [Controls] window a tool tip should appear saying what type of control it represents.)

iii. Use the [Layout] menu, the buttons in the [Dialog] control bar, or the mouse to size, align, and space the controls so the calculator resembles the screen shot shown earlier. Size the calculator using the its resize handles so that it is as small as possible without covering any of the controls.

Here's a snapshot of the Dialog Editor after the calculator is nearly complete:

2.4.1       Property Sheets

We can display the property sheet of just about any object by selecting it with the left mouse button, then choosing [View]/[Properties ...] or by choosing [Properties ...] from the shortcut menu that is displayed by clicking on the object with the right mouse button. As its name suggests, a property sheet allows us to view and modify an object's properties.

Step 4:

i. Use the property sheets of the static text controls to set their captions to "Argument #1", "Argument #2", and "Result", respectively. 

ii. Use the property sheets of the button controls to set their captions to "Add", "Sub", "Mul", and "Div", respectively and their ID's to "IDC_ADD", "IDC_SUB", "IDC_MUL", and "IDC_DIV", respectively. 

iii. Use the properties dialog of the edit controls to set their ID's to "IDC_ARG1", "IDC_ARG2", and "IDC_RESULT", respectively.

iv. Select the [Read-only] check box on the [Styles] page of the [Result] edit box's property sheet.

Here's a snapshot of the [Styles] page of the property sheet of the [Result] edit box:

IDC_ADD, IDC_MUL, IDC_ARG1, etc. are called resource identifiers; they will be explained in detail below. Changing the names of the resource identifiers is not mandatory, but it will simplify things later on.

We may build and test our application again. (Or simply choose [Layout]/[Test], then press the [Esc] key to stop the test.) Although the calculator looks more interesting, it still doesn't do any calculating. We'll need to "connect" the buttons and boxes to our program to make this happen. Notice that the [Tab] key can be used to make the keyboard focus jump from one control to another. By choosing the [Layout]/[Tab Order] menu item in the Dialog Editor we can view and modify the tab order.

2.5        The Composite Design Pattern

A calculator is an example of a composite object. We can treat it as an individual object that encapsulates its components: the menu bar, toolbar, buttons, labels, and boxes, or, when it's convenient, we can ignore the calculator and treat its components as individual objects.

Of course some of the calculator's components are also composites. For example, the toolbar is a composite consisting of several buttons. Sometimes it's convenient to think of a calculator as a tree-like structure:

Composites are so common, that they are included in many pattern catalogs:

Composite [Go4], [POSA]

Other Names

Composites are also called assemblies, wholes, aggregates, and trees. Components are also called parts.

Problem

Sometimes a composite structure must be viewed as a single object that encapsulates its components. Other times we need to change or analyze the individual components, which may also be composite structures.

Solution

If we call components without parts atoms, then we can view Atom and Composite as specializations of an abstract Component class. We define aggregation as the relationship between Composite and Component.

2.5.1       Static Structure

In some formulations each component maintains a reference to its parent:

We have already seen many instances of the composite pattern. For example, files are atoms and directories are composites, the leaf nodes of a tree are atoms, while the composites are parent nodes. In Microsoft's terminology components are called "windows" and atoms are called "controls".

2.6        The Class Wizard

Now we know how CALC_FORM and CCalcView are related, but how are the buttons and edit boxes related to CCalcView? How are instances of the MUL, ADD, SUB, DIV, ARG1, ARG2, and RESULT system classes related to the CCalcView application class?

Of course there are MFC wrapper classes for buttons and edit boxes, too. Just as buttons and edit boxes are components of  CALC_FORM objects, we could make CButton and CEdit objects CCalcView components:

Although this is possible, it is a case of object-oriented overkill.  Basically, a control is something that can be manipulated by a user. When a control is manipulated, it sends a message to its parent window describing what happened. In some cases the control changes state. For example, the internal state of an edit control is the string it displays. When the user types a new string into the edit control, it changes state and sends an EN_CHANGED message to its parent window. On the other hand, buttons are stateless controls. When a button is clicked, it simply sends a BN_CLICKED message to its parent window.

Instead of adding CEdit member variables to the CCalcView class representing the ARG1, ARG2, and RESULT edit controls, we can simply add three member variables m_arg1, m_arg2, and m_result representing the current states of the corresponding controls. And instead of adding CButton member variables to the CCalcView class representing the ADD, MUL, SUB, and DIV buttons, we can simply add member functions OnAdd(), OnMul(), OnSub(), and OnDiv() which are automatically called when the corresponding buttons send their BN_CLICKED messages.

2.6.1       Connecting Components to Members

Adding member variables and functions to the CCalcView class is easy, but "connecting" them to controls in the calculator sounds hard. How do we arrange to have CCalcView::OnAdd() called every time the user clicks the [Add] button? How do we transfer the state of the [Arg1] and [Arg2] edit boxes to the m_arg1 and m_arg2 member variables? How do we transfer the m_result member variable back to the [Result] edit control? The answer to these and many other questions is the Class Wizard.

Step 5:

i. Select [View]/[ClassWizard]/[Member Variables]. Make sure "CCalcView" is displayed in the [Class name:] edit box.

ii. Select IDC_ARG1 from the [Control IDs:] list, then press the [Add Variable ...] button. This displays the [Add Member Variable] dialog box.

iii. Type "m_arg1" for the member variable name, select "double" from the [Variable Type] list, then press the [OK] button.

iv. Repeat this procedure for IDC_ARG2 and IDC_RESULT, calling the corresponding member variables "m_arg2" and "m_result".

v. Click the Class Wizard's [OK] button when you are finished.

Here's a snapshot of the [Class Wizard]/[Member Variable]/[Add Member Variable] dialog box:

When you're finished, this is what the [Class Wizard]/[Member Variables] dialog should look like this:

While the [Class Wizard]/[Member Variables] dialog allows us to add member variables that are wired to control states, the [Class Wizard]/[Message Maps] dialog allows us to add member functions that are wired to receive control messages.

Step 6:

i. Select [View]/[ClassWizard]/[Message Maps]. Make sure "CCalcView" is displayed in the [Class name:] edit box.

ii. Select IDC_ADD from the [Object IDs:] list, select BN_CLICKED from the [Messages:] list, then click the [Add Function ...] button.

iii. An [Add Member Function] dialog box should appear asking if you want to name the function OnAdd. Click the [OK] button on this dialog.

iv. Repeat this procedure for IDC_MUL, IDC_SUB, and IDC_DIV.

v. Click the Class Wizard's [OK] button when you are finished.

When you're finished, this is what the [Class Wizard]/[Message Maps] dialog should look like:

Let's look inside CalcView.h to examine the new member variables and functions created by the ClassWizard. Here's an edited version:

class CCalcView : public CFormView
{
public:
   //{{AFX_DATA(CCalcView)
   enum { IDD = IDD_CALC_FORM };
   double   m_arg1;
   double   m_arg2;
   double   m_result;

   //}}AFX_DATA
// Generated message map functions
protected:
   //{{AFX_MSG(CCalcView)
   afx_msg void OnAdd();
   afx_msg void OnDiv();
   afx_msg void OnMul();
   afx_msg void OnSub();

   //}}AFX_MSG
// etc.
};

Notice that the members added by the Class Wizard are colored gray and are bracketed by scary-looking green comments:

//{{ ... }}//

This is MFC's way of saying, "Don't touch this code. It belongs to the class wizard!"

Now let's take a look at CalcView.cpp. Notice that the constructor initializes the member variables:

CCalcView::CCalcView()
   : CFormView(CCalcView::IDD)
{
   //{{AFX_DATA_INIT(CCalcView)
   m_arg1 = 0.0;
   m_arg2 = 0.0;
   m_result = 0.0;

   //}}AFX_DATA_INIT
   // TODO: add construction code here
}

The member functions are empty:

void CCalcView::OnAdd()
{
   // TODO: Add your control notification handler code here  
}

2.6.2       Implementing the Message Handlers

Remember, the OnAdd() function will automatically be called when the calculator's [Add] button is pressed. What should happen? Three things:

A. The current states of the [Arg1] and [Arg2] edit controls should be transferred to m_arg1 and m_ar2, respectively.

B. m_result = m_arg1 + m_arg2;

C. The value in m_result should be transferred back to the [Result] edit control.

Transferring state information to and from a control is accomplished by the UpdateData() function inherited from the CWnd base class of CView:

BOOL CWnd::UpdateData(BOOL bSaveAndValidate = TRUE);

Calling UpdateData(TRUE) transfers the state of the control to the associated member variable. Calling UpdateData(FALSE) transfers the value of the member variable to the state of the associated control:

In the case of an edit control, UpdateData() does much more than simply transfer state information. The state of an edit control like [Arg1] is a string, while the type of m_arg1 is a double, therefore UpdateData(TRUE) must also translate the string state into a double, while UpdateData(FALSE) must translate a double into a string. If we had specified minimum and maximum values at the time we created the m_arg1, then UpdateData(TRUE) would also check to make sure the value of m_arg1 was in the indicated range.

Using this information, we can now complete the definition of OnAdd():

void CCalcView::OnAdd()
{
   UpdateData(TRUE);  // m_arg1 = IDC_ARG1 & m_arg2 = IDC_ARG2
   m_result = m_arg1 + m_arg2;
   UpdateData(FALSE); // IDC_RESULT = m_result
}

We can use this same idea in all of the new functions.

Step 7:

i. Complete the definitions of OnAdd(), OnMul(), OnDiv(), and OnSub() following the pattern described above.

ii. Build and test the application.

2.6.3       Message Dialogs

One problem remains. What should we do if the user attempts to divide by zero? Ideally, we would like to display a simple dialog box containing an error message. We can use the global function AfxMessageBox() to do this:

// = IDOK if [OK] button is pressed
int AfxMessageBox(
   LPCTSTR lpszText,   // message to display
   UINT nType = MB_OK, // dialog box style
   UINT nIDHelp = 0     // help context id
);

We can also sound a warning beep using the global Beep() function. (Note, only Windows NT and Windows 2000 pay attention to the parameters):

// = TRUE if successful
BOOL Beep(
   DWORD dwFreq      // sound frequency, in hertz
   DWORD dwDuration // sound duration, in milliseconds
);

Here's our modified version of OnDiv():

void CCalcView::OnDiv()
{
   UpdateData(TRUE); // m_arg1 = IDC_ARG1 & m_arg2 = IDC_ARG2
   if (!m_arg2)
   {
      Beep(500, 150);
      AfxMessageBox("Error: division by 0");
   }
   else
   {
      m_result = m_arg1 / m_arg2;
      UpdateData(FALSE); // IDC_RESULT = m_result      
   }
}

Step 8: Make these changes to CCalcView::OnDiv(), then build and test the application once again.

2.7        Dialog Data Exchange and Validation

To show off the full power of Dialog Data Exchange and Validation (DDX/DDV), the mechanism underlying the UpdateData() function, let's arbitrarily restrict the ranges of m_arg1 and m_arg2 to be between 0 and 100. To do this, simply type these limits into the [Minimum Value:] and [Maximum Value:] boxes respectively, of the [View]/[Class Wizard ...]/[Member Variables] dialog:

Rebuild the application, then try to add 200 to 300. As soon as the [Add] button is pressed, the following dialog appears:

Where did this dialog box come from? It was created inside OnAdd(), by the first call to OnUpdateData(TRUE). Let's drill down into UpdateData(). UpdateData() is a CWnd member function. It creates a data exchange context, dx, then passes it to DoDataExchange():

BOOL CWnd::UpdateData(BOOL bSaveAndValidate)
{
   CDataExchange dx(this, bSaveAndValidate);
   DoDataExchange(&dx);
   // etc.
}

A data exchange context simply encapsulates a pointer to the calculator and a Boolean flag indicating the direction of the data exchange:

class CDataExchange
{
public:
   BOOL m_bSaveAndValidate; // TRUE => save and validate data
   CWnd* m_pDlgWnd;         // container, usually a dialog
   // etc.
};

The virtual CWnd::DoDataExchange() function is implemented in the CCalcView class by the Class Wizard:

void CCalcView::DoDataExchange(CDataExchange* pDX)
{
   CFormView::DoDataExchange(pDX);
   //{{AFX_DATA_MAP(CCalcView)
   DDX_Text(pDX, IDC_ARG1, m_arg1);
   DDV_MinMaxDouble(pDX, m_arg1, 0., 100.);
   DDX_Text(pDX, IDC_ARG2, m_arg2);
   DDV_MinMaxDouble(pDX, m_arg2, 0., 100.);
   DDX_Text(pDX, IDC_RESULT, m_result);
   //}}AFX_DATA_MAP
}

Notice the three calls to DDX_Text(), one for each edit box-member variable connection. There are many DDX functions, one for each type of control. The DDX_Text() function knows how to exchange data between an edit control and a member variable.[1] Also notice the two calls to DDV_MinMaxDouble(). Obviously these two calls are checking the range of the values entered into the IDC_ARG1 and IDC_ARG2 edit boxes. There are many DDV functions, too. Programmers may even define customized DDX and DDV functions.

2.8        Message Maps

DDX/DDV explains how controller states and values of form view member variables are exchanged, but how are handler functions called when controls are activated?

Recall SAF, the simple application framework described in Chapter 1, where each application object was required to implement a message handler function that was automatically called by the dispatcher when a message for that object was received:

class AppObject
{
public:
   virtual void HandleMessage(Message* msg) = 0;
   // etc.
};

Alternatively, a non-virtual table-driven message handler could have been pre-defined in the AppObject class:

void AppObject::HandleMessage(Message* msg)
{
   HandlerFunction* handler = find(msg->GetType());
   handler(msg);
}

Where find() searches a table containing associations between message types and handler functions:

Message

Handler

KEY_PRESSED

HandleKey

MOUSE_CLICKED

HandleMouse

etc.

etc.

Each time a new AppObject derived class is introduced, the programmer simply creates additional entries in this table. (Actually, the two approaches are nearly identical, because a call to a virtual function compiles into a search of the virtual function table.)

For example, suppose a banking customization introduces an Account class derived from AppObject. Assume the programmer provides Account with two message handler functions, one for handling withdrawals, the other for handling deposits:

class Account: public AppObject
{
public:
   static void HandleDeposit(Message* msg);
   static void HandleWithdraw(Message* msg);
   // etc.
private:
   double balance;
};

To complete the customization, the programmer must add two new entries to AppObject's handler table:

AppObject::HandlerTable[DEPOSIT] = Account::HandleDeposit;
AppObject::HandlerTable[WITHDRAW] = Account::HandleWithdraw;

This is the approach taken by MFC applications. When a message arrives, theApp, the application's dispatcher, calls the message handler function for the message's target, a CWnd object. The message handler is always called WindowProc():

// = value returned by specialized handler
virtual LRESULT CWnd::WindowProc(
   UINT message,  // type of message to be handled
   WPARAM wParam, // 16 bits of extra data
   LPARAM lParam  // 32 bits of extra data
);

WindowProc() is table-driven. It uses it parameters to search a table called the message map for an appropriate specialized message handler function to call.

The message map itself is constructed when the pre-processor expands MFC's BEGIN_MESSAGE_MAP macro. For example, the message map for the CCalcView class (which extends the message maps for the CWnd and CView classes) is created by the call to this macro in CalcView.cpp:

BEGIN_MESSAGE_MAP(CCalcView, CFormView)
   //{{AFX_MSG_MAP(CCalcView)
   ON_BN_CLICKED(IDC_ADD, OnAdd)
   ON_BN_CLICKED(IDC_DIV, OnDiv)
   ON_BN_CLICKED(IDC_MUL, OnMul)
   ON_BN_CLICKED(IDC_SUB, OnSub)
   ON_COMMAND(ID_CALCULATE_ADD, OnAdd)
   ON_COMMAND(ID_CALCULATE_DIV, OnDiv)
   ON_COMMAND(ID_CALCULATE_SUB, OnSub)
   ON_COMMAND(ID_CALCULATE_MUL, OnMul)
   ON_COMMAND(ID_HELP_USAGE, OnHelpUsage)

   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Most of the message-handler associations found in this macro call are placed there by the [Class Wizard]/[Message Map] dialog box.

2.9        Menus

Although menus on a calculator are a bit silly, we will add them just to show how it is done. Like dialogs and forms, menus are considered to be application resources. As such, they are created and modified using a corresponding resource editor: the Menu Editor. Like the Dialog Editor, the Menu Editor will generate menu elements which will be appended to the resource script in the Calc.rc file.

The Menu Editor is used to add menus to the application's menu bar, then to add menu entries under the menus. A menu entry can be a menu item (also called a menu selection) or a submenu. We can use the Composite design pattern to describe the relationship between menus, items, and entries:

We use the property sheet of each menu and menu item to set the caption, resource identifier, and, in the case of menu items, the prompt that will appear in the application's status bar when the mouse passes over the item.

2.9.1       Accelerator Keys

Conventionally, each menu item has a corresponding accelerator key that advanced users can use to select the menu item from the keyboard. This is done by first pressing the accelerator key for the menu (or submenu) while holding down the [Alt] key, this opens the corresponding menu, then the accelerator key for the menu is pressed.

The accelerator key for a menu entry is the underlined letter in the entry's caption. Normally this would be the first letter, unless several entries in the same menu begin with the same letter. The accelerator key for a menu entry is designated by placing an ampersand, "&", in front of the corresponding letter when the caption is specified. Visual C++ automatically sets up the accelerator key mechanism.

2.9.2       Creating the [Calculate] Menu

We begin by adding a [Calculate] menu to the main frame's menu bar.

Step 9:

i. Open the MAINFRAME  menu in the menu editor by double clicking on IDR_MAINFRAME  node in the [Workspace]/[Resource View]/[Menu] tree control.

ii. To create a new menu, select the blank space at the end of the menu bar, then select [View]/[Properties] (or use the shortcut menu) to view the property sheet for this new menu.

iii. Type "&Calculate" in the [Caption:] box, then close the property sheet.

Next, we add [Add], [Mul], [Sub], and [Div] items to the [Calculate] menu:

Step 10:

i. To add a menu item select a blank space under the new [Calculate] menu, then select [View]/[Properties] (or use the shortcut menu) to view the property sheet for this new menu.

ii. Type "&Add" in the [Caption:] box.

iii. Type "result = arg1 + arg2" in the [Prompt:] box.

iv. Type "ID_CACLULATE_ADD" into the [ID:] box. (If you already typed the prompt, VC++ should suggest this ID as soon as you click on the arrow next to the [ID:] box.

v. Repeat this procedure to create menu items for the Mul, Sub, and Div operations, then close the menu editor.

Here's a snapshot of the Menu Editor just as [Div] is being added to the [Calculate] menu:

2.9.3       Adding Menu Handlers

Selecting a menu item sends a command message to the application's main frame. A message is actually an object that encapsulates information about the message such as its identifier number (BN_CLICKED, WM_MOUSEMOVE, COMMAND, etc.), the time it was sent, the position of the mouse at the time it was sent, and two general purpose parameters. In the case of a command message these parameters specify the menu and menu item identifiers.

Handling the [Calculate]/[Add] command message in CMainFrame, the main frame class, will be a bit awkward, because the information we need: m_result, m_arg1, and m_arg2, are all CCalcView member variables. Fortunately, command messages are routed. This means that if there is no handler for a command message in CMainFrame, the message is forwarded to other application objects, including the CCalcView object, until a handler is found.

What should the handler for the [Calculate]/[Add] message do? Obviously, the same thing that was done for the [Add] button: transfer the edit controls to the member variables, perform the addition, then transfer the member variables back to the edit controls. But we already have a function that does this: CCalcView::OnAdd(). Can't we simply ask the Class Wizard to map the [Calculate]/[Add] message to this function? Of course we can:

Step 11:

i. Select [View]/[ClassWizard ...]/[Message Maps]. Make sure CCalcView is displayed in the [Class Name:] box.

ii. Select ID_CALCULATE_ADD from the [IDs:] list, select COMMAND from the [Messages:] list, then press the [Add function ...] button. The [Add member function] dialog should appear suggesting the name "OnCalculateAdd" for the new function. Instead, type in the name "OnAdd", this should "wire" the [Calculate]/[Add] menu item to the same function that the [Add] button is wired to.

iii. Repeat this procedure for the other [Calculate] menu items.

iv. Build and test the application.

2.10     Toolbars

Conventionally, the most commonly used menu items have corresponding toolbar buttons that intermediate users can use to quickly select the item. Once the menu items have been created and wired to the application, creating corresponding toolbar buttons is easy. We simply assign to them the resource identifier of the menu item they represent.

Like menus and dialogs, toolbars are application resources that are created and modified by a corresponding resource editor which appends toolbar elements to the resource script in the Calc.rc file. The Toolbar Editor allows programmers to add buttons to the main frame's toolbar, create a bitmap image for the button using the editor's [Graphics] and [Colors] toolbars, then use the button's property sheet to select its ID (which should be the same as its corresponding menu item), and to specify its prompt and tool tip text.

2.10.1    Tool Tips

When the mouse passes over a toolbar button, not only does a prompt appear in the application's status bar, but a tiny yellow window called a tool tip pops up over the button explaining what the button does. The status bar text and the tool tip text are both specified in the [Prompt:] box of the button's property sheet, separated by a newline character, "\n":

status bar text \n tool tip text

2.10.2    Adding [+], [X]. [-], and [/] Buttons to the Toolbar

Like the menu bar, the toolbar is a child window of the application's main frame:

Step 12:

i. Open the MAINFRAME  toolbar in the toolbar editor by double clicking on IDR_MAINFRAME  node in the [Workspace]/[Resource View]/[Toolbar] tree control.

ii. To create a new toolbar button for adding arguments, select the blank space on the end of the toolbar, then select [View]/[Properties] to view the property sheet for this new button.

iii. Choose ID_CALCULATE_ADD in the [ID:] list, the same ID used by the [Calculate]/[Add] menu item. The prompt for this menu item should now appear in the [Prompt:] box.

iii. Append to this prompt the string: "\nResult = Arg1 + Arg2".

iv. Use the [Graphics] and [Colors] toolbars to create a "+" symbol on the button.

v. Repeat this procedure to create buttons corresponding to the [Mul], [Div], and [sub] items on the [Calculate] menu.

vi. Build and test the application.

Here is a snapshot of the Toolbar Editor at the moment the properties for the [+] button were being edited:

2.11     Editing the String Table

Localization means customizing a program for use in an ethno-linguistic locale (French-speaking Quebec, for example), and internationalization means designing a program so that it is easy to localize. Among other things, localization includes changing every prompt, label, caption, warning, and error message into the local language. This can be a lot of work if these strings are embedded in the source code.

The most important manifestation of internationalization in MFC is the string table. Like buttons, boxes, and menus, strings may be treated as resources. Instead of referring directly to a particular string, a program can refer to a string by using its resource identifier, which locates the string in the application's string table. The string table can be edited by using the Visual C++ string table editor.

For example, the string that appears in the calculator's title bar is a resource with identifier IDR_MAINFRAME. Let's change this string to something more personal:

Step 13:

i. Open the string table in the string table editor by double clicking on the [String Table] node in the [Workspace]/[Resource View]/[String Table] tree control.

ii. Select the IDR_MAINFRAME entry, then select [View]/[Properties...] (or double click on the entry. Replace the first occurrence of "Calc" by "My Calculator" or some other string. (Students should place their names in the title bar.)

iii. Build and test the application.

Here's a snapshot of editing IDR_MAINFRAME using the table editor:

2.12     Editing Icons

Icons are also considered application resources. [Calc resources]/[Icons] shows that our calculator has two pre-defined icons with resource identifiers IDR_MAINFRAME and IDR_CALCTYPE. The first icon is the icon for the Calc.exe file. The second icon represents files of saved calculator data. (Right now our calculator doesn't save any data.) Double clicking on one of these resource identifiers opens the icon in the Visual C++ icon editor. The icon editor is a self-explanatory visual bitmap editor.

2.13     Editing Configurations

In Appendix 2 we saw that in addition to header, source, and resource files, every project also contains build configurations. A build configuration is a collection of compiler, linker, and debugger settings. The active configuration is used by the compiler and linker each time we build the project.

We can add, remove, and list a project's configurations using the [Build]/[Configurations...] dialog box. Every MFC project starts life with a release configuration and a debug configuration. By default, the debug configuration is the active configuration. We can use the [Build]/[Set Active Configuration] dialog box to change the active configuration.

We can only run a program under the debugger if the program was built using the debug configuration. This is because the debug configuration tells the linker to include library code that is needed by the debugger to step through the application. Unfortunately, all this extra code makes the executable file quite a bit larger. For example, Calc/Debug/Calc.exe is currently 121 KB.

After a program is debugged, we can change the active configuration to the release configuration, which leaves out the code needed by the debugger:

Step 14: Select "Calc-Win32 Release" in the [Build]/[Set Active Configuration ...] dialog box. Press the [OK] button, then re-build the application.

Here's a snapshot of the [Set Active Project Configuration] dialog:

Calc/Release/Calc.exe is now a trim 36 KB, about 30% of the size of Calc/Debug/Calc.exe, the executable built from the debug configuration.

2.14     Dynamic versus Static Linking

One of the reasons Calc.exe is so tiny is because it doesn't contain any of the MFC library code! Did the linker fail to find the MFC libraries? No. Instead, the first time an application calls an MFC library function Windows automatically locates the code and links it into the application's address space. This is called dynamic linking, and a library that can be dynamically linked is called a dynamic link library (DLL).

Note that the MFC code may already be in main memory as part of the address space of another application. In this case the address spaces of both applications can overlap. Thus, dynamic linking not only saves disk space—because .exe files will be smaller —it also saves memory, because the same library code won't be loaded multiple times.

Of course if Windows doesn't find the MFC DLLs on the host computer, i.e., the computer running the application, then the application immediately crashes. This means dynamic linking is a bad idea for commercial software that needs to run on customer computers which almost certainly will not have the MFC DLLs. Even computers with different versions of Visual C++ won't have the right DLLs!

To remedy this situation, we must specify that MFC libraries should be linked into the executable by the linker. This is called static linking. (Warning: The student edition of Visual C++ doesn't allow static linking. This prevents "students" from creating commercial applications using discounted software.)

Step 15:

i. Select "Use MFC in a Static Library" in the "Microsoft Foundation Classes" box of the [Project]/[Settings ...]/[General] dialog box. Make sure "Win32 Release" is displayed in the [Settings For:] box, then press the [OK] button.

ii. Build and test your application one last time.

Here's a snapshot of the [Project Settings] dialog:

Of course now Calc/Release/Calc.exe is now a tubby 320 KB. Almost three times as large as the dynamically linked debug configuration, and almost nine times as large as the dynamically linked release configuration!

2.15     Programming Notes

2.15.1    Visual Programming

The Dialog Editor is a primitive example of a visual programming environment (VPE). (Without it,  Visual C++ would need to change its name to "Textual C++".) A VPE allows programmers to build programs by using a mouse to drag icons representing objects called components from a toolbar into a window called a form, which represents the program's dispatcher and main window. The programmer uses VPE controls to connect and  initialize the components. Finally, the VPE "compiles" the form into a library or an executable program:

Visual Basic, Delphi, and HyperCard are examples of popular VPEs. Java Beans and Active X controls are examples of components. Although a programmer wouldn't use a VPE to build a complex program, VPEs are often used to build simple programs, prototypes, and graphical user interfaces.

A successful VPE allows programmers to add components created by third parties to its component toolbar. Thus, an application created by company A might be developed using a VPE purchased from company B and components purchased from companies X, Y, and Z. Furthermore, A's application may be implemented in BASIC, B's VPE may be implemented in Pascal, X's components may be implemented in C++, and Y and Z's components may be implemented in Java!

But how will company B's VPE know how to initialize and connect components created by companies X, Y, and Z? At the time company B developed their VPE, they probably had no knowledge of these components or the fly-by-night companies that built them. The answer is that the VPE must be able to ask any component— regardless of its implementation language —what interface it implements: what are its member variables, what are its member functions, and what messages does it send.

But how will the VPE ask a component about its interface? Obviously the component will need to implement a meta interface with functions like GetMemberFunctions(), GetMemberVariables(), and GetMessages(). But of course the meta interface functions might be called something else, so how will the VPE ask a component what meta interface it implements? Will this require a meta meta interface?

One way out of an infinite regress of meta interfaces is to develop a standard meta interface, sometimes called a meta object protocol, or MOP. IDL, COM, and Java Reflection are examples of "standard" meta interfaces.("Standard" is quoted because none of these standards are official. Here we are using "standard" to simply mean "accepted by more than one company".) We are now ready to define "component":

A component is an object that implements a standard meta interface.

According to this definition, the controls on the Dialog Editor's toolbar aren't really components, because they don't implement a standard meta interface. Instead, they are hardwired into the editor. However, we can add real components called Active X controls to the Dialog editor's [Controls] toolbar. Later we will see how this is done.

2.15.2    Resources, Resource Identifiers, and Resource Editors

The Dialog editor is an example of a resource editor, and the dialog boxes it creates are examples of resources. Visual C++ also has resource editors for creating menus, toolbars, icons, and other types of resources.

We can identify resources with the system classes defined in the last chapter, and we can identify resource instances, such as a particular menu or dialog box, with system objects. To make this idea clearer, each time our calculator program runs— and there could be many calculators running at the same time —the operating system creates a calculator object containing button, box, menu, and label objects. Although many calculators could be running at the same time, they would all look the same. This is because they are all instances of the same pattern or class. When we use a Dialog Editor to create a form, we are really creating this pattern, not an actual dialog box object.

How do we talk about system objects and classes inside of an application? We use identification numbers called resource identifiers (or resource descriptors) to refer to resources, and 32 bit words called handles to refer to resource instances. We can view our resource identifiers using [View]/[Resource Symbols ...]

Thus, we can think of MUL as the system class of all buttons of a certain size, position, and caption, i.e., "Mul". IDC_MUL, 1002, is the identification number for the MUL class. Each time a calculator is created, i.e., each time an instance of the CALC_FORM class (identification number 101) is created, an instance of the MUL class will also be created.

2.15.2.1         Windows are Bodies

The collaboration between CCalcView and CALC_FORM is an instance of the Wrapper-Body design pattern introduced in Chapter 1. CCalcView objects are wrappers and CALC_FORM objects are bodies. Each CCalcView application object contains a reference to a CALC_FORM system object called m_hWnd, which is inherited from the CWnd base class of CView:

Of course m_hWnd isn't an ordinary C++ pointer or reference, it's a window handle, HWND, which is simply a 32 bit identification number used by the operating system to locate the actual object.

2.15.2.2         Resource Scripts

Actually, the real product of a resource editor is an "element" in a resource script, which is similar to an element such as a link or image in an HTML file. The resource script for our calculator is contained in a file called Calc.rc. Use [File]/[Open]/[Open As]/[Text] to open this file. Here is the element that describes the calculator dialog box resource:

IDD_CALC_FORM DIALOG DISCARDABLE  0, 0, 215, 111
STYLE WS_CHILD
FONT 8, "MS Sans Serif"
BEGIN
   EDITTEXT    IDC_ARG1,26,36,71,14,ES_AUTOHSCROLL
   CTEXT       "Argument #1",IDC_STATIC,26,18,71,14
   EDITTEXT    IDC_ARG2,109,36,71,14,ES_AUTOHSCROLL
   CTEXT       "Argument #2",IDC_STATIC,109,17,71,14
   EDITTEXT    IDC_RESULT,68,59,71,14,ES_AUTOHSCROLL | ES_READONLY
   CTEXT       "Result",IDC_STATIC,22,61,37,15
   PUSHBUTTON  "Add",IDC_ADD,15,82,41,17
   PUSHBUTTON  "Mul",IDC_MUL,105,82,41,17
   PUSHBUTTON  "Sub",IDC_SUB,60,82,41,17
   PUSHBUTTON  "Div",IDC_DIV,150,82,41,17
   LTEXT       "Static",IDC_STATIC,147,42,19,8
END

The resource script also contains elements that describe the toolbar and menus. When the application is compiled, a special resource compiler will be used to compile the resource script, which will then be linked to the executable, along with object files and libraries:

2.15.3    User Interface Design Principles

While MFC makes it easy to build an application with a GUI, building a good GUI requires a specialist with training in psychology, ergonomics, graphic design, marketing, and other important human factors. There are many examples of applications that performed useful functions, but were ignored because they were needlessly difficult to learn and use. It is important to become familiar with the basic user interface design principles listed below, because when a user interface specialist isn't available, we become the "specialist".

Studies show that while some people become more productive when they use a computer, others actually become less productive [LAN]. Not surprisingly, aside from people under the age of 12, the people who become more productive tend to be people with good analytic reasoning skills, good short-term memories, good typing skills, and previous experience using computers. In short, people who either are or could be programmers.

This is partly because programmers who design user interfaces often don't have much experience with psychology, culture gaps, ergonomics, graphic design, marketing, and other important user interface design factors. A programmer's model of human-computer interaction is often based on his own experiences, experiences which suggest that nothing could be more natural than typing Ctrl-Alt-Del to log on to a computer!

User interface design is discussed at length in [SHN] and [LAN], while [SOM] contains an entire chapter on the topic. We will be content to list several principles from these sources.

2.15.3.1         Familiarity

The user interface should reflect the application domain. Graphical user interfaces should employ metaphors and icons that relate to the application domain. For example, an on-screen control panel should resemble the physical control panel it will replace. Messages, prompts, and commands should use simple, natural language and application domain terminology. Follow look-and-feel standards such as OSF/Motif or OPEN LOOK if possible.

2.15.3.2         Consistency

Similar sequences of actions should produce similar results, and similar situations should require similar sequences of actions. Input formats should be the same as output formats. Messages, prompts, and commands should employ identical terminology, syntax, and style.

2.15.3.3         Recoverability

Allow users to undo and redo operations whenever possible. Allow users to abort operation sequences. Always restore the application to its original state.

2.15.3.4         Robustness

Design systems to be tolerant of user errors. If possible, prevent users from making errors. For example, deactivate menu selections when they don't make sense in the application's current state. Validate all user inputs. If the input doesn't make sense, clearly explain the problem, give the user some suggestions, then start over. Error messages should be positive and constructive in tone. Avoid error messages like "Error of Type 6", "invalid input", and "NO, YOU IDIOT!". If recovery from an error is impossible, give the user a chance to save his data before the application terminates.

2.15.3.5         Provide ways to customize the user interface

The look and feel of a user interface should depend on an explicitly represented user profile, which is easy to change. A user profile contains information about the user's preferences, job, skill level, authorization level, and locale:

A locale is an ethno-linguistic region, such as French-speaking Quebec. (The ISO has defined a standard list of locales and their codes.) Locales not only determine the user interface's language, but can also determine character sets, calendars, monetary units, and formats of dates, addresses, and phone numbers.

2.16     Review Problems

2.16.1    Problem

Use the [Workspace]/[Class View] tree control to determine the number of public, protected, and private member functions and member variables were generated by the Class Wizard for the Calc project:

# public member functions = ?
# public member variables = ?
# protected member functions = ?
# protected member variables = ?
# private member functions = ?
# private member variables = ?

How many global functions and variables were generated? What were they?

2.16.2    Problem

Give some examples of functions that are part of the Calc project but that are neither generated by the App Wizard nor are generated by the programmer.

2.16.3    Problem

Use the [Workspace]/[File View] tree control to determine the number of header and source files were generated by the Class Wizard for the Calc project.

2.16.4    Problem

Like any tree, a tree control instantiates the Composite Design Pattern. In this case parent nodes are composites, while leaf nodes are atoms. Draw and object diagram showing how the  [Workspace]/[File View] tree control might be represented in the computer's memory. Your diagram only needs to show the CalcView.cpp, CalcView.h, CalcDoc.cpp, and CalcView.h files together with all of their ancestors, including the workspace itself.

2.17     Programming Problems

2.17.1    Problem: Hello, Earth!

Create a "Hello, Earth!" program with a form view:

2.17.2    Problem: Temperature Converter

Create a calculator that converts Fahrenheit temperature to centigrade temperatures and vice-versa. Here's a snapshot of the form view:

Notice that the calculator has no [Convert] button. This means the form view will need to have handler functions that will be automatically called each time one of the edit boxes is about to change. (Hint: an edit box sends the EN_UPDATE message to its form view just before it is about to display altered text.)

One tricky detail is that the [Fahrenheit] box will change each time a new digit is entered into the [centigrade] box, and vice-versa. This is no problem as long as digits are being entered, but when a decimal point is entered, then the [Fahrenheit] box should not be updated. Try comparing the value read from the [centigrade] box with its previous value, and only update the  [Fahrenheit] box if the two are different.

In case you forgot, here are the conversion formulas:

m_cent = 5.0/9.0 * (m_fahr - 32);
m_fahr = 9.0/5.0 * m_cent + 32;

2.17.3    Problem: Converters

The previous problem can be generalized to converters that convert between any two units: miles to meters, quarts to liters, ounces to grams, dollars to yen, etc.

2.17.4    Problem: Keypad Calculator

Most Windows installations include a calculator under the [Start]/[Programs]/[Accessories]/[Calculator] menu. In this problem you will create your own version of a key pad calculator called Calc. The user interface of Calc is a form that consists of a 4 x 5 grid of buttons and an edit box that serves as the calculator's display:

The calculator's view, CCalcView, encapsulates three member variables:

int m_display;    // = num entered = num displayed
int m_result;     // = the current result
Function m_fun;   // = previous function

where Function is an enumeration type:

enum Function {NOOP, SUM, SUB, DIV, MUL};

Numbers are entered into the display, and therefore m_display, by pressing the digit keys, and the [+/-], [1/x], and [.] keys. Pressing a function key or the [=] key:

1. Updates the current result by combining the displayed number with the previous current result using the previous function.

2. Displays the current result.

3. Updates the previous function to correspond to the button pressed, or to NOOP if the [=] key is pressed.

The [C] button sets the display to 0. The [CM] button sets the display and current result to 0.

2.17.5    Problem

Add trig, log, and exponential functions to your calculator. Note: log and exponential functions are the binary kind that allow the caller to specify the base.

2.17.6    Problem: Mine Sweeper

Most Windows installations include the Mine Sweeper game under the [Start]/[Programs]/[Accessories]/[Games] menu. In this problem you will create your own version of Mine Sweeper called Sweeper. We begin with some definitions.

A trap is a place where a mine can be hidden. Probing a mined trap causes it to "explode", ending the game. Probing an un-mined trap causes it to reveal how many of its neighboring traps are mined. If the traps are arranged in a grid pattern, this will be a number between 0 and 8. Here's the beginning of a partial implementation:

class Trap
{
public:
   Trap(bool mined = false);
   int Probe() { m_probed = true; return m_minedNbrs; }
   bool IsMined() { return m_mined; }
   bool IsProbed() { return m_probed; }
   // etc.
private:
   bool m_mined;     // = hiding a mine?
   int m_minedNbrs; // = # of mined neighbors
   bool m_probed;       // = already probed?
};

A mine field encapsulates an N x N array of trap pointers, where N is a pre-defined constant:

#define N 8 // = # rows & cols in a mine field

Initially, all of the traps are un-probed and approximately 20% have been randomly selected and mined. Here's a partial implementation:

class MineField
{
public:
   MineField(); // creates & inits traps
   // i = linear index (handy)
   Trap* GetTrap(int i) { return m_traps[i/N][i%N]; }
   // etc.
private:
   Trap* m_traps[N][N];
   int m_mines;  // = # mined traps (appx 20% of N)
   int m_probes; // = # probed traps
};

The MineField constructor must create and initialize each trap. Initialization is tricky. First, 20% of the traps must be randomly selected and mined. Second, m_minedNbrs must be computed for each trap.

Suggestion: Create a separate initialization member function that gets called by the constructor, but can be subsequently called to re-start the game.

Suggestion: Use the [Insert]/[New Class...]/[New Class] dialog box to create the Trap and MineField classes. (Be sure to select "Generic Class" for the class type.) The appropriate header and source files will be automatically created and added to the project. Don't forget to include these header files in header files where references to traps and mine fields are made.

Sweeper's view class, CSweepView, encapsulates a mine field member variable:

MineField m_field; // the game's only mine field
  

Sweeper's user interface is a form consisting of an N x N grid of small, square buttons, all the same size. Initially, none of the buttons is captioned. The button in row i, column j of the form's grid corresponds to the trap in row i,, column j of m_field, the view's mine field.

Double clicking on a button probes the corresponding trap. If the trap is mined, the computer beeps, a message box containing the message "BOOM!" is displayed, the button caption changes to "!!!", and the game is over. Otherwise, the button caption changes to reveal the number of mined neighbors.

Dynamically changing the caption of a button requires creating a wrapper object for that button. This can be done dynamically from the button's resource identifier using CWND::GetDlgItem(), but only call this if you are sure the corresponding system object has been created. For example, assume a button with resource identifier IDC_BUTTON_9 is pressed. Here's what its handler should do:

Trap* t = m_field.GetTrap(9); // get trap #9
if (t->IsMined())
{ 
   // player loses
}
else if (!t->IsProbed())
{
   CString str; // create new caption
   str.Format("%d", t->Probe()); // convert int to CString
   CButton* b = (CButton*)GetDlgItem(IDC_BUTTON_9); // create wrapper
   b->SetWindowText(str); // install new caption
   if (N - m_mines <= ++m_probes)
   {
      // player wins
   }
}

As a convenience to players, single clicking an un-probed button changes its caption to "M", indicating that the player believes the corresponding trap is mined. The game ends either when a mined trap is probed or when the player probes the last un-mined trap.

2.17.7    Problem

Add a menu item called "New Game" to the [File] menu of Sweeper. Selecting this item re-initializes the mine field (hiding mines in new, randomly selected traps) and erases all of the button captions.

2.17.8    Problem: Craps

Craps is a casino game in which a player makes a wager from his bank (i.e., the total amount of cash the player has to bet), then begins rolling a pair of dice. Of course the wager may not exceed the bank (that's how legs get broken).

The sum of the dice is computed after each roll:

m_die1 = (rand() % 6) + 1; // 1 <= m_die1 <= 6
m_die2 = (rand() % 6) + 1; // 1 <= m_die2 <= 6
point = m_die1 + m_die2;   // 2 <= point <= 12

The player immediately looses if point == 2 (snake eyes), 3 (craps), or 12 (box cars).

If the player is "coming out", i.e., beginning a new game, then the player immediately wins if point == 7 or 11, otherwise, point becomes the target point:

m_point = point;

The player will roll again and again until either point == m_point, in which case the player wins, or point == 2, 3, 7, 11, or 12, in which case the player loses. Notice that point == 7 or 11 is considered a win if the player is coming out, but a loss, otherwise.

If the player wins, then the new bank is the old bank plus the wager:

m_bank += m_wager;

If the player loses, then the wager is deducted from the bank:

m_bank -= m_wager;

In either case, a message box pops up with a beep saying if the player won or lost, and a new game begins:

m_coming_out = TRUE;

You'll need to seed the random number generator in the form view's constructor:

srand(time(0));

The time() function is declared in <time.h>.

Here's a snapshot of the application window:

The [Dice] and [Betting] boxes are called "Group Boxes." As their name suggests, they can be used for grouping controls together. The [New Game] button sets the coming out flag back to TRUE. Of course the wager is deducted from the bank.



[1] There is a strong analogy between DDX_Text() with its CDataExchange parameter and Serialize() with its CArchive parameter. Serialize() will be discussed in Chapter 3.