Таймеры в операционной системе Windows представляют собой механизм, с помощью которого можно вызывать на выполнение действия через заданный промежуток времени. Когда таймер срабатывает, приложению отсылается сообщение WM_TIMER, для которого можно построить обработчик.
События таймера ставятся в очередь событий приложения лишь в том случае, если приложение неактивно и его очередь сообщений пуста. Windows ставит в очередь событий только одно сообщение таймера. Если приложение активно и занято, сообщение таймера будет проигнорировано. Чтобы запустить или остановить таймер, нужно указать его идентификатор ID, который может принимать любое целое значение.
Создать приложение с двумя таймерами. Один таймер применить для поддержки часов приложения, второй - с управляемым пользователем интервалом срабатывания. Для этого нужно:
Создайте новый проект C++ на основе MFC и назовите его Timers
Укажите мастеру, что проект будет диалоговым приложением, остальные настройки мастера оставьте по умолчанию.
Установите для ресурсов русский язык
Создайте макет диалогового окна, показанный на рисунке. Для этого воспользуйтесь таблицей свойств элементов управления, приведенной ниже Таблица свойств главной диалоговой формы Timers | ||
|---|---|---|
| Элемент управления | Свойство | Значение |
| Главная форма | Caption |
Студент Иванов. Моделирование таймеров |
Font(Size) |
MS Sans Serif(10) |
|
Static Text |
Caption |
Сегодня |
Static Text |
Caption |
Текущая дата |
ID |
IDC_STATICDATE |
|
Static Text |
Caption |
Интервал таймера |
Edit Control |
ID |
IDC_INTERVAL |
Button |
ID |
IDC_STARTTIMER |
Caption |
Пуск | |
Button |
ID |
IDC_STOPTIMER |
Caption |
Стоп | |
Disabled |
True |
|
Static Text |
Caption |
Часы: |
Right Align Text |
True |
|
Static Text |
ID |
IDC_STATICTIME |
Caption |
Текущее время: | |
Static Text |
Caption |
Счетчик: |
Right Align Text |
True |
|
Static Text |
ID |
IDC_STATICCOUNT |
Caption |
0 | |
Button |
ID |
IDC_EXIT |
Caption |
Выход | |
Определите порядок перехода по клавише табуляции между элементами управления
Обеспечьте выход по кнопке ExitИнтерфейс окна приложения должен получиться таким

Включите режим
Выделите элемент IDC_EXIT
В панели свойств установите режим Control Evets
Добавьте обработчик сообщения BN_CLICKED (то же самое можно сделать двойным левым щелчком по кнопке)Обработчик сообщения OnBnClickedExit() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::OnBnClickedExit()
{
// TODO: Add your control notification handler code here
OnOK();
}
В данном приложении будут использованы два таймера: управляемый и неуправляемый. Чтобы однозначно определить таймер, ему нужно присвоить уникальный идентификатор и добавить в приложение
Выделите в панели ресурсов Resource View узел Timers.rc, затем либо через меню Edit оболочки, либо через контекстное меню узла выполните опцию Resource Symbols (символы ресурсов)
В диалоговом окне Resource Symbols щелкните на New (создать)
В диалоговом окне | Таймер времени | |
|---|---|
Name |
ID_CLOCK_TIMER |
Value |
1 |
| Таймер счетчика | |
Name |
ID_COUNT_TIMER |
Value |
2 |
Можно увидеть, что оболочка добавит в заголовочный файл ресурсов две строки:
Изменения в заголовочном файле ресурсов Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Timers.rc
//
#define ID_CLOCK_TIMER 1
#define ID_COUNT_TIMER 2
#define IDM_ABOUTBOX 0x0010
#define IDD_ABOUTBOX 100
#define IDS_ABOUTBOX 101
#define IDD_TIMERS_DIALOG 102
#define IDR_MAINFRAME 128
#define IDC_INTERVAL 1000
#define IDC_STARTTIMER 1001
#define IDC_STOPTIMER 1002
#define IDC_EXIT 1003
#define IDC_STATICCOUNT 1004
#define IDC_STATICTIME 1005
#define IDC_STATICDATE 1006
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 129
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1007
#define _APS_NEXT_SYMED_VALUE 103
#endif
#endif
Чтобы запустить таймер часов, отредактируйте функцию OnInitDialog() следующим образомИзменения в функции OnInitDialog() класса CTimersDlg файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
CDialog::OnInitDialog();
.....................................
// TODO: Add extra initialization here
// Запустить таймер часов
SetTimer(ID_CLOCK_TIMER, 1000, NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
Таймер запускается функцией SetTimer(). Первым аргументом является WM_TIMER. Третий необязательный аргумент - адрес функции обратного вызова, которая должна вызываться при каждом срабатывании таймера. Если поставить NULL (пустой указатель), то сообщение WM_TIMER будет отослано в очередь сообщений Windows.
Функция обратного вызова - это созданная разработчиком приложения функция, которая вызывается непосредственно операционной системой.
Добавьте к элементу управления IDC_STATICTIME, который сейчас имеет значение "Текущее время", переменную типа и назовите ее m_curTime. Для этого выделите элемент в дизайнере диалогового окна или через раскрывающийся список панели Properties. Затем через контекстное меню этого элемента или через меню оболочки Project вызовите мастер Add Variable...

Мастер добавит переменную в объявление класса и в функцию обмена данными
Изменения в объявлении класса CTimersDlg файла TimersDlg.h
class CTimersDlg : public CDialog
{
............................................
public:
// Переменная значения поля текста элемента IDC_STATICTIME
CString m_curTime;
};
Изменения в функции DoDataExchange() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_STATICTIME, m_curTime);
}
Добавьте к объекту класса CTimersDlg функцию-обработчик сообщения WM_TIMER, генерируемого таймером. Для этого в панели Class View выделите класс CTimersDlg и в панели свойств Properties установите режим Messages. Найдите сообщение WM_TIMER и назначьте для него обработчик OnTimer()Обработчик OnTimer() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
// Получить текущее время
CTime curTime = CTime::GetCurrentTime();
// Отобразить текущее время в нужном формате
m_curTime = curTime.Format("%H:%M:%S");
// Переслать значения из переменных приложения на экран
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}
Постройте приложение и убедитесь, что время появляется с задержкой в одну секунду после появления окна приложения. Исправьте это, добавив в функцию OnInitDialog() код посылки сообщения принудительного API -функцию PostMessage(), которую разместите сразу после строки создания объекта таймераИзменения в функции OnInitDialog() класса CTimersDlg файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
.....................................
// TODO: Add extra initialization here
// Запустить таймер часов
SetTimer(ID_CLOCK_TIMER, 1000, NULL);
PostMessage(WM_TIMER);
return TRUE; // return TRUE unless you set the focus to a control
}
Постройте приложение и убедитесь, что при появлении окна время уже установлено без задержки.Класс CTime библиотеки имеет много функциональных возможностей для работы с временем и датой. Некоторые функции, связанные с нашей задачей, приведены в таблице.
Таблица функций для извлечения времени и даты класса CTime | |
|---|---|
| Функция | Описание |
GetYear |
Возвращает год (в int ) |
GetMonth |
Возвращает месяц (в int ) |
GetDay |
Возвращает день месяца (в int ) |
GetHour |
Возвращает часы (в int ) |
GetMinute |
Возвращает минуты (в int ) |
GetSecond |
Возвращает секунды (в int ) |
GetDayOfWeek |
Возвращает день недели (1 - воскресенье, ..., 7 - суббота) (в int ) |
Format |
Возвращает местное время в формате, определяемом строкой форматирования. Строка форматирования передается данной функции в качестве параметра (в ). Коды форматов даты и времени приведены ниже. |
FormatGmt |
Возвращает среднее время по Гринвичу ( GMT ) в формате, определяемом строкой форматирования. Строка форматирования передается данной функции в качестве параметра (в ). Коды форматов даты и времени приведены ниже. |
Коды форматов времени и даты класса CTime | |
|---|---|
| Функция | Описание |
%a |
Сокращенное название дня недели |
%A |
Полное название дня недели |
%b |
Сокращенное название месяца |
%B |
Полное название месяца |
%c |
Стандартный формат даты и времени для данной местности (определяется параметрами, задаваемыми с помощью панели управления) |
%d |
День месяца |
%D |
Целое количество дней |
%H |
Время в 24-часовом формате |
%I |
Время в 12-часовом формате |
%j |
День года |
%m |
Порядковый номер месяца |
%M |
Минуты |
%p |
Индикатор AM/PM |
%S |
Секунды |
%U |
Порядковый номер недели в году (первым днем недели считается воскресенье) |
%w |
Порядковый номер дня недели |
%W |
Порядковый номер недели в году (первым днем недели считается понедельник) |
%x |
Дата в стандартном формате для данной местности |
%X |
Время в стандартном формате для данной местности |
%y |
Две последние цифры года (двузначное число) |
%Y |
Год полностью (четырехзначное число) |
%z, %Z |
Сокращенное или полное название часового пояса |
%% |
Знак процента |
Мы создали экземпляр curTime класса CTime как локальную переменную в функции - обработчике OnTimer(), который виден только внутри этой функции. Нам нужно вывести дату в диалоговую панель приложения только один раз при запуске приложения. Поэтому перенесем создание объекта curTime в описание класса CTimersDlg, чтобы он был виден во всех функциях этого класса, в том числе в функции OnInitDialog(), которая сработает один раз и выведет нам текущую дату.
Измените обработчик OnTimer(), убрав из него создание объекта curTimeИзменения в обработчике 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();
// Отобразить текущее время в нужном формате
m_curTime = curTime.Format("%H:%M:%S - %m");
// Переслать значения из переменных приложения на экран
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}
Добавьте объявление переменной curTime в объявление класса CTimersDlg файла TimersDlg.h (выполните это без применения мастера - вручную!)
Изменения в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
................................
private:
// Переменная-объект таймера
CTime curTime;
};
Добавим в функцию OnInitDialog() код извлечения текущей даты. Для этого создадим переменную m_strDate, ассоциированную с ресурсом IDC_STATICDATE:
Перейдите в дизайнер форм и выделите форму
В раскрывающемся списке панели
Через меню Project/Add Variable

вызовите мастер добавления переменных и настройте его так

Мастер внесет такие изменения в программу:
Изменения в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
................................
private:
// Переменная-объект таймера
CTime curTime;
// Переменная для отображения текущей даты
CString m_strDate;
};
Изменения в файлe TimersDlg.cpp
CTimersDlg::CTimersDlg(CWnd* pParent /*=NULL*/)
: CDialog(CTimersDlg::IDD, pParent)
, m_curTime(_T(""))
, m_strDate(_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);
}
Поскольку код извлечения даты предполагается относительно большой, упакуем его в отдельную функцию. Добавьте в класс CTimersDlg функцию-член извлечения текущей даты. Для этого:
В панели Class View выделите класс CTimersDlg и через контекстное меню Add/Add Function вызовите мастер добавления функций. Настройте его так, как приведено на рисунке

Мастер добавит код в два файла
Добавления в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
............................................
private:
// Переменная-объект таймера
CTime curTime;
// Переменная для отображения текущей даты
CString m_strDate;
// Извлечение текущей даты
CString GetCurDate(void);
};
Добавления в файлe TimersDlg.cpp
// Извлечение текущей даты
CString CTimersDlg::GetCurDate(void)
{
return CString();
}
Наполните функцию GetCurDate() кодом извлечения датыФункция GetCurDate() файла TimersDlg.cpp
// Извлечение текущей даты
CString CTimersDlg::GetCurDate(void)
{
// массив месяцев
char *month[] = {"января"
,"февраля"
,"марта"
,"апреля"
,"мая"
,"июня"
,"июля"
,"августа"
,"сентября"
,"октября"
,"ноября"
,"декабря"};
// массив дней недели
char *week[] = { "воскресенье"
,"понедельник"
,"вторник"
,"среда"
,"четверг"
,"пятница"
,"суббота" };
CString str; // Инициализируется пустой строкой при создании
int m_day, m_month, m_week, m_year;
m_day = curTime.GetDay(); // Число месяца
m_month = curTime.GetMonth(); // 1 - Январь
m_week = curTime.GetDayOfWeek(); // 1 - Воскресенье
m_year = curTime.GetYear(); // Год
char tmp[5]; // Для размещения числа месяца, года и нуль-символа
itoa(m_day, tmp, 10);// Преобразуем целое в строку в десятичном формате
str += tmp;
str += " ";
str += month[m_month - 1];
str += " (";
str += week[m_week - 1];
str += ") - ";
itoa(m_year, tmp, 10); // Преобразуем число-год в строку-год
str += tmp;
str += " г.";
return str;
}
Разместите в функции OnInitDialog() вызов нашей функции извлечения датыДобавление кода в функцию OnInitDialog() файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
....................................................
// TODO: Add extra initialization here
// Запустить таймер часов
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
}
Постройте приложение и убедитесь в работоспособности кодаРезультат должен выглядеть приблизительно так:
Добавление к приложению одного таймера представляет довольно простую задачу. Все, что для этого нужно сделать - создать SetTimer() запуска таймера. Но иногда встречаются ситуации, требующие одновременной работы в одном приложении с несколькими таймерами с разным периодом срабатывания. В этом случае задача несколько усложняется. Рассмотрим задачу создания второго таймера с управляемым периодом срабатывания.
Введите переменные для манипулирования со вторым таймером, ассоциированные с элементами управления интерфейса диалогового окна в соответствии с таблицей| Переменные, ассоциированные с элементами управления интерфейса | ||||
|---|---|---|---|---|
ID элемента управления |
Имя переменной | Категория | Тип переменной | Комментарии |
IDC_INTERVAL |
m_iInterval |
Value |
int |
Для хранения значения поля Edit Control |
IDC_STARTTIMER |
m_cStartTimer |
Control |
CButton |
Для управления кнопкой "Пуск" |
IDC_STOPTIMER |
m_cStopTimer |
Control |
CButton |
Для управления кнопкой "Стоп" |
IDC_STATICCOUNT |
m_sCount |
Value |
|
Для отображения значения счетчика |
Мастера добавления переменных приведены ниже:

Примечание. Обратите внимание, что определив тип переменной как 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, предварительно установив ее в режим
Заполните обработчик следующим кодомОбработчик сообщения 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);
}
Для управления доступом кнопок воспользуемся переменными, которые были созданы нами ранее как экземпляры класса 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 всем приложениям, запущенным одновременно, доступно ограниченное количество таймеров. Когда каждое приложение требует много таймеров и суммарное их количество в системе превышает допустимое значение, некоторым приложениям их число будет ограничено, а некоторым будет отказано вовсе. Поэтому рекомендуется следовать общему правилу: если предполагается использовать больше двух или трех таймеров, нужно перепроектировать приложение так, чтобы уменьшить их количество.
Таймеры в операционной системе Windows представляют собой механизм, с помощью которого можно вызывать на выполнение действия через заданный промежуток времени. Когда таймер срабатывает, приложению отсылается сообщение WM_TIMER, для которого можно построить обработчик.
События таймера ставятся в очередь событий приложения лишь в том случае, если приложение неактивно и его очередь сообщений пуста. Windows ставит в очередь событий только одно сообщение таймера. Если приложение активно и занято, сообщение таймера будет проигнорировано. Чтобы запустить или остановить таймер, нужно указать его идентификатор ID, который может принимать любое целое значение.
Создать приложение с двумя таймерами. Один таймер применить для поддержки часов приложения, второй - с управляемым пользователем интервалом срабатывания. Для этого нужно:
Создайте новый проект C++ на основе MFC и назовите его Timers
Укажите мастеру, что проект будет диалоговым приложением, остальные настройки мастера оставьте по умолчанию.
Установите для ресурсов русский язык
Создайте макет диалогового окна, показанный на рисунке. Для этого воспользуйтесь таблицей свойств элементов управления, приведенной ниже Таблица свойств главной диалоговой формы Timers | ||
|---|---|---|
| Элемент управления | Свойство | Значение |
| Главная форма | Caption |
Студент Иванов. Моделирование таймеров |
Font(Size) |
MS Sans Serif(10) |
|
Static Text |
Caption |
Сегодня |
Static Text |
Caption |
Текущая дата |
ID |
IDC_STATICDATE |
|
Static Text |
Caption |
Интервал таймера |
Edit Control |
ID |
IDC_INTERVAL |
Button |
ID |
IDC_STARTTIMER |
Caption |
Пуск | |
Button |
ID |
IDC_STOPTIMER |
Caption |
Стоп | |
Disabled |
True |
|
Static Text |
Caption |
Часы: |
Right Align Text |
True |
|
Static Text |
ID |
IDC_STATICTIME |
Caption |
Текущее время: | |
Static Text |
Caption |
Счетчик: |
Right Align Text |
True |
|
Static Text |
ID |
IDC_STATICCOUNT |
Caption |
0 | |
Button |
ID |
IDC_EXIT |
Caption |
Выход | |
Определите порядок перехода по клавише табуляции между элементами управления
Обеспечьте выход по кнопке ExitИнтерфейс окна приложения должен получиться таким

Включите режим
Выделите элемент IDC_EXIT
В панели свойств установите режим Control Evets
Добавьте обработчик сообщения BN_CLICKED (то же самое можно сделать двойным левым щелчком по кнопке)Обработчик сообщения OnBnClickedExit() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::OnBnClickedExit()
{
// TODO: Add your control notification handler code here
OnOK();
}
В данном приложении будут использованы два таймера: управляемый и неуправляемый. Чтобы однозначно определить таймер, ему нужно присвоить уникальный идентификатор и добавить в приложение
Выделите в панели ресурсов Resource View узел Timers.rc, затем либо через меню Edit оболочки, либо через контекстное меню узла выполните опцию Resource Symbols (символы ресурсов)
В диалоговом окне Resource Symbols щелкните на New (создать)
В диалоговом окне | Таймер времени | |
|---|---|
Name |
ID_CLOCK_TIMER |
Value |
1 |
| Таймер счетчика | |
Name |
ID_COUNT_TIMER |
Value |
2 |
Можно увидеть, что оболочка добавит в заголовочный файл ресурсов две строки:
Изменения в заголовочном файле ресурсов Resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Timers.rc
//
#define ID_CLOCK_TIMER 1
#define ID_COUNT_TIMER 2
#define IDM_ABOUTBOX 0x0010
#define IDD_ABOUTBOX 100
#define IDS_ABOUTBOX 101
#define IDD_TIMERS_DIALOG 102
#define IDR_MAINFRAME 128
#define IDC_INTERVAL 1000
#define IDC_STARTTIMER 1001
#define IDC_STOPTIMER 1002
#define IDC_EXIT 1003
#define IDC_STATICCOUNT 1004
#define IDC_STATICTIME 1005
#define IDC_STATICDATE 1006
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 129
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1007
#define _APS_NEXT_SYMED_VALUE 103
#endif
#endif
Чтобы запустить таймер часов, отредактируйте функцию OnInitDialog() следующим образомИзменения в функции OnInitDialog() класса CTimersDlg файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
CDialog::OnInitDialog();
.....................................
// TODO: Add extra initialization here
// Запустить таймер часов
SetTimer(ID_CLOCK_TIMER, 1000, NULL);
return TRUE; // return TRUE unless you set the focus to a control
}
Таймер запускается функцией SetTimer(). Первым аргументом является WM_TIMER. Третий необязательный аргумент - адрес функции обратного вызова, которая должна вызываться при каждом срабатывании таймера. Если поставить NULL (пустой указатель), то сообщение WM_TIMER будет отослано в очередь сообщений Windows.
Функция обратного вызова - это созданная разработчиком приложения функция, которая вызывается непосредственно операционной системой.
Добавьте к элементу управления IDC_STATICTIME, который сейчас имеет значение "Текущее время", переменную типа и назовите ее m_curTime. Для этого выделите элемент в дизайнере диалогового окна или через раскрывающийся список панели Properties. Затем через контекстное меню этого элемента или через меню оболочки Project вызовите мастер Add Variable...

Мастер добавит переменную в объявление класса и в функцию обмена данными
Изменения в объявлении класса CTimersDlg файла TimersDlg.h
class CTimersDlg : public CDialog
{
............................................
public:
// Переменная значения поля текста элемента IDC_STATICTIME
CString m_curTime;
};
Изменения в функции DoDataExchange() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_STATICTIME, m_curTime);
}
Добавьте к объекту класса CTimersDlg функцию-обработчик сообщения WM_TIMER, генерируемого таймером. Для этого в панели Class View выделите класс CTimersDlg и в панели свойств Properties установите режим Messages. Найдите сообщение WM_TIMER и назначьте для него обработчик OnTimer()Обработчик OnTimer() класса CTimersDlg файла TimersDlg.cpp
void CTimersDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
// Получить текущее время
CTime curTime = CTime::GetCurrentTime();
// Отобразить текущее время в нужном формате
m_curTime = curTime.Format("%H:%M:%S");
// Переслать значения из переменных приложения на экран
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}
Постройте приложение и убедитесь, что время появляется с задержкой в одну секунду после появления окна приложения. Исправьте это, добавив в функцию OnInitDialog() код посылки сообщения принудительного API -функцию PostMessage(), которую разместите сразу после строки создания объекта таймераИзменения в функции OnInitDialog() класса CTimersDlg файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
.....................................
// TODO: Add extra initialization here
// Запустить таймер часов
SetTimer(ID_CLOCK_TIMER, 1000, NULL);
PostMessage(WM_TIMER);
return TRUE; // return TRUE unless you set the focus to a control
}
Постройте приложение и убедитесь, что при появлении окна время уже установлено без задержки.Класс CTime библиотеки имеет много функциональных возможностей для работы с временем и датой. Некоторые функции, связанные с нашей задачей, приведены в таблице.
Таблица функций для извлечения времени и даты класса CTime | |
|---|---|
| Функция | Описание |
GetYear |
Возвращает год (в int ) |
GetMonth |
Возвращает месяц (в int ) |
GetDay |
Возвращает день месяца (в int ) |
GetHour |
Возвращает часы (в int ) |
GetMinute |
Возвращает минуты (в int ) |
GetSecond |
Возвращает секунды (в int ) |
GetDayOfWeek |
Возвращает день недели (1 - воскресенье, ..., 7 - суббота) (в int ) |
Format |
Возвращает местное время в формате, определяемом строкой форматирования. Строка форматирования передается данной функции в качестве параметра (в ). Коды форматов даты и времени приведены ниже. |
FormatGmt |
Возвращает среднее время по Гринвичу ( GMT ) в формате, определяемом строкой форматирования. Строка форматирования передается данной функции в качестве параметра (в ). Коды форматов даты и времени приведены ниже. |
Коды форматов времени и даты класса CTime | |
|---|---|
| Функция | Описание |
%a |
Сокращенное название дня недели |
%A |
Полное название дня недели |
%b |
Сокращенное название месяца |
%B |
Полное название месяца |
%c |
Стандартный формат даты и времени для данной местности (определяется параметрами, задаваемыми с помощью панели управления) |
%d |
День месяца |
%D |
Целое количество дней |
%H |
Время в 24-часовом формате |
%I |
Время в 12-часовом формате |
%j |
День года |
%m |
Порядковый номер месяца |
%M |
Минуты |
%p |
Индикатор AM/PM |
%S |
Секунды |
%U |
Порядковый номер недели в году (первым днем недели считается воскресенье) |
%w |
Порядковый номер дня недели |
%W |
Порядковый номер недели в году (первым днем недели считается понедельник) |
%x |
Дата в стандартном формате для данной местности |
%X |
Время в стандартном формате для данной местности |
%y |
Две последние цифры года (двузначное число) |
%Y |
Год полностью (четырехзначное число) |
%z, %Z |
Сокращенное или полное название часового пояса |
%% |
Знак процента |
Мы создали экземпляр curTime класса CTime как локальную переменную в функции - обработчике OnTimer(), который виден только внутри этой функции. Нам нужно вывести дату в диалоговую панель приложения только один раз при запуске приложения. Поэтому перенесем создание объекта curTime в описание класса CTimersDlg, чтобы он был виден во всех функциях этого класса, в том числе в функции OnInitDialog(), которая сработает один раз и выведет нам текущую дату.
Измените обработчик OnTimer(), убрав из него создание объекта curTimeИзменения в обработчике 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();
// Отобразить текущее время в нужном формате
m_curTime = curTime.Format("%H:%M:%S - %m");
// Переслать значения из переменных приложения на экран
UpdateData(FALSE);
CDialog::OnTimer(nIDEvent);
}
Добавьте объявление переменной curTime в объявление класса CTimersDlg файла TimersDlg.h (выполните это без применения мастера - вручную!)
Изменения в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
................................
private:
// Переменная-объект таймера
CTime curTime;
};
Добавим в функцию OnInitDialog() код извлечения текущей даты. Для этого создадим переменную m_strDate, ассоциированную с ресурсом IDC_STATICDATE:
Перейдите в дизайнер форм и выделите форму
В раскрывающемся списке панели
Через меню Project/Add Variable

вызовите мастер добавления переменных и настройте его так

Мастер внесет такие изменения в программу:
Изменения в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
................................
private:
// Переменная-объект таймера
CTime curTime;
// Переменная для отображения текущей даты
CString m_strDate;
};
Изменения в файлe TimersDlg.cpp
CTimersDlg::CTimersDlg(CWnd* pParent /*=NULL*/)
: CDialog(CTimersDlg::IDD, pParent)
, m_curTime(_T(""))
, m_strDate(_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);
}
Поскольку код извлечения даты предполагается относительно большой, упакуем его в отдельную функцию. Добавьте в класс CTimersDlg функцию-член извлечения текущей даты. Для этого:
В панели Class View выделите класс CTimersDlg и через контекстное меню Add/Add Function вызовите мастер добавления функций. Настройте его так, как приведено на рисунке

Мастер добавит код в два файла
Добавления в файлe TimersDlg.h
class CTimersDlg : public CDialog
{
............................................
private:
// Переменная-объект таймера
CTime curTime;
// Переменная для отображения текущей даты
CString m_strDate;
// Извлечение текущей даты
CString GetCurDate(void);
};
Добавления в файлe TimersDlg.cpp
// Извлечение текущей даты
CString CTimersDlg::GetCurDate(void)
{
return CString();
}
Наполните функцию GetCurDate() кодом извлечения датыФункция GetCurDate() файла TimersDlg.cpp
// Извлечение текущей даты
CString CTimersDlg::GetCurDate(void)
{
// массив месяцев
char *month[] = {"января"
,"февраля"
,"марта"
,"апреля"
,"мая"
,"июня"
,"июля"
,"августа"
,"сентября"
,"октября"
,"ноября"
,"декабря"};
// массив дней недели
char *week[] = { "воскресенье"
,"понедельник"
,"вторник"
,"среда"
,"четверг"
,"пятница"
,"суббота" };
CString str; // Инициализируется пустой строкой при создании
int m_day, m_month, m_week, m_year;
m_day = curTime.GetDay(); // Число месяца
m_month = curTime.GetMonth(); // 1 - Январь
m_week = curTime.GetDayOfWeek(); // 1 - Воскресенье
m_year = curTime.GetYear(); // Год
char tmp[5]; // Для размещения числа месяца, года и нуль-символа
itoa(m_day, tmp, 10);// Преобразуем целое в строку в десятичном формате
str += tmp;
str += " ";
str += month[m_month - 1];
str += " (";
str += week[m_week - 1];
str += ") - ";
itoa(m_year, tmp, 10); // Преобразуем число-год в строку-год
str += tmp;
str += " г.";
return str;
}
Разместите в функции OnInitDialog() вызов нашей функции извлечения датыДобавление кода в функцию OnInitDialog() файла TimersDlg.cpp
BOOL CTimersDlg::OnInitDialog()
{
....................................................
// TODO: Add extra initialization here
// Запустить таймер часов
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
}
Постройте приложение и убедитесь в работоспособности кодаРезультат должен выглядеть приблизительно так:

Добавление к приложению одного таймера представляет довольно простую задачу. Все, что для этого нужно сделать - создать SetTimer() запуска таймера. Но иногда встречаются ситуации, требующие одновременной работы в одном приложении с несколькими таймерами с разным периодом срабатывания. В этом случае задача несколько усложняется. Рассмотрим задачу создания второго таймера с управляемым периодом срабатывания.
Введите переменные для манипулирования со вторым таймером, ассоциированные с элементами управления интерфейса диалогового окна в соответствии с таблицей| Переменные, ассоциированные с элементами управления интерфейса | ||||
|---|---|---|---|---|
ID элемента управления |
Имя переменной | Категория | Тип переменной | Комментарии |
IDC_INTERVAL |
m_iInterval |
Value |
int |
Для хранения значения поля Edit Control |
IDC_STARTTIMER |
m_cStartTimer |
Control |
CButton |
Для управления кнопкой "Пуск" |
IDC_STOPTIMER |
m_cStopTimer |
Control |
CButton |
Для управления кнопкой "Стоп" |
IDC_STATICCOUNT |
m_sCount |
Value |
|
Для отображения значения счетчика |
Мастера добавления переменных приведены ниже:

Примечание. Обратите внимание, что определив тип переменной как 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, предварительно установив ее в режим
Заполните обработчик следующим кодомОбработчик сообщения 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);
}
Для управления доступом кнопок воспользуемся переменными, которые были созданы нами ранее как экземпляры класса 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 всем приложениям, запущенным одновременно, доступно ограниченное количество таймеров. Когда каждое приложение требует много таймеров и суммарное их количество в системе превышает допустимое значение, некоторым приложениям их число будет ограничено, а некоторым будет отказано вовсе. Поэтому рекомендуется следовать общему правилу: если предполагается использовать больше двух или трех таймеров, нужно перепроектировать приложение так, чтобы уменьшить их количество.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.