Опубликован: 17.08.2010 | Доступ: свободный | Студентов: 999 / 59 | Оценка: 4.11 / 3.89 | Длительность: 29:38:00
Самостоятельная работа 5:

Работа с таймерами MFC

Добавление второго таймера к приложению

Добавление к приложению одного таймера представляет довольно простую задачу. Все, что для этого нужно сделать - создать идентификатор таймера и вызвать для него функцию 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 CString Для отображения значения счетчика

    Мастера добавления переменных приведены ниже:


    Примечание. Обратите внимание, что определив тип переменной как 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);
    }
Александр Даниленко
Александр Даниленко
Стоит Windows 8 Pro, Visual Studio 2010 Express Edition .