Several standard controls are built into the Windows operating system, including such things as sliders, tree and list controls, progress bars, and so on. However, today you will work with a half dozen controls that appear in just about every Windows application:
These and other controls are readily available for use in Visual C++ applications. They can be found on the controls palette in the Dialog Painter editor in the Developer Studio, as shown in Figure 2.1.
FIGURE 2.1. The standard controls available on the Control palette.
You use the static text control to present text to the user. The user will not be able to change the text or otherwise interact with the control. Static text is intended as a read-only control. However, you can easily change the text displayed by the control as your application is running through the code you create for your application.
An edit box allows the user to enter or change text. The edit box is one of the primary tools for allowing the user to enter specific information that your application needs. It is a control that allows the user to type a specific amount of text, which you can capture and use for any needed purpose. The edit box accepts plain text only; no formatting is available to the user.
A command button is a button that the user can press to trigger some action. Command buttons have a textual label that can give users some idea of what will happen when they click that button. Buttons can also have images as part of the button, allowing you to place an image on the button--alone or along with a textual description--to convey what the button does.
A check box is a square that the user can click to check (¥) or uncheck. The check box control is used to turn a particular value on and off. They are basically on/off switches with an occasional third, in-between state. You normally use check boxes to control discrete, on/off-type variables.
A radio button is a circle that the user can click to fill with a black spot. The radio button is similar to the check box control, but it is used in a group of two or more where only one of the values can be in the on state at a time. You normally use radio buttons in groups of at least three, surrounded by a group box. The group box allows each group of radio buttons to be independent so that only one radio button in each group can be in the on state at any time.
A drop-down list box, or combo control, is an edit box with a list of available values attached. You use the drop-down list box to provide a list of choices, from which the user may select one value from the list. Sometimes, the user is given the option of typing in his own value when a suitable one isn't provided in the list.
The application you are going to build today will have a number of controls on a single dialog window, as shown in Figure 2.2. These controls have a number of different functions. At the top of the window is an edit field where the user can enter a message that displays in a message box when he or she clicks the button beside the field. Below this edit field are two buttons that either populate the edit field with a default message or clear the edit field. Below these buttons is a drop-down list box that contains a list of standard Windows applications. When the user selects one of these programs and then clicks the button beside the drop-down list, the selected program will run. Next are two groups of check boxes that affect the controls you add to the top half of the dialog: the controls for displaying a user message and the controls for running another program. The left set of check boxes will enable and disable each group of controls you provide. The right set of check boxes will show and hide each group of controls. At the bottom of the dialog box is a button that can be clicked to close the application.
FIGURE 2.2. Today's application will use a number of standard controls.
Using what you learned yesterday, create a new application shell and design the application dialog layout as follows:
Object | Property | Setting |
Static Text | ID | IDC_STATIC |
Caption | This is an example of a Visual C++ Application using a number of controls. | |
Static Text | ID | IDC_STATICMSG |
Caption | Enter a &Message: | |
Static Text | ID | IDC_STATICPGM |
Caption | Run a &Program: | |
Edit Box | ID | IDC_MSG |
Button | ID | IDC_SHWMSG |
Caption | &Show Message | |
Button | ID | IDC_DFLTMSG |
Caption | &Default Message | |
Button | ID | IDC_CLRMSG |
Caption | &Clear Message | |
Button | ID | IDC_RUNPGM |
Caption | &Run Program | |
Button | ID | IDC_EXIT |
Caption | E&xit | |
Combo Box | ID | IDC_PROGTORUN |
Group Box | ID | IDC_STATIC |
Caption | Enable Actions | |
Group Box | ID | IDC_STATIC |
Caption | Show Actions | |
Check Box | ID | IDC_CKENBLMSG |
Caption | &Enable Message Action | |
Check Box | ID | IDC_CKENBLPGM |
Caption | E&nable Program Action | |
Check Box | ID | IDC_CKSHWMSG |
Caption | S&how Message Action | |
Check Box | ID | IDC_CKSHWPGM |
Caption | Sh&ow Program Action |
TIP: When adding a combo box control to the window, it is important that you click and drag the area for the control as large as you want the drop-down list to be. After you draw the control on the window, you can resize the width of the control as you would normally expect to do. To resize how far the list drops down, you need to click the arrow, as if you were trying to trigger the drop-down list while the application was running.
FIGURE 2.3. Use the properties dialog to add entries in the combo box's drop-down list.
Now that you have all the controls laid out on the window, you need to make sure that the user navigates in the order you want if he or she uses the Tab key to move around the window. You can specify the tab order by following these steps:
FIGURE 2.4. Turning on Tab Order shows the order in which the dialog will be navigated.
NOTE: Any static text that has a mnemonic should appear just before the control that accompanies the text in the tab order. Because the user cannot interact with the static text, when the user chooses the mnemonic, the focus will go directly to the next control in the tab order.
A mnemonic is the underlined character in the caption on a button, check box, menu, or other control label. The user can press this underlined character and the Alt key at the same time to go directly to that control or to trigger the clicked event on the control. You specify a mnemonic by placing an ampersand (&) in front of the character to be used as the mnemonic when you type the Caption value. It is important to make certain that you do not use the same mnemonic more than once on the same window, or set of menus, because the user can get confused when choosing a mnemonic doesn't result in the action that he or she expects.
One last thing that you want to do before getting into the details of the application code is check your mnemonics to make certain that there are no conflicts in your controls. Follow these steps:
Figure 2.5. The mnemonic checker tells you whether there are conflicts.
FIGURE 2.6. Duplicate mnemonics can be automatically selected.
At this point, if you've programmed using Visual Basic or PowerBuilder, you probably figure that you're ready to start slinging some code. Well, with Visual C++, it's not quite the same process. Before you can begin coding, you have to assign variables to each of the controls that will have a value attached--everything except the static text and the command buttons. You will interact with these variables when you write the code for your application. The values that the user enters into the screen controls are placed into these variables for use in the application code. Likewise, any values that your application code places into these variables are updated in the controls on the window for the user to see.
How do you declare these variables and associate them with the controls that you placed on the window? Follow these steps:
FIGURE 2.7. The Member Variables tab on the Class Wizard is where you add variables to controls.
FIGURE 2.8. Adding a variable to a control.
Control | Variable Name | Category | Type |
IDC_MSG | m_strMessage | Value | CString |
IDC_PROGTORUN | m_strProgToRun | Value | CString |
IDC_CKENBLMSG | m_bEnableMsg | Value | BOOL |
IDC_CKENBLPGM | m_bEnablePgm | Value | BOOL |
IDC_CKSHWMSG | m_bShowMsg | Value | BOOL |
IDC_CKSHWPGM | m_bShowPgm | Value | BOOL |
TIP: All these variables are prefixed with m_ because they are class member variables. This is an MFC naming convention. After the m_, a form of Hungarian notation is used, in which the next few letters describe the variable type. In this case, b means boolean, and str indicates that the variable is a string. You'll see this naming convention in use in this book and other books about programming with Visual C++ and MFC. Following this naming convention will make your code more readable for other programmers; knowing the convention will make it easier for you to read other programmer's code as well.
Before you begin adding code to all the controls on your application window, you need to add a little bit of code to initialize the variables, setting starting values for most of them. Do this by following these steps:
FIGURE 2.9. You can use the Class Wizard to locate existing functions.
1: BOOL CDay2Dlg::OnInitDialog() 2: { 3: CDialog::OnInitDialog(); 4: 5: . 6: . 7: . 8: 9: // TODO: Add extra initialization here 10: 11: /////////////////////// 12: // MY CODE STARTS HERE 13: /////////////////////// 14: 15: // Put a default message in the message edit 16: m_strMessage = "Place a message here"; 17: 18: // Set all of the check boxes to checked 19: m_bShowMsg = TRUE; 20: m_bShowPgm = TRUE; 21: m_bEnableMsg = TRUE; 22: m_bEnablePgm = TRUE; 23: 24: // Update the dialog with the values 25: UpdateData(FALSE); 26: 27: /////////////////////// 28: // MY CODE ENDS HERE 29: /////////////////////// 30: 31: return TRUE; // return TRUE unless you set the focus to a Âcontrol
32: }
NOTE: There is more code in the OnInitDialog function than has been included in Listing 2.1. I won't include all the code for every function in the code listings throughout this book as a means of focusing on the code that you need to add or modify (and as a means of keeping this book down to a reasonable size). You are welcome to look at the code that has been left out, to learn what it is and what it does, as you build your understanding of MFC and Visual C++.
NOTE: If you've programmed in C or C++ before, you've noticed that you are setting the value of the m_strMessage variable in a very un-C-like manner. It looks more like how you would expect to set a string variable in Visual Basic or PowerBuilder. That's because this variable is a CString type variable. The CString class enables you to work with strings in a Visual C++ application in much the same way that you would work with strings in one of these other programming languages. However, because this is the C++ programming language, you still need to add a semicolon at the end of each command.
This initialization code is simple. You are setting an initial message in the edit box that you will use to display messages for the user. Next, you are setting all the check boxes to the checked state. It's the last line of the code you added to this function that you really need to notice.
The UpdateData function is the key to working with control variables in Visual C++. This function takes the data in the variables and updates the controls on the screen with the variable values. It also takes the data from the controls and populates the attached variables with any values changed by the user. This process is controlled by the argument passed into the UpdateData function. If the argument is FALSE, the values in the variables are passed to the controls on the window. If the argument is TRUE, the variables are updated with whatever appears in the controls on the window. As a result, which value you pass this function depends on which direction you need to update. After you update one or more variables in your code, then you need to call UpdateData, passing it FALSE as its argument. If you need to read the variables to get their current value, then you need to call UpdateData with a TRUE value before you read any of the variables. You'll get the hang of this as you add more code to your application.
The first thing that you want to take care of is making sure that the user can close your application. Because you deleted the OK and Cancel buttons and added a new button for closing the application window, you need to place code into the function called by the Exit button to close the window. To do this, follow these steps:
1: void CDay2Dlg::OnExit() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Exit the program 10: OnOK(); 11: 12: /////////////////////// 13: // MY CODE ENDS HERE 14: ///////////////////////
15: }
A single function call within the OnExit function closes the Window and exits the application. Where did this OnOK function come from, and why didn't you have to call it in yesterday's application? Two functions, OnOK and OnCancel, are built into the ancestor CDialog class from which your CDay2Dlg class is inherited. In the CDialog class, the message map already has the object IDs of the OK and Cancel buttons attached to the OnOK and OnCancel buttons so that buttons with these IDs automatically call these functions. If you had specified the Exit button's object ID as IDOK, you would not have needed to add any code to the button unless you wanted to override the base OnOK functionality.
Showing the message that the user typed into the edit box should be easy because it's similar to what you did in yesterday's application. You can add a function to the Show Message button and call the MessageBox function, as in Listing 2.3.
1: void CDay2Dlg::OnShwmsg() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Display the message for the user 10: MessageBox(m_strMessage); 11: 12: /////////////////////// 13: // MY CODE ENDS HERE 14: ///////////////////////
15: }
If you compile and run the application at this point, you'll see one problem with this code. It displays the string that you initialized the m_strMessage variable within the OnInitDialog function. It doesn't display what you type into the edit box. This happens because the variable hasn't been updated with the contents of the control on the window yet. You need to call UpdateData, passing it a TRUE value, to take the values of the controls and update the variables before calling the MessageBox function. Alter the OnShwmsg function as in Listing 2.4.
1: void CDay2Dlg::OnShwmsg() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Update the message variable with what the user entered 10: UpdateData(TRUE); 11: 12: // Display the message for the user 13: MessageBox(m_strMessage); 14: 15: /////////////////////// 16: // MY CODE ENDS HERE 17: ///////////////////////
18: }
Now if you compile and run your application, you should be able to display the message you type into the edit box, as shown in Figure 2.10.
FIGURE 2.10. The message entered in the edit box is displayed to the user.
If the user prefers the edit box to be cleared before he or she types a message, you can attach a function to the Clear Message button to clear the contents. You can add this function through the Class Wizard in the usual way. The functionality is a simple matter of setting the m_strMessage variable to an empty string and then updating the controls on the window to reflect this. The code to do this is in Listing 2.5.
1: void CDay2Dlg::OnClrmsg() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Clear the message 10: m_strMessage = ""; 11: 12: // Update the screen 13: UpdateData(FALSE); 14: 15: /////////////////////// 16: // MY CODE ENDS HERE 17: ///////////////////////
18: }
The last thing that you want to do with the message controls is add functionality to the Enable Message Action and Show Message Action check boxes. The first of these check boxes enables or disables the controls dealing with displaying the user message. When the check box is in a checked state, the controls are all enabled. When the check box is in an unchecked state, all those same controls are disabled. In a likewise fashion, the second check box shows and hides this same set of controls. The code for these two functions is in Listing 2.6.
1: void CDay2Dlg::OnCkenblmsg() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Get the current values from the screen 10: UpdateData(TRUE); 11: 12: // Is the Enable Message Action check box checked? 13: if (m_bEnableMsg == TRUE) 14: { 15: // Yes, so enable all controls that have anything 16: // to do with showing the user message 17: GetDlgItem(IDC_MSG)->EnableWindow(TRUE); 18: GetDlgItem(IDC_SHWMSG)->EnableWindow(TRUE); 19: GetDlgItem(IDC_DFLTMSG)->EnableWindow(TRUE); 20: GetDlgItem(IDC_CLRMSG)->EnableWindow(TRUE); 21: GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE); 22: } 23: else 24: { 25: // No, so disable all controls that have anything 26: // to do with showing the user message 27: GetDlgItem(IDC_MSG)->EnableWindow(FALSE); 28: GetDlgItem(IDC_SHWMSG)->EnableWindow(FALSE); 29: GetDlgItem(IDC_DFLTMSG)->EnableWindow(FALSE); 30: GetDlgItem(IDC_CLRMSG)->EnableWindow(FALSE); 31: GetDlgItem(IDC_STATICMSG)->EnableWindow(FALSE); 32: } 33: 34: /////////////////////// 35: // MY CODE ENDS HERE 36: /////////////////////// 37: } 38: 39: void CDay2Dlg::OnCkshwmsg() 40: { 41: // TODO: Add your control notification handler code here 42: 43: /////////////////////// 44: // MY CODE STARTS HERE 45: /////////////////////// 46: 47: // Get the current values from the screen 48: UpdateData(TRUE); 49: 50: // Is the Show Message Action check box checked? 51: if (m_bShowMsg == TRUE) 52: { 53: // Yes, so show all controls that have anything 54: // to do with showing the user message 55: GetDlgItem(IDC_MSG)->ShowWindow(TRUE); 56: GetDlgItem(IDC_SHWMSG)->ShowWindow(TRUE); 57: GetDlgItem(IDC_DFLTMSG)->ShowWindow(TRUE); 58: GetDlgItem(IDC_CLRMSG)->ShowWindow(TRUE); 59: GetDlgItem(IDC_STATICMSG)->ShowWindow(TRUE); 60: } 61: else 62: { 63: // No, so hide all controls that have anything 64: // to do with showing the user message 65: GetDlgItem(IDC_MSG)->ShowWindow(FALSE); 66: GetDlgItem(IDC_SHWMSG)->ShowWindow(FALSE); 67: GetDlgItem(IDC_DFLTMSG)->ShowWindow(FALSE); 68: GetDlgItem(IDC_CLRMSG)->ShowWindow(FALSE); 69: GetDlgItem(IDC_STATICMSG)->ShowWindow(FALSE); 70: } 71: 72: /////////////////////// 73: // MY CODE ENDS HERE 74: ///////////////////////
75: }
By now, you should understand the first part of these functions. First, you update the variables with the current values of the controls on the window. Next, you check the value of the boolean variable attached to the appropriate check box. If the variable is TRUE, you want to enable or show the control. If the variable if FALSE, you want to disable or hide the control.
At this point, the code begins to be harder to understand. The first function, GetDlgItem, is passed the ID of the control that you want to change. This function returns the object for that control. You can call this function to retrieve the object for any of the controls on the window while your application is running. The next part of each command is where a member function of the control object is called. The second function is a member function of the object returned by the first function. If you are not clear on how this works, then you might want to check out Appendix A, "C++ Review," to brush up on your C++.
The second functions in these calls, EnableWindow and ShowWindow, look like they should be used on windows, not controls. Well, yes, they should be used on windows; they happen to be members of the CWnd class, which is an ancestor of the CDialog class from which your CDay2Dlg class is inherited. It just so happens that, in Windows, all controls are themselves windows, completely separate from the window on which they are placed. This allows you to treat controls as windows and to call windows functions on them. In fact, all the control classes are inherited from the CWnd class, revealing their true nature as windows.
If you compile and run your application now, you can try the Enable and Show Message Action check boxes. They should work just fine, as shown in Figure 2.11.
FIGURE 2.11. The user message controls can now be disabled.
The last major piece of functionality to be implemented in your application is for the set of controls for running another program. If you remember, you added the names of three Windows applications into the combo box, and when you run your application, you can see these application names in the drop-down list. You can select any one of them, and the value area on the combo box is updated with that application name. With that part working as it should, you only need to add code to the Run Program button to actually get the value for the combo box and run the appropriate program. Once you create the function for the Run Program button using the Class Wizard, add the code in Listing 2.7 to the function.
1: void CDay2Dlg::OnRunpgm() 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Get the current values from the screen 10: UpdateData(TRUE); 11: 12: // Declare a local variable for holding the program name 13: CString strPgmName; 14: 15: // Copy the program name to the local variable 16: strPgmName = m_strProgToRun; 17: 18: // Make the program name all uppercase 19: strPgmName.MakeUpper(); 20: 21: // Did the user select to run the Paint program? 22: if (strPgmName == "PAINT") 23: // Yes, run the Paint program 24: WinExec("pbrush.exe", SW_SHOW); 25: 26: // Did the user select to run the Notepad program? 27: if (strPgmName == "NOTEPAD") 28: // Yes, run the Notepad program 29: WinExec("notepad.exe", SW_SHOW); 30: 31: // Did the user select to run the Solitaire program? 32: if (strPgmName == "SOLITAIRE") 33: // Yes, run the Solitaire program 34: WinExec("sol.exe", SW_SHOW); 35: 36: /////////////////////// 37: // MY CODE ENDS HERE 38: ///////////////////////
39: }
As you expect, the first thing that you do in this function is call UpdateData to populate the variables with the values of the controls on the window. The next thing that you do, however, might seem a little pointless. You declare a new CString variable and copy the value of the combo box to it. Is this really necessary when the value is already in a CString variable? Well, it depends on how you want your application to behave. The next line in the code is a call to the CString function MakeUpper, which converts the string to all uppercase. If you use the CString variable that is attached to the combo box, the next time that UpdateData is called with FALSE as the argument, the value in the combo box is converted to uppercase. Considering that this is likely to happen at an odd time, this is probably not desirable behavior. That's why you use an additional CString in this function.
Once you convert the string to all uppercase, you have a series of if statements that compare the string to the names of the various programs. When a match is found, the WinExec function is called to run the application. Now, if you compile and run your application, you can select one of the applications in the drop-down list and run it by clicking the Run Program button.
CAUTION: It is important to understand the difference in C and C++ between using a single equal sign (=) and a double equal sign (==). The single equal sign performs an assignment of the value on the right side of the equal sign to the variable on the left side of the equal sign. If a constant is on the left side of the equal sign, your program will not compile, and you'll get a nice error message telling you that you cannot assign the value on the right to the constant on the left. The double equal sign (==) is used for comparison. It is important to make certain that you use the double equal sign when you want to compare two values because if you use a single equal sign, you alter the value of the variable on the left. This confusion is one of the biggest sources of logic bugs in C/C++ programs.
NOTE: The WinExec function is an obsolete Windows function. You really should use the CreateProcess function instead. However, the CreateProcess function has a number of arguments that are difficult to understand this early in programming using Visual C++. The WinExec function is still available and is implemented as a macro that calls the CreateProcess function. This allows you to use the much simpler WinExec function to run another application while still using the function that Windows wants you to use.
Another API function that can be used to run another application is the ShellExecute function. This function was originally intended for opening or printing files, but can also be used to run other programs.
Today, you learned how you can use standard windows controls in a Visual C++ application. You learned how to declare and attach variables to each of these controls and how to synchronize the values between the controls and the variables. You also learned how you can manipulate the controls by retrieving the control objects using their object ID and how you can manipulate the control by treating it as a window. You also learned how to specify the tab order of the controls on your application windows, thus enabling you to control how users navigate your application windows. Finally, you learned how to attach application functionality to the controls on your application window, triggering various actions when the user interacts with various controls. As an added bonus, you learned how you can run other Windows applications from your own application.
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. The answers to the quiz questions and exercises appear in Appendix B, "Answers."
© Copyright, Macmillan Computer Publishing. All rights reserved.