Стоит Windows 8 Pro, Visual Studio 2010 Express Edition . |
Работа с таймерами MFC
Добавление второго таймера к приложению
Добавление к приложению одного таймера представляет довольно простую задачу. Все, что для этого нужно сделать - создать идентификатор таймера и вызвать для него функцию SetTimer() запуска таймера. Но иногда встречаются ситуации, требующие одновременной работы в одном приложении с несколькими таймерами с разным периодом срабатывания. В этом случае задача несколько усложняется. Рассмотрим задачу создания второго таймера с управляемым периодом срабатывания.
Добавление к приложению новых переменных
-
Введите переменные для манипулирования со вторым таймером, ассоциированные с элементами управления интерфейса диалогового окна в соответствии с таблицей
Мастера добавления переменных приведены ниже:
Примечание. Обратите внимание, что определив тип переменной как int, мы тем самым ограничили возможность ввода в закрепленное за этой переменной поле ввода элемента редактирования только цифрами. Кроме того, задав диапазон изменения переменной, мы установили автоматический контроль за диапазоном пользовательского ввода. Теперь, если в соответствующее поле пользователь введет значение, выходящее за пределы допустимого диапазона, то приложение автоматически выведет подсказку с интервалом значений данной переменной. Вывод подсказки обеспечит функция UpdateData(), которую мы определим ниже в обработчике OnEnChangeInterval() изменений, внесенных в поле редактирования.
Проблема. Здесь есть потенциальная ошибка, связанная с тем, что функция UpdateData() применяется еще и в обработчике сообщений WM_TIMER, регулярно посылаемых запущенным таймером. Если пользователь введет в поле редактирования недопустимое значение, то функция UpdateData() в обработчике OnEnChangeInterval(), обнаружив это, остановится, выдаст сообщение и будет ждать правильных значений. В то же время поступит очередное сообщение от таймера и сработает функция-обработчик OnTimer(), где тоже вызывается функция UpdateData(), которая еще не закончила работу в обработчике OnEnChangeInterval(). Возникнет конфликт реентерабельности, который приведет к аварийному останову приложения. Решение этой проблемы приведено ниже.
Примечание. Обратите внимание, что когда мы вносим изменения в программу с помощью мастеров, соответствующие файлы с кодом модифицируются, но оболочка их не отмечает как измененные и если ее сразу закрыть, то файлы автоматически обновляются. Видимо ребята из Microsoft чуток перемудрили (слава им...). Аналогично, нет возможности через мышку вставить содержимое буфера обмена в поля мастера, приходится это делать через клавиатуру (Ctrl-V) . Аналогично, когда хочется изменить иконку приложения, вставляя новый файл в каталог res и переименовывая его в имя приложения, то проект нужно силовым порядком перестроить, чтобы файл ресурсов перекомпилировался, так как процедура make оболочки это не учитывает.
Мастер добавления переменных внесет следующие изменения в код программы
Изменения в объявлении класса CTimersDlg файла CTimersDlg.h // CTimersDlg dialog class CTimersDlg : public CDialog { ............................................................. public: // Переменная значения поля текста элемента IDC_STATICTIME CString m_curTime; afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnBnClickedExit(); private: // Переменная-объект таймера CTime curTime; // Переменная для отображения текущей даты CString m_strDate; // Извлечение текущей даты CString GetCurDate(void); public: // Для хранения значения поля Edit Control int m_iInterval; // Для управления кнопкой "Пуск" CButton m_cStartTimer; // Для управления кнопкой "Стоп" CButton m_cStopTimer; // Для отображения значения счетчика CString m_sCount; };
Изменения в файле реализации класса CTimersDlg.cpp CTimersDlg::CTimersDlg(CWnd* pParent /*=NULL*/) : CDialog(CTimersDlg::IDD, pParent) , m_curTime(_T("")) , m_strDate(_T("")) , m_iInterval(0) , m_sCount(_T("")) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } 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); }
-
Добавьте через мастер (или вручную) еще одну вспомогательную переменную, которая будет фактически использоваться как счетчик и значение которой будет возрастать с каждым событием таймера. Для этого в панели Class View выделите класс CTimersDlg и через контекстное меню или главное меню оболочки Project/Add Variable... настройте мастер так
Получим следующие добавления кода
Добавление в объявлении класса CTimersDlg файла CTimersDlg.h // CTimersDlg dialog class CTimersDlg : public CDialog { ............................................................. public: // Для хранения значения поля Edit Control int m_iInterval; // Для управления кнопкой "Пуск" CButton m_cStartTimer; // Для управления кнопкой "Стоп" CButton m_cStopTimer; // Для отображения значения счетчика CString m_sCount; private: // Вспомогательная переменная для счетчика срабатывания второго таймера int m_iCount; };
Изменения в файле реализации класса CTimersDlg.cpp CTimersDlg::CTimersDlg(CWnd* pParent /*=NULL*/) : CDialog(CTimersDlg::IDD, pParent) , m_curTime(_T("")) , m_strDate(_T("")) , m_iInterval(0) , m_sCount(_T("")) , m_iCount(0) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
Добавим функцию для обработки сообщения о событии EN_CHANGE для элемента управления Edit Control, имеющего идентификатор IDC_INTERVAL. Обработчик будет выполняться всякий раз после того, как пользователь изменит содержимое поля редактирования и нажмет клавишу Enter или выйдет из фокуса мышью после изменения поля. Для этого:
- В режиме дизайна формы выделите элемент поля редактирования и в панели свойств Properties, предварительно установив ее в режим Control Events, создайте обработчик (это же можно сделать, если дважды щелкнуть по полю события EN_CHANGE )
-
Заполните обработчик следующим кодом
Обработчик сообщения EN_CHANGE об изменении содержимого поля редактирования в файле реализации класса CTimersDlg.cpp void CTimersDlg::OnEnChangeInterval() { // TODO: If this is a RICHEDIT control, the control will not // send this notification unless you override the CDialog::OnInitDialog() // function and call CRichEditCtrl().SetEventMask() // with the ENM_CHANGE flag ORed into the mask. // TODO: Add your control notification handler code here // Переслать значения из поля ввода элементов управления, // отображаемые на экране, в соответствующие переменные UpdateData(TRUE); }
Отметьте также, что оболочка при создании обработчика сообщения обязательно его регистрирует в таблице сообщений класса окна, которому операционная система и передаст это сообщение для обработки, а также вставляет в описание класса прототип функции - обработчика.
Регистрация обработчика сообщения в таблице сообщений класса диалогового окна CTimersDlg BEGIN_MESSAGE_MAP(CTimersDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP ON_WM_TIMER() ON_BN_CLICKED(IDC_EXIT, OnBnClickedExit) ON_EN_CHANGE(IDC_INTERVAL, OnEnChangeInterval) END_MESSAGE_MAP()
Прототип функции-обработчика в классе CTimersDlg class CTimersDlg : public CDialog { ......................................................... private: // Вспомогательная переменная для счетчика срабатывания второго таймера int m_iCount; public: afx_msg void OnEnChangeInterval(); };
Управление вторым таймером
Введем код, изменяющий режим работы второго таймера.
Запуск и остановка счетчика второго таймера
Чтобы перевести второй таймер в рабочее состояние, необходимо выполнить следующие действия:
-
инициализировать переменную m_iInterval, связанную с содержимым поля ввода, начальным значением, поскольку оболочка автоматически инициализировала ее нулем в конструкторе класса CTimersDlg
Конструктор класса CTimersDlg CTimersDlg::CTimersDlg(CWnd* pParent /*=NULL*/) : CDialog(CTimersDlg::IDD, pParent) , m_curTime(_T("")) , m_strDate(_T("")) , m_iInterval(0) , m_sCount(_T("")) , m_iCount(0) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
- запустить таймер сразу после щелчка по кнопке "Пуск" (элемент IDC_STARTTIMER )
- после каждого щелчка увеличивать вспомогательную переменную m_iCount и обновлять через закрепленную за элементом IDC_STATICCOUNT переменную m_sCount вывод на экран
- остановить таймер сразу после щелчка по кнопке "Стоп" (элемент IDC_STOPTIMER )
- попеременно блокировать и разблокировать кнопки "Пуск" и "Стоп"
Для реализации этого плана выполните следующие пошаговые инструкции
-
Добаьте код инициализации интервала срабатывания второго таймера в функцию OnInitDialog()
Добавление в функцию OnInitDialog() файла TimersDlg.cpp BOOL CTimersDlg::OnInitDialog() { ..................................................... // TODO: Add extra initialization here // Инициализировать интервал срабатывания второго таймера m_iInterval = 100; // Для хранения значения поля Edit Control UpdateData(FALSE); // Обновить диалоговое окно // Запустить таймер часов SetTimer(ID_CLOCK_TIMER, 1000, NULL); curTime = CTime::GetCurrentTime(); // Заполнить объект таймера m_strDate = GetCurDate(); // Наша функция извлечения даты PostMessage(WM_TIMER); // Исполнить функцию-обработчик OnTimer() return TRUE; // return TRUE unless you set the focus to a control }
-
Добавьте обработчик сообщения BN_CLICKED от кнопки "Пуск" (идентификатор элемента управления IDC_STARTTIMER )
Обработчик кнопки "Пуск" файла TimersDlg.cpp 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);// Запустить второй таймер }
-
Добавьте обработчик сообщения BN_CLICKED от кнопки "Стоп" (идентификатор элемента управления IDC_STOPTIMER )
Обработчик кнопки "Стоп" файла TimersDlg.cpp void CTimersDlg::OnBnClickedStoptimer() { // TODO: Add your control notification handler code here KillTimer(ID_COUNT_TIMER); // Остановить второй таймер }
-
Отредактируйте созданную ранее функцию-обработчик OnTimer() сообщения срабатывания таймера WM_TIMER так
Изменения в обработчике OnTimer() файла TimersDlg.cpp 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: // Первый таймер-часы // Извлечь текущее время в нужном формате 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); }