Chapter 17

Building Menus and Dialogs


CONTENTS

Users tell programs to do things by choosing menu items or by clicking buttons on dialogs. They give the application details about their request by typing in edit boxes, choosing from list boxes, selecting radio buttons, checking or unchecking radio buttons, and more. This chapter builds a simple application that has menu items and dialogs, to illustrate the coding issues involved.

Building an Application That Displays a String

In this chapter, you will see how to develop an application very much like the traditional "Hello, world!" of C programming. The application simply displays a text string in the main window. The document (what you save in a file) contains the string and a few settings. There is a new menu item to bring up a dialog box to change the string and the settings, which control the appearance of the string. This is a deliberately simple application so that the concepts of adding menu items and adding dialogs are not obscured by trying to understand the actual brains of the application. So bring up Developer Studio and follow along.

Creating an Empty Shell with AppWizard

First, use AppWizard to create the starter application. Choose File, New and then Project Workspace. Name the project ShowString so that your class names will match those shown here. Click Create.

It doesn't matter much whether you choose SDI or MDI, but MDI will allow you to see for yourself how little effort is required to have multiple documents open at once, so choose MDI. Choose US English and then click Next.

The ShowString application needs no database support and no OLE support, so click Next on each of these steps. In AppWizard's Step 4 dialog box, set to work selecting a docking toolbar, status bar, printing and print preview, context-sensitive help, and 3-D controls, and then click Next. Choose source-file comments and shared DLL and then click Next. The class names and file names are all fine, so click Finish. Figure 17.1 shows the final confirmation dialog.

Figure 17.1 : AppWizard summarizes the design choices for ShowString.

Displaying a String

The ShowString application displays a string that will be kept in the document. You need to add a member variable to the document class, CShowStringDoc, and add loading and saving code to the Serialize() function. You can initialize the string by adding code to OnNewDocument() for the document and, in order to actually display it, override OnDraw() for the view.

Member Variable and Serialization  Add a private variable to the document and a public function to get the value, by adding these lines to ShowStringDoc.h:
private:
CString string;
public:
CString GetString() {return string;}

The inline function will give other parts of your application a copy of the string to use whenever necessary but make it impossible for other parts to change the string.

Next, change the skeleton CShowStringDoc::Serialize() function provided by AppWizard to look like Listing 17.1. Since you used the MFC CString class, the archive has operators << and >> already defined, so this is a simple function to write. It fills the archive from the string when you are saving the document and fills the string from the archive when you are loading the document from a file.


Listing 17.1  ShowStringDoc.cpp-CShowStringDoc::Serialize()

void CShowStringDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        ar << string;

    }

    else

    {

        ar >> string;

    }

}


Initializing the String  Whenever a new document is created, you want your application to initialize string to "Hello, world". A new document is created when the user chooses File, New. This message is caught by CShowStringApp (the message map is shown in Listing 17.2) and handled by CWinApp::OnFileNew(). Starter applications generated by AppWizard call OnFileNew() to create a blank document when they run. OnFileNew() calls the document's OnNewDocument() which actually initializes the member variables of the document.

Listing 17.2  ShowString.cpp-Message Map

BEGIN_MESSAGE_MAP(CShowStringApp, CWinApp)

	//{{AFX_MSG_MAP(CShowStringApp)

	ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

		// NOTE - the ClassWizard will add and remove mapping macros here.

		//    DO NOT EDIT what you see in these blocks of generated code!

	//}}AFX_MSG_MAP

	// Standard file based document commands

	ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)

	ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

	// Standard print setup command

	ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)

END_MESSAGE_MAP()


AppWizard gives you the simple OnNewDocument()shown in Listing 17.3.


Listing 17.3  ShowStringDoc.cpp-CShowStringDoc::OnNewDocument()

BOOL CShowStringDoc::OnNewDocument()

{

    if (!CDocument::OnNewDocument())

        return FALSE;



    // TODO: add reinitialization code here

    // (SDI documents will reuse this document)



    return TRUE;

}


Take away the comments and add this line in their place:


string = "Hello, world!";

(What else could it say, after all?) Leave the call to CDocument::OnNewDocument() because that will handle all the other work involved in making a new document.

Getting the String onto the Screen  Whenever your view needs to be drawn, such as when your application is first started, resized, restored, or when a window that had been covering it is taken away, the view's OnDraw() function is called to draw it. AppWizard has provided a skeleton, shown in Listing 17.4.

Listing 17.4  ShowStringView.cpp-CShowStringView::OnDraw()

void CShowStringView::OnDraw(CDC* pDC)

{

    CShowStringDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);



    // TODO: add draw code for native data here

}


OnDraw() takes a pointer to a device context, discussed in the "Understanding Device Contexts" section of Chapter 11, "Drawing on the Screen." The device context class, CDC, has a member function called DrawText() that draws text on the screen. It is declared like this:


int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )

The CString to be passed to this function is going to be the string from the document class, which can be accessed as pDoc->GetString(). The lpRect is the client rectangle of the view, returned by GetClientRect(). Finally, nFormat is the way the string should be displayed; for example, DT_CENTER means that the text should be centered from left to right within the view. DT_VCENTER means that the text should be centered up and down, but this works only for single lines of text that are identified with DT_SINGLELINE. Multiple format flags can be combined with |, so DT_CENTER|DT_VCENTER|DT_SINGLELINE is the nFormat that you want. The drawing code to be added to CShowStringView::OnDraw() looks like this:


CRect rect;

GetClientRect(&rect);

pDC->DrawText(pDoc->GetString(), &rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

This sets up a CRect and passes its address to GetClientRect(), which sets the CRect to the client area of the view. DrawText() draws the document's string in the rectangle, centered vertically and horizontally.

At this point, the application should display the string properly. Build it, execute it, and you should see something like Figure 17.2. You have quite a lot of functionality: menus, toolbars, status bar, and so on, but nothing real, yet. Starting with the next section, that will change.

Figure 17.2 : ShowString starts simply, with the usual greeting.

Building the ShowString Menus

AppWizard creates two menus for you, shown in the ResourceView window in Figure 17.3. IDR_MAINFRAME is the menu shown when no file is open; IDR_SHOWSTTYPE is the menu shown when a ShowString document is open. Notice that IDR_MAINFRAME has no View or Window menus and that the File menu is much shorter than the one on the IDR_SHOWSTTYPE menu, with only New, Open, Print Setup, recent files, and Exit items.

Figure 17.3 : AppWizard creates two menus for ShowString.

You are going to add a menu item to ShowString, so the first decision is where to add it. The user will be able to edit the string that is displayed and to set the format of the string. You could add a Value item to the Edit menu that brings up a small dialog for only the string and then create a Format menu with one item, Appearance, that brings up the dialog to set the appearance. But the choice you are going to see here is to combine everything into one dialog and then put it on a new Tools menu, under the Options item. You may have noticed already that more and more Windows applications are standardizing on Tools, Options as the place for miscellaneous settings.

Do you need to add the item to both menus? No. When there is no document open, there is nowhere to save the changes made with this dialog. So only IDR_SHOWSTTYPE needs to have a menu added. Bring up the menu by double-clicking on it in the ResourceView window. At the far right of the menu, after Help, is an empty menu. Click on it and type &Tools. The Properties window will appear; pin it to the background by clicking the pushpin. The Caption box will contain &Tools. The menu at the end becomes the Tools menu, with an empty item underneath it; another empty menu then appears to the right of the Tools menu, as shown in Figure 17.4.

Figure 17.4 : Adding the Tools menu is easy in the ResourceView window.

TIP
The & in the Caption edit box indicates which letter should act as the key to select the menu item when it is displayed. This letter will be underlined in the menu. There is no further work required on your part.

Click the new Tools menu and drag it between the View and Window menus, corresponding to the position of Tools in products like Developer Studio and Microsoft Word. Next, click the empty subitem. The Properties box changes to show the blank properties of this item; change the caption to &Options and enter a sensible prompt, as shown in Figure 17.5.

Figure 17.5 : The menu item Tools, Options will control everything that ShowString does.

All menu items have a resource ID, and this resource ID is the way the menu items are connected to your code. Developer Studio will choose a good one for you, but it doesn't appear in the Properties box right away. Click on some other menu item and then click on Options again, and you'll see that the resource ID is ID_TOOLS_OPTIONS. Alternatively, press Enter when you are finished and the highlight will move down to the empty menu item below Options. Press the UpArrow cursor key to return the highlight to the Options item.

If you'd like to provide an accelerator, such as Ctrl+C for Edit, Copy, this is a good time to do it. Click Accelerator in the ResourceView window and then double-click IDR_MAINFRAME, the only Accelerator table in this application. At a glance, you can see what key combinations are already in use. Ctrl+O and Ctrl+P are already taken, but Ctrl+T is available. To connect Ctrl+T to Tools, Options, follow these steps:

  1. Click on the empty line at the bottom of the Accelerator table. If you have closed the Properties box, bring it back by choosing Edit, Properties and then pin it in place. (Alternatively, double-click the empty line to bring up the Properties box.)
  2. Click the drop-down list box labeled ID and choose ID_TOOLS_OPTIONS from the list, which is in alphabetical order. (There are a lot of entries before ID_TOOLS_OPTIONS; drag the elevator down almost to the bottom of the list or start typing the resource ID-by the time you enter ID_TO the highlight will be in the right place.)
  3. Enter T in the Key box and make sure that the Ctrl check box is checked and that the Alt and Shift boxes are unchecked. Alternatively, click the Next Key Typed button and then type Ctrl+T, and the dialog will be filled in properly.
  4. Click on another line in the Accelerator table to commit the changes.

Figure 17.6 shows the properties box for this accelerator after clicking on the newly entered line again.

Figure 17.6 : Keyboard accelerators are connected to resource IDs.

What happens when the user chooses this new menu item, Tools, Options? A dialog is displayed. So, tempting as it may be to start connecting this menu to code, it makes more sense to build the dialog first.

Building the ShowString Dialogs

As discussed in Chapter 7 "Dialog and Controls," a dialog box is a special kind of window. The controls on it are also windows. Because they are contained within the dialog box, they are child windows, and the dialog box is their parent window. There are two types of dialog boxes:

The examples of modal and modeless dialog boxes are both common dialog boxes available to any Windows application. But of course many applications need dialogs that have not been conveniently provided by Microsoft, and ShowString is no exception. ShowString is actually going to have two custom dialogs: one brought up by Tools, Options, and an About box. An About box has been provided by AppWizard but needs to be changed a little, and the Options box will be built from scratch.

ShowString's About Box

Figure 17.7 shows the About box that AppWizard makes for you; it will do as an About box. It contains the name of the application and the current year. To view the About box for ShowString, click on the ResourceView tab in the project workspace window. Expand the Dialogs list by clicking the + icon next to the word Dialogs, and then double click on IDD_ABOUTBOX to bring up the About dialog box resource.

Figure 17.7 : AppWizard makes an About box for you.

You might want to add a company name to your About box. Here's how to add "Que Books," as an example. Click the line of text that reads Copyright 1996, and it will be surrounded by a selection box. Bring up the properties box, if it is not up. Edit the caption to add Que Books at the end; the changes are reflected immediately in the dialog.

TIP
If the rulers you see in Figure 17.7 don't appear when you open IDD_ABOUTBOX in Developer Studio, you can turn them on by choosing Layout, Guide Settings and selecting the Rulers and Guides radio button in the top half of the Guide Settings dialog box.

I decided to add a text string reminding users what book this application is from. Here's how to do that:

  1. Size the dialog a little taller by clicking the whole dialog to select it and then clicking the sizing square in the middle of the bottom border and dragging the bottom border down a little. (This visual editing is what gave Visual C++ its name when it first came out.)
  2. In the floating toolbar called Controls, click the button labeled Aa to get a static control, which means a piece of text that the user cannot change, perfect for labels like this. Click within the dialog under the other text to insert the static text there.
  3. In the properties box, change the caption from Static to Using Visual C++ 5. The box automatically resizes to fit the text.
  4. Holding down the Ctrl key, click on the other two static text lines in the dialog. Choose Layout, Align Controls and then Left, which aligns the edges of the three selected controls. The one you select last stays still, and the others move to align with it.
  5. Choose Layout, Space Evenly, and then Down. These menu options can save you a great deal of dragging, squinting at the screen, and then dragging again.

The About box should look like Figure 17.8.

Figure 17.8 : In a matter of minutes, your About box can be customized.

TIP
All the Layout menu items are on the Dialog toolbar.

ShowString's Options Dialog

The Options dialog is pretty simple to build. First, make a new dialog by choosing Insert, Resource and then double-clicking Dialog. An empty dialog called Dialog1 appears, with an OK and a Cancel button, as shown in Figure 17.9.

Figure 17.9 : A new dialog always has OK and Cancel buttons.

Next, follow these steps to convert the empty dialog into the Options dialog:

  1. Change the ID to IDD_OPTIONS and the caption to Options.
  2. In the floating toolbar called Controls, click the button labeled ab| to get an edit box, where the user can enter the new value for the string. Click inside the dialog to place the control and then change the ID to IDC_OPTIONS_STRING. (Control IDs should all start with "IDC" and should then mention the name of their dialog and then an identifier that is unique to that dialog.)
  3. Click the sizing squares to resize the edit box as wide as possible.
  4. Add a static label above the edit box and change that caption to String:.

You will revisit this dialog later, when adding the appearance abilities, but for now it's ready to be connected. It should look like Figure 17.10.

Figure 17.10 : The Options dialog is the place to change the string.

Making the Menu Work

When the user chooses Tools, Options, the Options dialog should be displayed. You use ClassWizard to arrange for one of your functions to be called when the item is chosen, and then you write the function, which will create an object of your dialog class and then display it.

The Dialog Class

ClassWizard will make the dialog class for you. While the window displaying the IDD_OPTIONS dialog has focus, choose View, ClassWizard. ClassWizard realizes there is not yet a class that corresponds to this dialog and offers to create one, as shown in Figure 17.11. Leave Create a new class selected and then click OK.

Figure 17.11 : Create a C++ class to go with the new dialog.

The Create New Class dialog, shown in Figure 17.12, appears. Fill it in as follows:

Figure 17.12 : The dialog class will inherit from CDialog and will not be added to the Component Gallery.

  1. Choose a sensible name for the class, one that starts with C and contains the word Dialog; this example uses COptionsDialog.
  2. The base class defaults to CDialog, which is perfect for this case.
  3. At the bottom of the dialog is a check box called Add to Component Gallery. This will let you make your dialog available to other projects; the IDD_OPTIONS dialog doesn't really qualify to be shared with others, so leave it unchecked. The Component Gallery is discussed in Chapter 29, "Power-User C++ Features."
  4. Click Create to create the class.

Perhaps you're curious about what code was created for you when ClassWizard made the class. The header file is shown in Listing 17.5.


Listing 17.5   OptionsDialog.h-Header File for COptionsDialog

// OptionsDialog.h : header file

//



/////////////////////////////////////////////////////////////////////////////

// COptionsDialog dialog



class COptionsDialog : public CDialog

{

// Construction

public:

    COptionsDialog(CWnd* pParent = NULL);   // standard constructor



// Dialog Data

    //{{AFX_DATA(COptionsDialog)

    enum { IDD = IDD_OPTIONS };

    CString     m_string;

    //}}AFX_DATA

// Overrides

    // ClassWizard generated virtual function overrides

    //{{AFX_VIRTUAL(COptionsDialog)

    protected:

    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

    //}}AFX_VIRTUAL

// Implementation

protected:

    // Generated message map functions

    //{{AFX_MSG(COptionsDialog)

        // NOTE: the ClassWizard will add member functions here

    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()

};


There are an awful lot of comments here to help ClassWizard find its way around in the file when the time comes to add more functionality, but there is only one member variable, m_string; one constructor; and one member function, DoDataExchange(), which gets the control value into the member variable, or vice versa. The source file is not much longer; it's shown in Listing 17.6.


Listing 17.6   OptionsDialog.cpp-Implementation File for COptionsDialog

// OptionsDialog.cpp : implementation file

//



#include "stdafx.h"

#include "ShowString.h"

#include "OptionsDialog.h"



#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// COptionsDialog dialog

COptionsDialog::COptionsDialog(CWnd* pParent /*=NULL*/)

    : CDialog(COptionsDialog::IDD, pParent)

{

    //{{AFX_DATA_INIT(COptionsDialog)

    m_string = _T(__);

    //}}AFX_DATA_INIT

}

void COptionsDialog::DoDataExchange(CDataExchange* pDX)

{

    CDialog::DoDataExchange(pDX);

    //{{AFX_DATA_MAP(COptionsDialog)

    DDX_Text(pDX, IDC_OPTIONS_STRING, m_string);

    //}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(COptionsDialog, CDialog)

    //{{AFX_MSG_MAP(COptionsDialog)

        // NOTE: the ClassWizard will add message map macros here

    //}}AFX_MSG_MAP

END_MESSAGE_MAP()


The constructor sets the string to an empty string; this code is surrounded by special ClassWizard comments that will enable it to add other variables later. The DoDataExchange() function calls DDX_Text() to transfer data from the control with the resource ID IDC_OPTIONS_STRING to the member variable m_string, or vice versa. This code, too, is surrounded by ClassWizard comments. Finally, there is an empty message map, because COptionsDialog doesn't catch any messages.

Catching the Message

There are seven classes in ShowString: CAboutDlg, CChildFrame, CMainFrame, COptionsDialog, CShowStringApp, CShowStringDoc, and CShowStringView. Which one should catch the command that is sent when the user chooses the menu item Tools, Options? The string and the options will be saved in the document and displayed in the view, so one of those two classes should handle the changing of the string. The document owns the private variable and will not let the view change the string unless you implement a public function to set the string. So it makes the most sense to have the document catch the message.

To catch the message, follow these steps:

  1. Bring up ClassWizard (if it is not already up).
  2. Click the Message Maps tab.
  3. Select CShowStringDoc from the Class name drop-down list box.
  4. Select ID_TOOLS_OPTIONS from the Object IDs list box on the left, and select COMMAND from the Messages list box on the right.
  5. Click Add Function to add a function that will handle this command.
  6. The Add Member Function dialog, shown in Figure 17.13, appears, giving you an opportunity to change the function name from the usual one. Do not change it; just click OK.

Figure 17.13 : ClassWizard suggests a good name for the message-catching function.

TIP
You should almost never change the names that ClassWizard suggests for message catchers. If you find that you have to, be sure to choose a name that starts with On.

What happened to CShowStringDoc when you arranged for the ID_TOOLS_OPTIONS message to be caught? The new message map in the header file is shown in Listing 17.7.


Listing 17.7   ShowStringDoc.h-Message Map for CShowStringDoc

// Generated message map functions

protected:

    //{{AFX_MSG(CShowStringDoc)

    afx_msg void OnToolsOptions();

    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()


This is just declaring the function. In the source file, ClassWizard changed the message map as shown in Listing 17.8.


Listing 17.8   ShowStringDoc.cpp-Message Map for CShowStringDoc

BEGIN_MESSAGE_MAP(CShowStringDoc, CDocument)

    //{{AFX_MSG_MAP(CShowStringDoc)

    ON_COMMAND(ID_TOOLS_OPTIONS, OnToolsOptions)

    //}}AFX_MSG_MAP

END_MESSAGE_MAP()


This arranges for OnToolsOptions() to be called when the command ID_TOOLS_OPTIONS is sent. ClassWizard also added a skeleton for OnToolsOptions():


void CShowStringDoc:: OnToolsOptions() 

{

    // TODO: Add your command handler code here

    

}

Making the Dialog Work

OnToolsOptions() should initialize and display the dialog and then do something with the value that the user provided. In order to initialize the edit control on the dialog, and to use the value that the user entered, the edit box control needs to be connected to a member variable of COptionsDialog. ClassWizard handles this very nicely. Here's what to do:

  1. Click on the Member Variables tab.
  2. Choose COptionsDialog from the Class name drop-down list box.
  3. Choose IDC_OPTIONS_STRING (the resource ID that you gave the edit box) from the list box.
  4. Click Add Variable to bring up the Add Member Variable dialog.
  5. Fill in a name that starts m_, such as m_string.
  6. The rest of the dialog is fine; Class Wizard knows the ways that people are most likely to use each control. Figure 17.14 shows the completed Add Member Variable dialog.
    Figure 17.14 : The Add Member Variable dialog connects dialog controls to members of the dialog class.

  7. Click OK to close this dialog and then click OK to close ClassWizard.

You're ready to write OnToolsOptions(), shown in Listing 17.9.


Listing 17.9   ShowStringDoc.cpp-OnToolsOptions()

]void CShowStringDoc::OnToolsOptions() 

{

    COptionsDialog dlg;

    dlg.m_string = string;

    if (dlg.DoModal() == IDOK)

    {

        string = dlg.m_string;

        SetModifiedFlag();

        UpdateAllViews(NULL);

    }



}


This code fills the member variable of the dialog with the member variable of the document (ClassWizard added m_string as a public member variable of COptionsDialog, so the document can change it) and then puts up the dialog by calling DoModal(). If the user clicks OK, the member variable of the document is changed, the modified flag is set (so that the user will be prompted to save the document on exit), and the view is asked to redraw itself with a call to UpdateAllViews(). In order for this to compile, of course, the compiler must know what a COptionsDialog is, so add this line at the beginning of ShowStringDoc.cpp:


#include "OptionsDialog.h"

At this point, you can build the application and run it. Choose Tools, Options and change the string. Click OK and you'll see the new string in the view. Exit the application, and you'll be asked whether to save the file. Save it, restart the application, and open the file again. The default "Hello world" document remains open, and the changed document is open with a different string. The application works, as you can see in Figure 17.15 (the windows are resized to let them both fit in the figure.)

Figure 17.15 : ShowString can change the string, save it to a file, and reload it.

Adding Appearance Options to the Options Dialog

ShowString doesn't have much to do, just demonstrate menus and dialogs. But the only dialog control that ShowString uses is an edit box. In this section, you add a set of radio buttons and check boxes to change the way the string is drawn in the view.

Changing the Options Dialog

It is quite simple to incorporate a full-fledged Font dialog into an application, but the example in this section is going to do something much simpler. A group of radio buttons will let the user choose among several colors. One check box will allow the user to specify that the text should be centered horizontally, and another will allow that the text be centered vertically. Because these are check boxes, the text can be either, neither, or both.

Open the IDD_OPTIONS dialog by double-clicking on it in the ResourceView window and then add the radio buttons by following these steps:

  1. Stretch the dialog taller to make room for the new controls.
  2. Click the radio button in the Controls floating toolbar and then click on the Options dialog to drop the control.
  3. Choose Edit, Properties and then pin the properties box in place.
  4. Change the resource ID of the first radio button to IDC_OPTIONS_BLACK and change the caption to &Black.
  5. Check the Group box to indicate that this is the first of a group of radio buttons.
  6. Add another radio button with resource ID IDC_OPTIONS_RED and, as caption, &Red. Do not check the Group box since the Red radio button does not start a new group but is part of the group that started with the Black radio button.
  7. Add a third radio button with resource ID IDC_OPTIONS_GREEN and, as caption, &Green. Again, do not check Group.
  8. Drag the three radio buttons into a horizontal arrangement, and select all three.
  9. Choose Layout, Align Controls, Bottom (to even them up).
  10. Choose Layout, Space Evenly, Across to space the controls across the dialog.

Next, add the check boxes by follwing these steps:

  1. Click on the check box in the Controls floating toolbar and then click the Options dialog, dropping a check box onto it.
  2. Change the resource ID of this check box to IDC_OPTIONS_HORIZCENTER and the caption to Center &Horizontally.
  3. Check the Group box to indicate the start of a new group after the radio buttons.
  4. Drop another check box onto the dialog as in Step 1 and give it the resource ID IDC_OPTIONS_VERTCENTER and the caption Center &Vertically.
  5. Arrange the check boxes under the radio buttons.
  6. Click the Group box on the Controls floating toolbar and then click and drag a group box around the radio buttons. Change the caption to Text Color.
  7. Move the OK and Cancel buttons down to the bottom of the dialog.
  8. Select each horizontal group of controls and use Layout, Center in Dialog Horizontally to neaten things up.
  9. Choose Edit, Select All and then drag all the controls up toward the top of the dialog. Shrink the dialog to fit around the new controls. It should now resemble Figure 17.16.

Figure 17.16 : The options dialog for ShowString has been expanded.

TIP
If you don't recognize the icons on the Controls toolbar, use the tooltips. If you hold the cursor over any of the toolbar buttons, a tip will pop up after a few seconds, telling you what control the button represents.

Finally, set the tab order by choosing Layout, Tab Order and clicking on the controls, in this order:

  1. IDC_OPTIONS_STRING
  2. IDC_OPTIONS_BLACK
  3. IDC_OPTIONS_RED
  4. IDC_OPTIONS_GREEN
  5. IDC_OPTIONS_HORIZCENTER
  6. IDC_OPTIONS_VERTCENTER
  7. IDOK
  8. IDCANCEL

Then click away from the dialog to leave the two static text controls as positions 9 and 10.

Adding Member Variables to the Dialog Class

Having added controls to the dialog, you need to add corresponding member variables to the class, COptionsDialog. Bring up ClassWizard and add member variables for each control. Figure 17.17 shows the summary of the member variables created. The check boxes are connected to BOOL variables; these member variables will be TRUE if the box is checked and FALSE if it is not. The radio buttons are handled differently. Only the first-the one with the Group box checked in its properties box-is connected to a member variable. That integer is a zero-based index that indicates which button is selected. In other words, when the Black button is selected, m_color will be 0; when Red is selected, m color will be 1; and when Green is selected, m_color will be 2.

Figure 17.17 : Member variables in the dialog class are connected to individual controls or the group of radio buttons.

Adding Member Variables to the Document

The variables to be added to the document are the same ones that were added to the dialog. They need to be added to the CShowStringDoc class definition in the header file, to OnNewDocument(), and to Serialize(). The top few lines of the class definition should now look like Listing 17.10.


Listing 17.10   ShowStringDoc.h-CShowStringDoc Member Variables

private:

    CString string;

    int    color;

    BOOL horizcenter;

    BOOL vertcenter;

public:

    CString GetString() {return string;}

    int    GetColor() {return color;}

    BOOL GetHorizcenter() {return horizcenter;}

    BOOL GetVertcenter() {return vertcenter;}


As with string, these are private variables with public get functions but no set functions. All these options should be serialized; the new Serialize() is shown in Listing 17.11.


Listing 17.11   ShowStringDoc.cpp-Serialize()

void CShowStringDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        ar << string;

        ar << color;

        ar << horizcenter;

        ar << vertcenter;

    }

    else

    {

        ar >> string;

        ar >> color;

        ar >> horizcenter;

        ar >> vertcenter;

    }

}


What are good defaults for these new member variables? Black text, centered in both directions, was the old behavior, and it makes sense to use it as the default. The new OnNewDocument() is shown in Listing 17.12.


Listing 17.12   ShowStringDoc.cpp-OnNewDocument()

BOOL CShowStringDoc::OnNewDocument()

{

    if (!CDocument::OnNewDocument())

        return FALSE;



    string = "Hello, world!";

    color = 0;     //black

    horizcenter = TRUE;

    vertcenter = TRUE;



    return TRUE;

}


Of course, at the moment, these member variables are never changed from these defaults. To allow the user to change the variables, you will have to change the function that handles the dialog box.

Changing OnToolsOptions()

The OnToolsOptions() function sets the values of the dialog member variables from the document member variables and then displays the dialog. If the user clicked OK, the document member variables are set from the dialog member variables and the view is redrawn. Having just added three member variables to the dialog and the document, you have three lines to add before the dialog is displayed and then three more to add in the block that's called after OK is clicked. The new OnToolsOptions() is shown in Listing 17.13.


Listing 17.13   ShowStringDoc.cpp-OnToolsOptions()

void CShowStringDoc::OnToolsOptions() 

{

    COptionsDialog dlg;

    dlg.m_string = string;

    dlg.m_color = color;

    dlg.m_horizcenter = horizcenter;

    dlg.m_vertcenter = vertcenter;

    

    if (dlg.DoModal() == IDOK)

    {

        string = dlg.m_string;

        color = dlg.m_color;

        horizcenter = dlg.m_horizcenter;

        vertcenter = dlg.m_vertcenter;

        SetModifiedFlag();

        UpdateAllViews(NULL);

    }



}


So what happens when the user brings up the dialog box and changes the value of a control, say, by unselecting Center Horizontally? The framework-through Dialog Data Exchange (DDX), as set up by ClassWizard-changes the value of COptionsDialog::m_horizcenter to FALSE. This code in OnToolsOptions() changes the value of CShowStringDoc::horizcenter to FALSE. When the user saves the document, Serialize() will save horizcenter. This is all good, but none of this code actually changes the way the view is drawn. That involves OnDraw().

Changing OnDraw()

The single call to DrawText() in OnDraw() gets a little more complex now. The document member variables are used to set the appearance of the view.

The color is set with CDC::SetTextColor() before the call to DrawText(). It's a good idea to save the old text color and restore it when you are finished. The parameter to SetTextColor() is a COLORREF, and you can directly specify combinations of red, green, and blue as hex numbers in the form 0x00bbggrr, so that, for example, 0x000000FF is bright red. Most people prefer to use the RGB macro, which takes hex numbers from 0x0 to 0xFF, specifying the amount of each color; bright red is RGB(FF,0,0). Add the lines shown in Listing 17.14 lines before the call to DrawText() to set up everything.


Listing 17.14   ShowStringDoc.cpp-OnDraw () Additions Before DrawText() Call

COLORREF oldcolor;

switch (pDoc->GetColor())

{

case 0:

    oldcolor = pDC->SetTextColor(RGB(0,0,0)); //black

    break;

case 1:

    oldcolor = pDC->SetTextColor(RGB(0xFF,0,0)); //red

    break;

case 2:

    oldcolor = pDC->SetTextColor(RGB(0,0xFF,0)); //green

    break;

}


Add this line after the call to DrawText():


pDC->SetTextColor(oldcolor);

There are two approaches to setting the centering flags. The brute-force way is to list the four possibilities (neither, horizontal, vertical, and both) and have a different DrawText() statement for each. If you were to add other settings, this would quickly become unworkable. It's better to set up an integer to hold the DrawText() flags and "or in" each flag, if appropriate. Add the lines shown in Listing 17.15 before the call to DrawText().


Listing 17.15   ShowStringDoc.cpp-OnDraw () Additions After DrawText() Call

    int DTflags = 0;

    if (pDoc->GetHorizcenter())

    {

        DTflags |= DT_CENTER;

    }

    if (pDoc->GetVertcenter())

    {

        DTflags |= (DT_VCENTER|DT_SINGLELINE);

    }


The call to DrawText() now uses the DTflags variable:


pDC->DrawText(pDoc->GetString(), &rect, DTflags);

Now the settings from the dialog box have made their way to the dialog class, to the document, and finally to the view, to actually affect the appearance of the text string. Build and execute ShowString and then try it out. Any surprises?

From Here...

This is not the last you will see of ShowString; it will reappear in Chapter 21, "Help," and throughout Part IV, "ActiveX Applications and ActiveX Controls." But there's a lot of other material to cover between here and there. The rest of this part of the book presents sample applications and how-to instructions for everyday tasks all developers face: