Today you are going to learn
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.
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.
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:
FIGURE 4.1. The Timers application dialog layout.
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 |
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:
FIGURE 4.2. The Resource pop-up menu.
FIGURE 4.3. Adding a new resource symbol.
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.
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.
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: 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.
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.
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.
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:
FIGURE 4.5. Specifying a range for a variable.
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.
To make your second timer operational, you need to
To implement this additional functionality, perform the following steps:
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: }
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: }
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: }
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.
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.
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: