| Стоит 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 всем приложениям, запущенным одновременно, доступно ограниченное количество таймеров. Когда каждое приложение требует много таймеров и суммарное их количество в системе превышает допустимое значение, некоторым приложениям их число будет ограничено, а некоторым будет отказано вовсе. Поэтому рекомендуется следовать общему правилу: если предполагается использовать больше двух или трех таймеров, нужно перепроектировать приложение так, чтобы уменьшить их количество.

