Chapter 9

Building a Complete Application: ShowString


In this chapter you pull together the concepts demonstrated in previous chapters to create an application that really does something. You add a menu, a menu item, a dialog box, and persistence to an application that draws output based on user settings. In subsequent chapters this application serves as a base for more advanced work.

Building an Application That Displays a String

In this chapter you 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. (Chapter 1, "Building Your First Application," covers AppWizard and creating starter applications.) Choose File, New and the Project tab. Name the project ShowString so that your class names will match those shown throughout this chapter. Click OK.

In Step 1 of AppWizard, 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 compound document support, so click Next on Step 2 and Step 3 without changing anything. 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 9.1 shows the final confirmation dialog box. Click OK.

Fig. 9.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. Documents and views were introduced in Chapter 6, "Documents and Views."

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 gives other parts of your application a copy of the string to use whenever necessary but makes it impossible for other parts to change the string.

Next, change the skeleton CShowStringDoc::Serialize() function provided by AppWizard to look like Listing 9.1. (Expand CShowStringDoc in ClassView and double-click Serialize() to edit the code.) 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. Chapter 9, "Persistence and File I/O," introduces serialization.

Listing 9.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 9.2) and handled by CWinApp::OnFileNew(). (Message maps and message handlers and discussed in Chapter 4, "Messages and Commands.") 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 9.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 9.3.

Listing 9.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 on the Screen

As you learned in Chapter 7, "Drawing on the Screen," a view's OnDraw() function is called whenever that view needs to be drawn, such as when your application is first started, resized, or restored, or when a window that had been covering it is taken away. AppWizard has provided a skeleton, shown in Listing 9.4. To edit this function, expand CShowStringView in ClassView and then double-click OnDraw().

Listing 9.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, as discussed in Chapter 7, "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 display; 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 and execute it, and you should see something like Figure 9.2. You have quite a lot of functionality: menus, toolbars, status bar, and so on, but nothing than any other Windows application doesn't have, yet. Starting with the next section, that changes.

Fig. 9.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 9.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.

Fig. 9.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 displays and to set the format of the string. You could add a Value item to the Edit menu that brings up a small dialog box for only the string and then create a Format menu with one item, Appearance, that brings up the dialog box to set the appearance. But the choice you are going to see here is to combine everything into one dialog box 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 box. So only IDR_SHOWSTTYPE needs to have a menu added. Bring up the menu by double-clicking it in the ResourceView window. At the far right of the menu, after Help, is an empty menu. Click it and type &Tools. The Properties dialog box appears; pin it to the background by clicking the pushpin. The Caption box contains &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 9.4.

Fig. 9.4 Adding the Tools menu is easy in the ResourceView window.

The & in the Caption edit box precedes the letter that serves as the mnemonic key for selecting that menu with the keyboard (for example, Alt + T in the case of Tools). This letter appears underlined in the menu. There is no further work required on your part. You can opt to select a different mnemonic key by moving the & so it precedes a different letter in the menu or menu item name (for example, T&ools changes the key from 'T' to 'o').

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 sub-item. The Properties dialog box changes to show the blank properties of this item; change the caption to &Options and enter a sensible prompt, as shown in Figure 9.5.

Fig. 9.5 The menu command 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 dialog box right away. Click some other menu item, and then click Options again; you see that the resource ID is ID_TOOLS_OPTIONS. Alternatively, press Enter when you are finished and the highlight moves down to the empty menu item below Options. Press the up-arrow 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 the + next to 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 is already taken, but Ctrl+T is available. To connect Ctrl+T to Tools, Options, follow these steps:

  1. Click the empty line at the bottom of the Accelerator table. If you have closed the Properties dialog box, bring it back by choosing View, Properties, and then pin it in place. (Alternatively, double-click the empty line to bring up the Properties dialog 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; then make sure that the Ctrl check box is selected and that the Alt and Shift boxes are deselected. Alternatively, click the Next Key Typed button and then type Ctrl+T, and the dialog box will be filled in properly.

  4. Click another line in the Accelerator table to commit the changes.

Figure 9.6 shows the Properties dialog box for this accelerator after clicking the newly entered line again.

Fig. 9.6 Keyboard accelerators are connected to resource IDs.

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

Building the ShowString Dialog Boxes

Chapter 2, "Dialog Boxes and Controls," introduced dialog boxes. This section builds on that background. ShowString is actually going to have two custom dialog boxes: one brought up by Tools, Options, and also an About dialog box. An About dialog box has been provided by AppWizard, but it needs to be changed a little; you build the Options dialog box from scratch.

ShowString's About Dialog Box

Figure 9.7 shows the About dialog box that AppWizard makes for you; it contains the name of the application and the current year. To view the About dialog box for ShowString, click 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 IDD_ABOUTBOX to bring up the About dialog box resource.

Fig. 9.7 AppWizard makes an About dialog box for you.

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

If the rulers you see in Figure 9.7 don't appear when you open IDD_ABOUTBOX in Developer Studio, you can turn them on by choosing Layout, Guide Settings and then 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 box a little taller by clicking the whole dialog box to select it, 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 box under the other text to insert the static text there.

  3. In the Properties dialog box, change the caption from Static to Using Visual C++ 5. The box automatically resizes to fit the text.

  4. Hold down the Ctrl key and click the other two static text lines in the dialog box. Choose Layout, Align Controls, 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, Down. These menu options can save you a great deal of dragging, squinting at the screen, and then dragging again.

The About dialog box should look like Figure 9.8.

Fig. 9.8 In a matter of minutes, you can customize your About dialog box.

All the Layout menu items are on the Dialog toolbar.

ShowString's Options Dialog Box

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

Fig. 9.9 A new dialog box always has OK and Cancel buttons.

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

  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 in which the user can enter the new value for the string. Click inside the dialog box to place the control and then change the ID to IDC_OPTIONS_STRING. (Control IDs should all start with "IDC" and then mention the name of their dialog box and an identifier that is unique to that dialog box.)

  3. Drag 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 box later, when adding the appearance capabilities, but for now it's ready to be connected. It should look like Figure 9.10.

Fig. 9.10 The Options dialog box is the place to change the string.

Making the Menu Work

When the user chooses Tools, Options, the Options dialog box should display. 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 creates an object of your dialog box class and then displays it.

The Dialog Box Class

ClassWizard makes the dialog box class for you. While the window displaying the IDD_OPTIONS dialog box has focus, choose View, ClassWizard. ClassWizard realizes there is not yet a class that corresponds to this dialog box and offers to create one, as shown in Figure 9.11.

Fig. 9.11 Create a C++ class to go with the new dialog box.

Leave Create a New Class selected and then click OK. The Create New Class dialog box, shown in Figure 9.12, appears.

Fig. 9.12 The dialog box class inherits from CDialog.

Fill in the dialog box as follows:

  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. Click OK to create the class.

The ClassWizard dialog box has been waiting behind these other dialog boxes, and now you use it. Click the Member Variables tab and connect IDC_OPTIONS_STRING to a CString called m_string, just as you connected controls to member variables of the dialog box class in Chapter 2, "Dialog Boxes and Controls." Click OK to close ClassWizard.

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

Listing 9.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 9.6.

Listing 9.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 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

The next step in building ShowString is to catch the command message sent when the user chooses Tools, Options. There are seven classes in ShowString: CAboutDlg, CChildFrame, CMainFrame, COptionsDialog, CShowStringApp, CShowStringDoc, and CShowStringView. Which one should catch the command? 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 to handle this command.

  6. The Add Member Function dialog box, shown in Figure 9.13, appears, giving you an opportunity to change the function name from the usual one. Do not change it; just click OK.

Fig. 9.13 ClassWizard suggests a good name for the message-catching function.

You should almost never change the names that ClassWizard suggests for message catchers. If you find that you have to (perhaps because the suggested name is too long or conflicts with another function name in the same object), be sure to choose a name that starts with On.

Click Edit Code to close ClassWizard and edit the newly added function. 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 9.7.

Listing 9.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 maps shown in Listing 9.8.

Listing 9.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 Box Work

OnToolsOptions() should initialize and display the dialog box and then do something with the value that the user provided. (This process was first discussed in Chapter 2, "Dialogs and Controls.") You have already connected the edit box to a member variable, m_string, of the dialog box class. You initialize this member variable before displaying the dialog box and use it afterwards.

OnToolsOptions(), shown in Listing 9.9, displays the dialog box. Add this code to the empty function ClassWizard generated for you when you arranged to catch the message.

Listing 9.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 box 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 brings up the dialog box by calling DoModal(). If the user clicks OK, the member variable of the document changes, the modified flag is set (so that the user is 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 see the new string in the view. Exit the application; you are 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 9.14 (the windows are resized to let them both fit in the figure).

Fig. 9.14 ShowString can change the string, save it to a file, and reload it.

Adding Appearance Options to the Options Dialog Box

ShowString doesn't have much to do, just demonstrate menus and dialog boxes. But the only dialog box 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 Box

It is quite simple to incorporate a full-fledged Font dialog box 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 that the text be centered vertically. Because these are check boxes, the text can be either, neither, or both.

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

  1. Stretch the dialog box taller to make room for the new controls.

  2. Click the radio button in the Controls floating toolbar, and then click the Options dialog box to drop the control.

  3. Choose View, Properties, and then pin the Properties dialog box in place.

  4. Change the resource ID of the first radio button to IDC_OPTIONS_BLACK, and change the caption to &Black.

  5. Select 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 &Red as the caption. Do not select 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 &Green as the caption. Again, do not select 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 box.

Next, add the check boxes by following these steps:

  1. Click the check box in the Controls floating toolbar and then click the Options dialog box, 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. Select the Group box to indicate the start of a new group after the radio buttons.

  4. Drop another check box onto the dialog box 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 box.

  8. Select each horizontal group of controls and use Layout, Center in Dialog, Horizontal to make things neater.

  9. Choose Edit, Select All, and then drag all the controls up toward the top of the dialog box. Shrink the dialog box to fit around the new controls. It should now resemble Figure 9.15.

Fig. 9.15 The options dialog box for ShowString has been expanded.

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 pops up after a few seconds, telling you what control the button represents.

Finally, set the tab order by choosing Layout, Tab Order, and then clicking 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 box to leave the two static text controls as positions 9 and 10.

Adding Member Variables to the Dialog Box Class

Having added controls to the dialog box, you need to add corresponding member variables to the COptionsDialog class. Bring up ClassWizard, select the Member Variable tab, and add member variables for each control. Figure 9.16 shows the summary of the member variables created. The check boxes are connected to BOOL variables; these member variables are TRUE if the box is selected and FALSE if it is not. The radio buttons are handled differently. Only the firstóthe one with the Group box selected in its Properties dialog 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 is 0; when Red is selected, m color is 1; and when Green is selected, m_color is 2.

Fig. 9.16 Member variables in the dialog box 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 box. You add them to the CShowStringDoc class definition in the header file, to OnNewDocument(), and to Serialize(). Add the lines in Listing 9.10 at the top of the CShowStringDoc definition in ShowStringDoc.h, replacing the previous definition of string and GetString().

Listing 9.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 9.11. Change your copy by double-clicking the function name in ClassView and adding the new code.

Listing 9.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;
    }
}

Finally, you need to initialize these variables in OnNewDocument(). 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 9.12.

Listing 9.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, users cannot change these member variables from the defaults. To allow the user to change the variables, you have to change the function that handles the dialog box.

Changing OnToolsOptions()

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

Listing 9.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 deselecting 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() saves 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. Edit OnDraw() by expanding CShowStringView in the ClassView, and double-clicking OnDraw().

The color is set with CDC::SetTextColor() before the call to DrawText(). You should always 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), for instance. Add the lines shown in Listing 9.14 lines before the call to DrawText() to set up everything.

Listing 9.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 9.15 before the call to DrawText().

Listing 9.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 box 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? Be sure to change the text, experiment with various combinations of the centering options, and try all three colors.

From Here...

This is not the last you will see of ShowString; it reappears in Chapter 12, "Help," and throughout Part V, "ActiveX Applications and ActiveX Controls" (Chapters 13-17.) 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:


© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.