Teach Yourself Visual C++ 6 in 21 Days

Previous chapterNext chapterContents


- 4 -
Working with Timers



You may often find yourself building an application that needs to perform a specific action on a regular basis. The task can be something simple such as displaying the current time in the status bar every second or writing a recovery file every five minutes. Both of these actions are regularly performed by several applications that you probably use on a daily basis. Other actions that you might need to perform include checking specific resources on a regular basis, as a resource monitor or performance monitor does. These examples are just a few of the situations where you want to take advantage of the availability of timers in the Windows operating system.

Today you are going to learn

Understanding Windows Timers

Windows timers are mechanisms that let you set one or more timers to be triggered at a specific number of milliseconds. If you set a timer to be triggered at a 1,000 millisecond interval, it triggers every second. When a timer triggers, it sends a WM_TIMER message to your application. You can use the Class Wizard to add a function to your application to handle this timer message.

Timer events are placed only in the application event queue if that queue is empty and the application is idle. Windows does not place timer event messages in the application event queue if the application is already busy. If your application has been busy and has missed several timer event messages, Windows places only a single timer message in the event queue. Windows does not send your application all the timer event messages that occurred while your application was busy. It doesn't matter how many timer messages your application may have missed; Windows still places only a single timer message in your queue.

When you start or stop a timer, you specify a timer ID, which can be any integer value. Your application uses this timer ID to determine which timer event has triggered, as well as to start and stop timers. You'll get a better idea of how this process works as you build your application for today.

Placing a Clock on Your Application

In the application that you will build today, you will use two timers. The first timer maintains a clock on the window. This timer is always running while the application is running. The second timer is configurable to trigger at whatever interval the user specifies in the dialog. The user can start and stop this timer at will. Let's get started.

Creating the Project and Application

You will build today's sample application in three phases. In the first phase, you will add all the controls necessary for the entire application. In the second phase, you will add the first of the two timers. This first timer will control the clock on the application dialog. In the third phase, you will add the second timer, which the user can tune, start, and stop as desired.

To create today's application, follow these steps:

1. Create a new project, named Timers, using the same AppWizard settings that you've used for the past three days. Specify the application title as Timers.

2. Lay out the dialog window as shown in Figure 4.1, using the control properties in Table 4.1. Remember that when you place a control on the window, you can right-click the mouse to open the control's properties from the pop-up menu.

FIGURE 4.1. The Timers application dialog layout.

TABLE 4.1. CONTROL PROPERTY SETTINGS.

Object Property Setting
Static Text ID IDC_STATIC

Caption Timer &Interval:
Edit Box ID IDC_INTERVAL
Button ID IDC_STARTTIME

Caption &Start Timer
Button ID IDC_STOPTIMER

Caption S&top Timer

Disabled Checked
Static Text ID IDC_STATIC

Caption Time:
Static Text ID IDC_STATICTIME

Caption Current Time
Static Text ID IDC_STATIC

Caption Count:
Static Text ID IDC_STATICCOUNT

Caption 0
Button ID IDC_EXIT

Caption E&xit

3. Set the tab order as you learned on Day 2, "Using Controls in Your Application."

4. Add code to the Exit button to close the application, as you did on Day 2.

Adding the Timer IDs

Because you will be using two timers in this application, you should add two IDs to your application to represent the two timer IDs. This can be done by following these steps:

1. On the Resource View tab in the workspace pane, right-click the mouse over the Timers resources folder at the top of the resource tree. Select Resource Symbols from the pop-up menu, as in Figure 4.2.

FIGURE 4.2. The Resource pop-up menu.

2. On the Resource Symbols dialog, click the New button.

3. On the New Symbol dialog, enter ID_CLOCK_TIMER as the symbol name and 1 as the value, as shown in Figure 4.3.

FIGURE 4.3. Adding a new resource symbol.

4. Repeat steps 2 and 3, specifying ID_COUNT_TIMER as the symbol name and 2 as the value.

5. Click the Close button to close the Resource Symbols dialog. The two timer IDs are now in your application and ready for use.

Starting the Clock Timer

To start the clock timer, you need to edit the OnInitDialog function, as you did in the previous two days. Add the new code in Listing 4.1.

LISTING 4.1. THE OnInitDialog FUNCTION.

 1: BOOL CTimersDlg::OnInitDialog()
 2: {
 3:     CDialog::OnInitDialog();
 4: .
 5: .
 6: .
 7:     // TODO: Add extra initialization here
 8: 
 9:     ///////////////////////
10:     // MY CODE STARTS HERE
11:     ///////////////////////
12: 
13:     // Start the clock timer
14:     SetTimer(ID_CLOCK_TIMER, 1000, NULL);
15: 
16:     ///////////////////////
17:     // MY CODE ENDS HERE
18:     ///////////////////////
19: 
20:     return TRUE;  // return TRUE  unless you set the focus to a                       Âcontrol
21: }.

In this listing, you started the clock timer with the SetTimer function. The first argument that you passed to the SetTimer function is the ID for the clock timer. The second argument is how often you want to trigger the event. In this case, the clock timer event is triggered every 1,000 milliseconds, or about every second. The third argument is the address of an optional callback function that you can specify to bypass the WM_TIMER event. If you pass NULL for this argument, the WM_TIMER event is placed in the application message queue.


NOTE: A callback function is a function you create that is called directly by the Windows operating system. Callback functions have specific argument definitions, depending on which subsystem calls the function and why. After you get past the function definition, however, you can do whatever you want or need to do in the function.
A callback function works by passing the address of the function as an argument to a Windows function that accepts callback functions as arguments. When you pass the function address to Windows, your function is called directly every time the circumstances occur that require Windows to call the callback function.

Handling the Clock Timer Event

Now that you've started a timer, you need to add the code to handle the timer event message. You can do this by following these steps:

1. Using the Class Wizard, add a variable to the IDC_STATICTIME control of type CString named m_sTime.

2. Using the Class Wizard, add a function to handle the WM_TIMER message for the CTimersDlg object.

3. Edit the OnTimer function, adding the code in Listing 4.2.

LISTING 4.2. THE OnTimer FUNCTION.

 1: void CTimersDlg::OnTimer(UINT nIDEvent)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Get the current time
10:     CTime curTime = CTime::GetCurrentTime();
11: 
12:     // Display the current time
13:     m_sTime.Format("%d:%d:%d", curTime.GetHour(),
14:         curTime.GetMinute(),
15:         curTime.GetSecond());
16: 
17:     // Update the dialog
18:     UpdateData(FALSE);
19: 
20:     ///////////////////////
21:     // MY CODE ENDS HERE
22:     ///////////////////////
23: 
24:     CDialog::OnTimer(nIDEvent);
25: }

In this listing, you declare an instance of the CTime class, initializing it to the current system time. The next thing that you do is set the m_sTime string to the current time, using the Format method to format the time in the familiar HH:MM:SS format. Finally, you update the dialog window with the current time. If you compile and run your application now, you should see a clock running in the middle of your dialog window, as in Figure 4.4.

FIGURE 4.4. A running clock on your application dialog.

Adding a Second Timer to Your Application

As you have seen, adding a single timer to an application is a pretty simple task. All it takes is calling the SetTimer function and then placing the timer code in the OnTimer function. However, sometimes you need more than one timer running simultaneously in the same application. Then things get a little bit more involved.

Adding the Application Variables

Before you add the second timer to your application, you need to add a few variables to the controls. With the clock timer, you needed only a single variable for updating the clock display. Now you need to add a few other variables for the other controls, as listed in Table 4.2.

TABLE 4.2. CONTROL VARIABLES.

Object Name Category Type
IDC_STATICCOUNT m_sCount Value CString
IDC_INTERVAL m_iInterval Value int
IDC_STARTTIME m_cStartTime Control CButton
IDC_STOPTIMER m_cStopTime Control CButton

After you add all the variables using the Class Wizard, follow these steps:

1. Using the Class Wizard, select the m_iInterval variable and specify a Minimum Value of 1 and a Maximum Value of 100000 in the two edit boxes below the list of variables, as shown in Figure 4.5.

FIGURE 4.5. Specifying a range for a variable.

2. On the Class View tab in the workspace pane, add a member variable to the CTimersDlg class as you learned yesterday. Specify the variable type as int, the variable name as m_iCount, and the access as Private.

3. Using the Class Wizard, add a function on the EN_CHANGE event message for the IDC_INTERVAL control ID (the edit box). Edit the function and add the code in Listing 4.3.

LISTING 4.3. THE OnChangeInterval FUNCTION.

 1: void CTimersDlg::OnChangeInterval()
 2: {
 3:     // TODO: If this is a RICHEDIT control, the control will not
 4:     // send this notification unless you override the         ÂCDialog::OnInitialUpdate()
 5:     // function and call CRichEditCrtl().SetEventMask()
 6:     // with the EN_CHANGE flag ORed into the mask.
 7: 
 8:     // TODO: Add your control notification handler code here
 9: 
10:     ///////////////////////
11:     // MY CODE STARTS HERE
12:     ///////////////////////
13: 
14:     // Update the variables
15:     UpdateData(TRUE);
16: 
17:     ///////////////////////
18:     // MY CODE ENDS HERE
19:     ///////////////////////
20: }

When you specify a value range for the timer interval variable, Visual C++ automatically prompts the user, stating the available value range if the user enters a value outside of the specified range. This prompt is triggered by the UpdateData function call in the OnChangeInterval function. The last variable that was added through the workspace pane is used as the actual counter, which is incremented with each timer event.

Starting and Stopping the Counting Timer

To make your second timer operational, you need to

To implement this additional functionality, perform the following steps:

1. Edit the OnInitDialog function, updating the code as in Listing 4.4.

LISTING 4.4. THE UPDATED OnInitDialog FUNCTION.

 1: BOOL CTimersDlg::OnInitDialog()
 2: {
 3:     CDialog::OnInitDialog();
 4: .
 5: .
 6: .
 7:     // TODO: Add extra initialization here
 8: 
 9:     ///////////////////////
10:     // MY CODE STARTS HERE
11:     ///////////////////////
12: 
13:     // Initialize the counter interval
14:     m_iInterval = 100;
15: 
16:     // Update the dialog
17:     UpdateData(FALSE);
18: 
19:     // Start the clock timer
20:     SetTimer(ID_CLOCK_TIMER, 1000, NULL);
21: 
22:     ///////////////////////
23:     // MY CODE ENDS HERE
24:     ///////////////////////
25: 
26:     return TRUE;  // return TRUE  unless you set the focus to a                       Âcontrol
27: }

2. Using the Class Wizard, add a function to the BN_CLICKED message on the IDC_STARTTIME button. Edit the OnStarttime function as in Listing 4.5.

LISTING 4.5. THE OnStarttime FUNCTION.

 1: void CTimersDlg::OnStarttime()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Update the variables
10:     UpdateData(TRUE);
11: 
12:     // Initialize the count
13:     m_iCount = 0;
14:     // Format the count for displaying
15:     m_sCount.Format("%d", m_iCount);
16: 
17:     // Update the dialog
18:     UpdateData(FALSE);
19:     // Start the timer
20:     SetTimer(ID_COUNT_TIMER, m_iInterval, NULL);
21: 
22:     ///////////////////////
23:     // MY CODE ENDS HERE
24:     ///////////////////////
25: }

3. Using the Class Wizard, add a function to the BN_CLICKED message on the IDC_STOPTIMER button. Edit the OnStoptimer function as in Listing 4.6.

LISTING 4.6. THE OnStoptimer FUNCTION.

 1: void CTimersDlg::OnStoptimer()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Stop the timer
10:     KillTimer(ID_COUNT_TIMER);
11: 
12:     ///////////////////////
13:     // MY CODE ENDS HERE
14:     ///////////////////////
15: }

4. Edit the OnTimer function, updating the code as in Listing 4.7.

LISTING 4.7. THE UPDATED OnTimer FUNCTION.

 1: void CTimersDlg::OnTimer(UINT nIDEvent)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Get the current time
10:     CTime curTime = CTime::GetCurrentTime();
11: 
12:     // Which timer triggered this event?
13:     switch (nIDEvent)
14:     {
15:         // The clock timer?
16:     case ID_CLOCK_TIMER:
17:         // Display the current time
18:         m_sTime.Format("%d:%d:%d", curTime.GetHour(),
19:             curTime.GetMinute(),
20:             curTime.GetSecond());
21:         break;
22:         // The count timer?
23:     case ID_COUNT_TIMER:
24:         // Increment the count
25:         m_iCount++;
26:         // Format and display the count
27:         m_sCount.Format("%d", m_iCount);
28:         break;
29:     }
30: 
31:     // Update the dialog
32:     UpdateData(FALSE);
33: 
34:     ///////////////////////
35:     // MY CODE ENDS HERE
36:     ///////////////////////
37: 
38:     CDialog::OnTimer(nIDEvent);
39: }

In the OnInitDialog function, you added the initialization of the m_iInterval variable, starting it at 100. This initialization is reflected on the dialog window by calling the UpdateData function.

In the OnStarttime function, you first synchronize the variables with the control values, allowing you to get the current setting of the m_iInterval variable. Next, you initialize the m_iCount variable, setting it to 0, and then format the value in the m_sCount CString variable, which is updated in the dialog window. The last thing that you do is to start the timer, specifying the ID_COUNT_TIMER ID and using the interval from the m_iInterval variable.

In the OnStoptimer function, all you really need to do is stop the timer. You do this by calling the KillTimer function, passing the timer ID as the only argument.

It is in the OnTimer function that things begin to get interesting. Here, you still see the code for handling the clock timer event. To add the functionality for the counter timer, you need to determine which timer has triggered this function. The only argument to the OnTimer function just happens to be the timer ID. You can use this ID in a switch statement to determine which timer has called this function and to control which set of code is executed. The clock timer code is still the same as it was in Listing 4.2. The counter timer code is placed into its spot in the switch statement, incrementing the counter and then updating the m_sCount variable with the new value. You can compile and run your application at this point, and you can specify a timer interval and start the timer running, as in Figure 4.6.

FIGURE 4.6. A running counter on your application dialog.

Enabling the Stop Button

If you run your application, you'll find that it works well except for one small problem. When you start your second timer, you can't stop it. When you were specifying all the properties of the controls, you disabled the Stop Timer button. Before you can stop the timer, you need to enable this button.

What makes the most sense is enabling the stop button and disabling the start button once the timer starts. Then you reverse the situation when the timer stops again. You can do this in the same way you enabled and disabled controls on Day 2, or you can modify your approach just a little.

Remember that when you added variables to the controls, you added variables to the start and stop buttons. These were not normal variables, but control variables. Instead of getting a pointer to these controls using their IDs, you can work directly with the control variables. Try that now by updating the OnStarttime and OnStoptimer functions as in Listing 4.8.

LISTING 4.8. THE REVISED OnStarttime AND OnStoptimer FUNCTIONS.

 1: void CTimersDlg::OnStarttime()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Update the variables
10:     UpdateData(TRUE);
11: 
12:     // Initialize the count
13:     m_iCount = 0;
14:     // Format the count for displaying
15:     m_sCount.Format("%d", m_iCount);
16: 
17:     // Update the dialog
18:     UpdateData(FALSE);
19:     // Start the timer
20:     SetTimer(ID_COUNT_TIMER, m_iInterval, NULL);
21: 
22:     // Enable the Stop Timer button
23:     m_cStopTime.EnableWindow(TRUE);
24:     // Disable the Start Timer button
25:     m_cStartTime.EnableWindow(FALSE);
26: 
27:     ///////////////////////
28:     // MY CODE ENDS HERE
29:     ///////////////////////
30: }
31: 
32: void CTimersDlg::OnStoptimer()
33: {
34:     // TODO: Add your control notification handler code here
35: 
36:     ///////////////////////
37:     // MY CODE STARTS HERE
38:     ///////////////////////
39: 
40:     // Stop the timer
41:     KillTimer(ID_COUNT_TIMER);
42: 
43:     // Disable the Stop Timer button
44: