Стоит Windows 8 Pro, Visual Studio 2010 Express Edition . |
Работа с таймерами MFC
Управление блокировкой кнопок "Пуск" и "Стоп"
Для управления доступом кнопок воспользуемся переменными, которые были созданы нами ранее как экземпляры класса CButton
// Для управления кнопкой "Пуск" CButton m_cStartTimer; // Для управления кнопкой "Стоп" CButton m_cStopTimer;
Эти переменные связаны с идентификаторами соответствующих кнопок в функции DoDataExchange() и с помощью методов класса CButton мы можем управлять этими кнопками.
Связь переменных класса CButton с соответствующими ресурсами void CTimersDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Text(pDX, IDC_STATICTIME, m_curTime); DDX_Text(pDX, IDC_STATICDATE, m_strDate); DDX_Text(pDX, IDC_INTERVAL, m_iInterval); DDV_MinMaxInt(pDX, m_iInterval, 1, 100000); DDX_Control(pDX, IDC_STARTTIMER, m_cStartTimer); DDX_Control(pDX, IDC_STOPTIMER, m_cStopTimer); DDX_Text(pDX, IDC_STATICCOUNT, m_sCount); }
-
Дополните обработчики этих кнопок следующим кодом
Дополнения в обработчике кнопки "Пуск" void CTimersDlg::OnBnClickedStarttimer() { // TODO: Add your control notification handler code here // Инициализировать вспомогательную переменную // для счетчика срабатывания второго таймера m_iCount = 0; // Форматировать счетчик для отображения m_sCount.Format("%d", m_iCount); UpdateData(FALSE); // Обновить диалоговое окно SetTimer(ID_COUNT_TIMER, m_iInterval, NULL);// Запустить второй таймер // Блокировать кнопку пуска m_cStartTimer.EnableWindow(FALSE); // Разблокировать кнопку останова m_cStopTimer.EnableWindow(TRUE); }
Дополнения в обработчике кнопки "Стоп" void CTimersDlg::OnBnClickedStoptimer() { // TODO: Add your control notification handler code here KillTimer(ID_COUNT_TIMER); // Остановить второй таймер // Разблокировать кнопку пуска m_cStartTimer.EnableWindow(TRUE); // Блокировать кнопку останова m_cStopTimer.EnableWindow(FALSE); }
- Постройте проект и обратите внимание, что часы опять стали появляться с задержкой, пока не сработает в первый раз их таймер
Ранее при инициализации приложения именно для этой цели в функции OnInitDialog() мы посылали сообщение WM_TIMER, а в обработчике OnTimer() не было проверки, кто послал это сообщение. Теперь же обработчик принимает целевые сообщения от каждого таймера. Чтобы обработать нецелевое сообщение:
-
Добавьте в код обработчика следующее дополнение
Дополнение в обработчик OnTimer() void CTimersDlg::OnTimer(UINT nIDEvent) { // TODO: Add your message handler code here and/or call default // Получить текущее время // CTime curTime = CTime::GetCurrentTime(); curTime = CTime::GetCurrentTime(); // Который таймер запустил это событие switch(nIDEvent){ case ID_CLOCK_TIMER: // Первый таймер-часы default: // Извлечь текущее время в нужном формате m_curTime = curTime.Format("%H:%M:%S"); break; case ID_COUNT_TIMER: // Второй таймер-счетчик m_iCount++; // Увеличить счетчик // Форматировать счетчик для отображения m_sCount.Format("%d", m_iCount); } // Переслать значения из переменных приложения на экран UpdateData(FALSE); CDialog::OnTimer(nIDEvent); }
-
Постройте проект и убедитесь, что все наладилось
И еще одно, в функции OnInitDialog() вызов функции UpdateData(FALSE) можно убрать, поскольку в приведенном обработчике OnTimer() есть вызов этой функции, а мы все равно посылаем сообщение WM_TIMER строкой
PostMessage(WM_TIMER);// Исполнить функцию-обработчик OnTimer()
- Уберите вызов UpdateData(FALSE) из функции OnInitDialog()
Решение проблемы реентерабельности
Мы уже говорили, что если запустить приложение и ввести в поле редактирования значение вне установленного нами диапазона [1, 100000] для переменной m_iInterval, то возникнет конфликт реентерабельности, который приводит к аварии. Чтобы это исправить, введем в обработчик изменения поля OnEnChangeInterval() флаг, который будем обнулять перед вызовом функции UpdateData(), а после вызова - поднимать. Далее во всех других местах программы функцию UpdateData() будем выполнять только при условии, что флаг поднят, т.е. функция UpdateData() завершится.
- Добавьте в класс CTimersDlg логическую переменную updateFlag
- Инициализируйте ее значением true в конструкторе класса CTimersDlg
-
Добавьте в обработчик OnEnChangeInterval() следующий код
Дополнение в обработчик OnEnChangeInterval() void CTimersDlg::OnEnChangeInterval() { ................................................ // Переслать значения из поля ввода элементов управления, // отображаемые на экране, в соответствующие переменные updateFlag = false; UpdateData(TRUE); updateFlag = true; }
-
Найдите в обработчике OnTimer() вызов функции UpdateData() и посадите его на условие
if(updateFlag) UpdateData(FALSE);
-
В самое начало обработчика кнопки "Пуск" введите код
if(!updateFlag) return;
- Постройте приложение, удостовертесь что все работает, но не высвечивается начальное значение счетчика 0, как это было до введения второго таймера
- Сделайте, чтобы высвечивался нуль как и раньше
Сколько таймеров можно включить одновременно
В операционной системе Windows всем приложениям, запущенным одновременно, доступно ограниченное количество таймеров. Когда каждое приложение требует много таймеров и суммарное их количество в системе превышает допустимое значение, некоторым приложениям их число будет ограничено, а некоторым будет отказано вовсе. Поэтому рекомендуется следовать общему правилу: если предполагается использовать больше двух или трех таймеров, нужно перепроектировать приложение так, чтобы уменьшить их количество.