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.
The sample application you will build is very much like the traditional "Hello, world!" of C programming. It 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 string's appearance. 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.
First, use AppWizard to create the starter application. (Chapter 1, "Building Your First Windows Application," covers AppWizard and creating starter applications.) Choose File, New and the Project tab. Select an MFC AppWizard (exe) application, name the project ShowString so that your classnames will match those shown throughout this chapter, and click OK.
In Step 1 of AppWizard, it doesn't matter much whether you choose SDI or MDI, but MDI will enable you to see for yourself how little effort is required to have multiple documents open at once. So, choose MDI. Choose U.S. 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, select a docking toolbar, initial status bar, printing and print preview, context-sensitive help, and 3D controls, and then click Next. Choose source file comments and shared DLL, and then click Next. The classnames and filenames are all fine, so click Finish. Figure 8.1 shows the final confirmation dialog box. Click OK.
FIG. 8.1 AppWizard summarizes the design choices for ShowString.
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 are introduced in Chapter 4, "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 8.1. (Expand CShowStringDoc in ClassView and double-click Serialize() to edit the code.) Because 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 7, "Persistence and File I/O," introduces serialization.
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 8.2, you can see it yourself by scrolling toward the top of ShowString.cpp) and handled by CWinApp::OnFileNew(). (Message maps and message handlers are discussed in Chapter 3, "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.
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 8.3. To see yours in the editor, double-click OnNewDocument() in ClassView--you may have to expand CshowStringDoc first.
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 other work involved in making a new document.
Getting the String Onscreen As you learned in Chapter 5, "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 8.4. To edit this function, expand CShowStringView in ClassView and then double-click 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 5. The device context class, CDC, has a member function called DrawText() that draws text onscreen. It is declared like this:
int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )
See "Understanding Device Contexts," ch. 5
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 will see something like Figure 8.2. You have a lot of functionality--menus, toolbars, status bar, and so on--but nothing that any other Windows application doesn't have, yet. Starting with the next section, that changes.
FIG. 8.2 ShowString starts simply, with the usual greeting.
AppWizard creates two menus for you, shown in the ResourceView window in Figure 8.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 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. 8.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 string's format. 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. The choice you are going to see here, though, is to combine everything into one dialog box and then put it on a new Tools menu, under the Options item.
NOTE: You may have noticed already that more and more Windows applications are standardizing 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. Open 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 8.4.
FIG. 8.4 Adding the Tools menu is easy in the ResourceView window.
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 8.5. The prompt will be shown on the status bar when the user pauses the mouse over the menu item or moves the highlight over it with the cursor.
TIP: 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 that it precedes a different letter in the menu or menu item name (for example, T&ools changes the key from T to o). You should not use the same mnemonic letter for two menus or for two items on the same menu.
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 right away in the Properties dialog box. 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, like the Ctrl+C for Edit, Copy that the system provides, 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:
FIG. 8.5 The menu command Tools, Options controls everything that ShowString does.
Figure 8.6 shows the Properties dialog box for this accelerator after again clicking the newly entered line.
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.
Chapter 2, "Dialogs and Controls," introduces dialog boxes. This section builds on that background. ShowString is 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.
FIG. 8.6 Keyboard accelerators are connected to resource IDs.
Figure 8.7 shows the About dialog box that AppWizard makes for you; it contains the application name 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. 8.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© 1998, and it will be surrounded by a selection box. Bring up the Properties dialog box, if it isn't up. Edit the caption to add Que Books at the end; the changes are reflected immediately in the dialog box.
TIP: If the rulers you see in Figure 8.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 to remind users what book this application is from. Here's how to do that:
The About dialog box will resemble Figure 8.8.
FIG. 8.8 In a matter of minutes, you can customize your About dialog box.
TIP: All the Layout menu items are on the Dialog toolbar.
The Options dialog box is 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 button and a Cancel button, as shown in Figure 8.9.
FIG. 8.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:
You will revisit this dialog box later, when adding the appearance capabilities, but for now it's ready to be connected. It will look like Figure 8.10.
FIG. 8.10 The Options dialog box is the place to change the string.
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.
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 8.11.
FIG. 8.11 Create a C++ class to go with the new dialog box.
Leave Create a New Class selected and then click OK. The New Class dialog box, shown in Figure 8.12, appears.
FIG. 8.12 The dialog box class inherits from CDialog.
Fill in the dialog box as follows:
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. 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 8.5.
// 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 isn't much longer; it's shown in Listing 8.6.
// 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.
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.
NOTE: Often the hardest part of catching these messages is deciding which class should catch them. The decision between View and Document is frequently a very difficult one. If the message handler will need access to a private member of either class, that's the class to catch the message.
To catch the message, follow these steps:
FIG. 8.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 (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. Otherwise the next developer to work on your project is going to have a very hard time finding the message handlers.
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 8.7.
// 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 8.8.
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 }
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. 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 8.9, displays the dialog box. Add this code to the empty function ClassWizard generated for you when you arranged to catch the message.
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 document's member variable (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(). 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 8.14 (the windows are resized to let them both fit in the figure).
FIG. 8.14 ShowString can change the string, save it to a file, and reload it.
ShowString doesn't have much to do, just demonstrate menus and dialog boxes. However, 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.
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 give the user a choice of several colors. One check box will enable 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:
Next, add the check boxes by following these steps:
FIG. 8.15 The Options dialog box 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 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:
Then click away from the dialog box to leave the two static text controls as positions 9 and 10.
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 8.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 isn't. 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. 8.16 Member variables in the dialog box class are connected to individual controls or the group of radio buttons.
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 8.10 at the top of the CShowStringDoc definition in ShowStringDoc.h, replacing the previous definition of string and GetString(). Make sure that the variables are private and the functions are public.
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 8.11. Change your copy by double-clicking the function name in ClassView and adding the new code.
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 8.12.
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.
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 8.13.
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); }
}
What happens when the user opens 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().
The single call to DrawText() in OnDraw() becomes a little more complex now. The document member variables are used to set the view's appearance. 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 8.14 before the call to DrawText() to set up everything.
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 8.15 before the call to DrawText().
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. Any surprises? Be sure to change the text, experiment with various combinations of the centering options, and try all three colors. l
© Copyright, Macmillan Computer Publishing. All rights reserved.