Building a good user interface is half the battle of programming a Windows application. Luckily, Visual C++ and its AppWizard supply an amazing amount of help in creating an application that supports all the expected user-interface elements, including menus, dialog boxes, toolbars, and status bars. The subjects of menus and dialog boxes are covered elsewhere in this book. In this chapter, however, you get a lesson in how to get the most out of toolbars and status bars, as well as get a look at the Windows 95 Registry.
Although you can add a toolbar to your application with AppWizard, you still need to use a little programming polish to get things just right. This is because every application is different and AppWizard can create only the most generally useful toolbar for most applications. When you create your own toolbars, you'll probably want to add or delete buttons to support your application's unique command set.
For example, when you create a standard AppWizard application with a toolbar, AppWizard creates the toolbar shown in Figure 18.1. This toolbar provides buttons for the commonly used commands in the File and Edit menus, as well as a button for displaying the About dialog box. But what if your application doesn't support these commands? It's up to you to modify the default toolbar to fit your application.
Figure 18.1 : The default toolbar provides buttons for commonly used commands.
The first step in modifying the toolbar is to delete buttons you no longer need. To do this, first select the ResourceView tab to display your application's resources. Then, select the IDR_MAINFRAME toolbar resource, as shown in Figure 18.2. When you do, the toolbar editor appears, also shown in Figure 18.2.
Figure 18.2 : The toolbar editor enables you to customize your application's toolbar.
After you have the toolbar editor on the screen, deleting buttons is as easy as dragging the unwanted buttons from the toolbar. Just place your mouse pointer on the button, hold down the left mouse button, and drag the button away from the toolbar. When you release the mouse button, the toolbar button disappears. Figure 18.3 shows the edited toolbar with only the Help button remaining. The single, blank button template is only a starting point for the next button you may want to create. If you leave it blank, it doesn't appear in the final toolbar.
Adding buttons to a toolbar is a bit more complicated because you not only have to draw the button's icon, but you must also match the button with the command that it selects. To draw the new button, first click the blank button template in the toolbar. The blank button appears, enlarged, in the edit window, as shown in Figure 18.4.
Figure 18.4 : Click the button template to bring it up in the button editor.
Suppose you want to create a toolbar button that draws a circle in the application's window. You draw a circle on the blank button, which takes care of creating the button's icon, but now you need a way to associate the button with the program command. First, double-click the button in the toolbar to display its Toolbar Button Properties property sheet. Then, give the button an appropriate ID, which, in this case, might be something like ID_CIRCLE.
Now you need to define the button's tool tip and description. The tool tip appears whenever the user leaves his mouse pointer over the button for a second or two and acts as a reminder of the button's purpose. A tool tip of "Draw a circle" would be appropriate for the circle button. The description appears in the application's status bar. In this case, a description of "Draws a red circle in the window" might be good. You type these two text strings into the Prompt box. The description comes first, followed by the newline character (\n) and the tool tip, as shown in Figure 18.5.
Figure 18.5 : After drawing the button, you specify its properties.
You've now defined a command ID for your new toolbar button. You need to associate that ID with a message-handler function, which MFC will automatically call when the user clicks the button. To do this, make sure the button for which you want to create a message handler is selected in the custom toolbar, and then click the ClassWizard button. The MFC ClassWizard property sheet appears, with the button's ID already selected (see fig. 18.6). To add the message-response function, select, in the Class Name box, the class to which you want to add the function. Then, double-click the COMMAND selection in the Messag_es box. Accept the function name that MFC suggests in the next message box, and you're all set. Click the OK button to finalize your changes.
Figure 18.6 : You can use ClassWizard to create message-response functions for your toolbar buttons.
NOTE |
If you haven't defined a message-response function for a toolbar button, MFC disables the button when you run the application. This is also true for menu commands that have not yet been associated with a message-response function. In fact, for all intents and purposes, toolbar buttons are menu commands. Ordinarily, toolbar buttons duplicate menu commands, providing a quicker way for the user to select commonly used commands in the menus. In this case, the menu item and the toolbar button both represent the exact same command and you give both the same ID. Then, the same message-response function gets called whether the user selects the command from the menu bar or from the toolbar. N |
If you compile and run the application now, you'll see the window shown in Figure 18.7. In the figure, you can see the new toolbar button, as well as its tool tip and description line. The toolbar looks a little sparse in this example, but you can add as many buttons as you like.
Figure 18.7 : The new toolbar button shows its tool tip and description.
You can create as many buttons as you need, just follow the above procedures for each. After you have the buttons created, you're through with the toolbar resources and are ready to write the code that responds to the buttons. For example, in the previous example, a circle button was added to the toolbar and a message-response function, called OnCircle(), was added to the program. MFC will call that message-response function whenever the user clicks the associated button. However, right now, that function doesn't do anything, as shown in Listing 18.1.
Listing 18.1 LST18_1.TXT-An Empty Message-Response Function
void CToolView::OnCircle() { // TODO: Add your command handler code here }
Although the circle button is supposed to draw a circle in the window, you can see that the OnCircle() function is going to need a little help accomplishing that task. But, by adding the lines shown in Listing 18.2 to the function, the circle button will actually do what it's supposed to do, as shown in Figure 18.8.
Listing 18.2 LST18_2.TXT-Drawing a Circle
CClientDC clientDC(this); CBrush newBrush(RGB(255,0,0)); CBrush* oldBrush = clientDC.SelectObject(&newBrush); clientDC.Ellipse(20, 20, 200, 200); clientDC.SelectObject(oldBrush);
NOTE |
You can find the circle-drawing application in the CHAP18\TOOL directory of this book's CD-ROM. |
In most cases, after you have created your toolbar resource and associated its buttons with the appropriate command IDs, you don't need to bother any more with the toolbar. The code generated by AppWizard creates the toolbar for you, and MFC takes care of calling the buttons' response functions for you. However, there may be times when you want to change the toolbar's default behavior or appearance in some way. In those cases, you can call upon the CToolBar class's member functions, which are listed in Table 18.1 along with their descriptions. Please refer to your Visual C++ online documentation for more details.
Function | Description |
CommandToIndex() | Gets the index of a button given its ID |
Create() | Creates the toolbar |
GetButtonInfo() | Gets information about a button |
GetButtonStyle() | Gets a button's style |
GetButtonText() | Gets a button's text label |
GetItemID() | Gets the ID of a button given its index |
GetItemRect() | Gets an item's display rectangle given its index |
GetToolBarCtrl() | Gets a reference to the CToolBarCtrl object represented by the CToolBar object |
LoadBitmap() | Loads the toolbar's button images |
LoadToolBar() | Loads a toolbar resource |
SetBitmap() | Sets a new toolbar button bitmap |
SetButtonInfo() | Sets a button's ID, style, and image number |
SetButtons() | Sets the IDs for the toolbar buttons |
SetButtonStyle() | Sets a button's style |
SetButtonText() | Sets a button's text label |
SetHeight() | Sets the toolbar's height |
SetSizes() | Sets the buttons' sizes |
Normally, you don't need to call the toolbar's methods, but you can get some unusual results when you do, such as the extra high toolbar shown in Figure 18.9. (The buttons are the same size, but the toolbar window is bigger.) This toolbar resulted from a call to the toolbar object's SetHeight() member function. Truthfully, it's not suggested that you monkey with the size of the toolbar, but you can use other member functions to modify the toolbar's buttons in response to a user's command. For example, there may be times when you need to display different buttons at different times. The CToolBar class's member functions enable you to perform this sort of toolbar trickery.
Status bars are mostly benign objects that sit at the bottom of your application's window, doing whatever MFC instructs them to do. This consists of displaying command descriptions and the status of various keys on the keyboard, including the Caps Lock and Scroll Lock keys. In fact, status bars are so mundane from the programmer's point of view (at least, they are in an AppWizard application) that they aren't even represented by a resource that you can edit like a toolbar. When you tell AppWizard to incorporate a status bar into your application, there's not much left for you to do.
Or is there? A status bar, just like a toolbar, must reflect the interface needs of your specific application. For that reason, the CStatusBar class features a set of methods with which you can customize the status bar's appearance and operation. Table 18.2 lists the methods along with brief descriptions.
Method | Description |
CommandToIndex() | Gets an indicator's index given its ID |
Create() | Creates the status bar |
GetItemID() | Gets an indicator's ID given its index |
GetItemRect() | Gets an item's display rectangle given its index |
GetPaneInfo() | Gets information about an indicator |
GetPaneStyle() | Gets an indicator's style |
GetPaneText() | Gets an indicator's text |
GetStatusBarCtrl() | Gets a reference to the CStatusBarCtrl object represented by the CStatusBar object |
SetIndicators() | Sets the indicators' IDs |
SetPaneInfo() | Sets the indicators' IDs, widths, and styles |
SetPaneStyle() | Sets an indicator's style |
SetPaneText() | Sets an indicator's text |
When you create a status bar as part of an AppWizard application, you get a window similar to that shown in Figure 18.10. The status bar has several parts, called panes, that display certain information about the status of the application and the system. These panes, which are marked in Figure 18.10, include indicators for the Caps Lock, Num Lock, and Scroll Lock keys, as well as a message area for showing status text and command descriptions. To see a command description, place your mouse pointer over a button on the toolbar (see fig. 18.11).
Figure 18.10 : The default MFC status bar contains a number of informative panes.
Figure 18.11 : The message area is used mainly for command descriptions.
The most common way you can customize a status bar is to add new panes. This process can be a little tricky, though. To add a pane to a status bar, you must complete these steps:
The following sections cover these steps in detail.
This step is easy, thanks to Visual C++'s symbol browser. To add the command ID, first choose the View, Resource S ymbols command on Visual C++'s menu bar. When you do, you see the Resource Symbols dialog box (see fig. 18.12), which displays the currently defined symbols for your application's resources. Click the New button, and the New Symbol dialog box appears. Type the new ID into the Name box, and the ID's value into the Value box (see fig. 18.13). Usually, you can just accept the value that MFC suggests for the ID.
Figure 18.12 : Use the Resource Symbols dialog box to add new command IDs to your application.
Figure 18.13 : Type the new ID's name and value into the New Symbol dialog box.
Click the OK and Close buttons to finalize your selections, and your new command ID is defined.
The Visual C++ compiler insists that every status bar pane has a default string defined for it. To define the string, first go to the ResourceView window (by clicking the ResourceView tab) and open the String Table resource into the string table editor, as shown in Figure 18.14. (To open the string table editor, double-click the String Table resource.)
Figure 18.14 : You define the new pane's default string in the application's string table resource.
Now, select the Insert, New String command from the menu bar, which brings up the String Properties dialog box. Type the new pane's command ID into the ID box and the default string into the Caption box (see fig. 18.15). (Instead of typing the command ID, you can select it from the drop-down list.)
Figure 18.15 : Use the String Properties dialog box to define the new pane's default string.
When MFC constructs your status bar, it uses an array of IDs to determine which panes to display and where to display them. This array of IDs is passed as an argument to the status bar's SetIndicators() member function, which is called in the CMainFrame class's OnCreate() function. You find this array of IDs, shown in Listing 18.3, near the top of the MAINFRM.CPP file.
Listing 18.3 LST18.3.TXT-The Indicator Array
static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, };
To add your new pane to the array, type the pane's ID into the array at the position in which you want it to appear in the status bar, followed by a comma. (The first pane, ID_SEPARATOR, should always remain in the first position.) Listing 18.4 shows the indicator array with the new pane added.
Listing 18.4 LST18_4.TXT-Adding the New Pane's ID
static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_MYNEWPANE, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, };
MFC does not automatically enable new panes when it creates the status bar. Instead, you must create a command-update handler for the new pane, and enable the pane yourself. (You first learned about command-update handlers in Chapter 5 "Messages and Commands.") You must also add whatever code is needed to display information in the pane, assuming that the default string you defined in an earlier step is only a placeholder.
First, you must declare the new command-update handler in the MAINFRM.H header file. Unfortunately, although you can use ClassWizard to add command-update handlers for menu commands, you have to create status bar handlers by hand. The prototype to add to the header file looks like this:
afx_msg void OnMyNewPane(CCmdUI *pCmdUI);
Of course, the actual name of the handler varies from pane to pane, but the rest of the line should look exactly as it does here.
Next, you have to add the handler to the class's message map, which is what associates the command ID with the handler. Because the handler was not added by ClassWizard, be sure that you place the table entry outside of the AFX_MSG_MAP comments, as shown in Listing 18.5. You can find the message map defined near the top of the MAINFRM.CPP file (or near the top of the implementation file for the class to which you're adding the command-update handler).
Listing 18.5 LST18_5.TXT-Adding the Handler to the Message Map
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) // NOTE - the ClassWizard will add and remove mapping macros here. // DO NOT EDIT what you see in these blocks of generated code ! ON_WM_CREATE() //}}AFX_MSG_MAP ON_UPDATE_COMMAND_UI(ID_MYNEWPANE, OnMyNewPane) END_MESSAGE_MAP()
As you learned in Chapter 5 "Messages and Commands," you use the ON_UPDATE_COMMAND_UI macro to associate command IDs with their UI handers. The macro's two arguments are the command ID and the name of the command-update handler.
Now you're ready to write the new command-update handler. In the handler, you have to enable the new pane, as well as set the pane's contents. Listing 18.6 shows the command-update handler for the new pane. The command-update handler should be placed in the class implementation file that contains the message map entries for the status bar. In this example, that class is CMainFrame.
Listing 18.6 LST18_6.TXT-The Pane's Command-Update Handler
void CMainFrame::OnMyNewPane(CCmdUI *pCmdUI) { pCmdUI->Enable(); pCmdUI->SetText(m_paneString); }
If you don't understand how a command-update handler works, please refer to Chapter 5 "Messages and Commands," where these important functions that control a command item's appearance are discussed in detail. (A status bar pane is not a command item, but it uses the same mechanism for updating.) In the previous code example, m_paneString is the text that you want displayed in the pane. You can replace m_paneString with a string literal or with a variable that contains the string you want to appear in the status bar.
If you want to see the status bar tricks described in the previous sections in action, check out the CHAP18\STAT folder of this book's CD-ROM. When you run the program there, called Status Bar Demo, you see the window shown in Figure 18.16. As you can see, the status bar contains an extra panel displaying the text "Default string." If you select the File, Change String command or click the Change String toolbar button, a dialog box appears into which you can type a new string for the panel. When you exit the dialog box via the OK button, not only does the text appear in the new panel, but the panel also resizes itself to accommodate the new string (see fig. 18.17). In this section, you see how the new panel works.
Figure 18.16 : The Status Bar Demo application shows how to add and manage a status bar panel.
Figure 18.17 : The new panel resizes itself to fit the selected string.
In Listing 18.6, m_paneString is a CString object that holds the text to display in the pane. At the beginning of the program, this string, which is a data member of the CMainFrame class, is initialized to "Default string," which is the string that appears in the new pane when the Status Bar Demo program starts up. As you already know, the panel is enabled, and its contents set, in the OnMyNewPanel() command-update handler.
How does the panel resize itself? This happens when the user changes the panel's text with the Change String command. When the user selects this command, MFC calls the OnChangestring() message-response function, which is shown in Listing 18.7.
Listing 18.7 LST18_7.TXT-Changing the Panel's Text and Size
void CMainFrame::OnChangestring() { // TODO: Add your command handler code here CPaneDlg dialog(this); dialog.m_paneString = m_paneString; int result = dialog.DoModal(); if (result == IDOK) { m_paneString = dialog.m_paneString; CClientDC dc(this); SIZE size = dc.GetTextExtent(m_paneString); int index = m_wndStatusBar.CommandToIndex(ID_MYNEWPANE); m_wndStatusBar.SetPaneInfo(index, ID_MYNEWPANE, SBPS_POPOUT, size.cx); } }
In OnChangestring(), the program displays the dialog box, and, if the user exits the dialog box by clicking the OK button, the program changes the text string and resets the size of the pane. It does this by first calling the GetTextExtent() function, which returns the size of the string. To get the index of the pane, the program calls the status bar's CommandToIndex() member function, which returns the index given the pane's ID. Then the program uses the string size and index in the call to the status bar's SetPaneInfo() member function. This function's arguments are the pane index, the pane's new ID (in this case, you're not changing the ID), the pane's style, and the pane's width. The SBPS_POPOUT style creates a pane that seems to stick out from the status bar, rather than being indented.
The days of huge WIN.INI files or myriad private INI files are now gone. When an application wants to store information about itself, it does so using a centralized system registry. And, although the Registry makes sharing information between processes easier, it admittedly makes things more confusing for the programmer. This is, of course, the price programmers always pay for greater user satisfaction. In this section, you uncover some of the mysteries of the Registry, as well as learn how to manage it in your applications.
Unlike INI files, which are plain text files that can be edited with any text editor, the Registry contains binary and ASCII information that can be edited only using the Registry Editor or using special API function calls specially created for managing the Registry. If you've ever used the Registry Editor to browse your system's Registry, you know that it contains a huge amount of information that's organized into a tree structure. Figure 18.18 shows how the Registry appears when you first run the Registry Editor. (You can find the Registry Editor, called REGEDIT.EXE, in your main Windows folder, or you can run it with the Start menu's Run command by selecting the Run command and then typing regedit.)
Figure 18.18 : The Registry Editor displays the Registry.
The stuff listed in the left-hand window is the Registry's predefined keys. The plus marks next to the keys in the tree indicate that you can "open" the keys and view more detailed information associated with the key. In short, keys can have subkeys lower in the hierarchy. Subkeys can themselves have subkeys. And, any key may or may not have a value associated with it. If you explore a key deep enough in the hierarchy, you see a list of values appear in the right-hand window. In Figure 18.19, you can see the current user's screen appearance. To get there, you have to browse from HKEY_CURRENT_USER to Control Panel to Appearance. You can go one level deeper to Schemes, and view the various desktop schemes that are installed on the system.
Figure 18.19 : The Registry is structured as a tree containing a huge amount of information.
In order to know where things are stored in the Registry, you need to know about the predefined keys and what they mean. From Figure 18.18, you can see that the six predefined keys are HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, HKEY_CURRENT_CONFIG, and HKEY_DYN_DATA.
The HKEY_CLASSES_ROOT key holds document types and properties, as well as class information about the various applications installed on the machine. For example, if you explore this key on your system, you'd probably find an entry for the .DOC file extension, under which you'd find entries for the applications that can handle this type of document (see fig. 18.20).
Figure 18.20 : The HKEY_CLASSES_ROOT key holds document information.
The HKEY_CURRENT_USER key contains all the system settings the current user has established, including color schemes, printers, and program groups. The HKEY_LOCAL_MACHINE key, on the other hand, contains status information about the computer, and the HKEY_USERS key organizes information about each user of the system, as well as the default configuration. Finally, the HKEY_CURRENT_CONFIG key holds information about the hardware configuration, and the HKEY_DYN_DATA key contains information about dynamic registry data, which is data that changes frequently.
Now that you know a little about the Registry, let me say that it would take an entire book to explain how to fully access and use the Registry. As you may imagine, the Win32 API features many functions for manipulating the Registry. And, if you're going to use those functions, you sure better know what you're doing! However, you can easily use the Registry with your MFC applications to store information that the application needs from one session to another. To make this task as easy as possible, MFC provides the CWinApp class with the SetRegistryKey() member function, which creates (or opens) a key entry in the Registry for your application. All you have to do is supply a key name (usually a company name) for the function to use, like this:
SetRegistryKey("MyCoolCompany");
You should call SetRegistryKey() in the application class's InitInstance() member function, which is called once at program startup.
After you've called SetRegistryKey(), your application can create the subkeys and values it needs the old-fashioned way, by calling one of two functions. The WriteProfileString() function adds string values to the Registry, and the WriteProfileInt() function adds integer values to the Registry. To get values from the Registry, you can use the GetProfileString() and GetProfileInt() functions. (You can also use RegSetValueEx() and RegQueryValueEx() to set and retrieve Registry values.)
NOTE |
Normally, the WriteProfileString(), WriteProfileInt(), GetProfileString(), and GetProfileInt() functions transfer information to and from an INI file. But when you call SetRegistryKey(), MFC reroutes these profile functions to the Registry, making adding keys to the Registry an almost painless process. |
In this chapter, you might have noticed that the Status Bar Demo application has a Restore String command on its File menu. You can use this command to restore the string that was in the status bar's new pane when you last quit the application. Status Bar Demo stored this string in the Registry and reads it from the Registry when requested to do so.
Status Bar Demo calls SetRegistryKey("MyCoolCompany") in the CStatApp class's InitInstance() function, which creates the MyCoolCompany key if it doesn't exist or opens the key if it does. The first time you quit the application, the CMainFrame class calls WriteProfileString(), like this:
CWinApp* pApp = AfxGetApp(); pApp->WriteProfileString("StatusBarDemo", "PaneText", m_paneString);
The first line gets a pointer to the application object, which is the class in which the WriteProfileString() function is defined. The second line calls WriteProfileString() to store the string in the Registry. The function's three arguments are the section key, the item key, and the value for the item key. After this function call, which, in Status Bar Demo, is made in the CMainFrame class's OnDestroy() function, you have the keys shown in Figure 18.21 in your Registry.
Figure 18.21 : The Status Bar Demo application adds several keys to the Registry.
If you select the File, Restore String command, the program reads the string from the Registry like this:
CWinApp* pApp = AfxGetApp(); m_paneString = pApp->GetProfileString("StatusBarDemo", "PaneText", "Default string");
GetProfileString()'s three arguments are the section key, the item key, and the item value to return if the key doesn't yet exist.
Users of Windows applications expect to find certain user-interface elements in place when they run a new application. Two of the most visible of these interface elements are the toolbar and the status bar, which have become standard parts of just about every Windows application. The toolbar provides the user with shortcut buttons for selecting menu commands, whereas the status bar keeps the user apprised of the application's state, as well as displays messages such as command descriptions.
Although not a visible component of an application, the Registry is the place where you should store data that your application needs from session to session. Using the Registry can be tricky when relying solely on the Win32 Registry functions. However, MFC makes dealing with the Registry as easy as writing values to the old-fashioned INI files. For more information on related topics, please refer to the following chapters: