7. Таймер

Приложения Presentation Manager могут следить за временем или выполнять какие-либо периодические действия с использованием таймера. Однако в отличие от программы MS-DOS , где для использования таймера было необходимо перехватывать соответствующее аппаратное прерывание, работа с таймером в Presentation Manager основана на передаче сообщений.

В своем приложении вы можете запустить несколько таймеров, каждый из которых будет периодически посылать в функцию окна сообщение с кодом WM_TIMER . В первом параметре этого сообщения передается идентификатор созданного таймера, поэтому нетрудно определить, от какого таймера пришло сообщение.

Заметим, что таймеры, запущенные с использованием функций Presentation Manager, не подходят для прецизионного измерения времени. Дело в том, что сообщения от таймера попадает в очередь сообщений приложения и обрабатываются наряду с другими сообщениями, поэтому они могут оказаться задержанными.

Если вы собираетесь создавать на базе IBM OS/2 систему управления, работающую в реальном режиме времени, не исключено, что вам придется создавать собственный драйвер или использовать другие приемы работы с физическим таймером компьютера. Однако для диалоговых программ задержки в поступлении сообщений от таймера не играют большой роли.

7.1. Запуск и останов таймера

Для запуска таймера вы должны использовать функцию WinStartTimer , прототип которой приведен ниже:

ULONG WinStartTimer (
  HAB   hab,       // идентификатора блока Anchor-block
  HWND  hwnd,      // идентификатор окна
  ULONG idTimer,   // идентификатор таймера
  ULONG dtTimeout);// задержка в мс

После запуска таймера функция окна hwnd будет получать сообщения WM_TIMER с периодом, заданным параметром dtTimeout. Для операционной системы IBM OS/2 Warp версии 3.0 период должен быть целым числом в диапазоне от 0 до 4294976295. Предыдущие версии этой операционной системы допускали задавать период в диапазоне от 0 до 65535. Через параметр idTimer вы должны передать функции идентификатор таймера.

В случае успеха функция WinStartTimer возвращает идентификатор таймера. Если произошла ошибка, возвращается нулевое значение.

Вот пример использования функции WinStartTimer :

#define ID_APP_TIMER1   1
WinStartTimer (hab, hWnd, ID_APP_TIMER1, 1000);

Для остановки таймера вы должны воспользоваться функцией WinStopTimer :

BOOL WinStopTimer (
  HAB   hab,      // идентификатора блока Anchor-block
  HWND  hwnd,     // идентификатор окна
  ULONG idTimer); // идентификатор таймера

Через параметр idTimer этой функции необходимо передать идентификатор таймера, запущенного ранее функцией WinStartTimer .

7.2. Сообщение WM_TIMER

Сообщение WM_TIMER имеет один параметр - идентификатор таймера. Этот параметр передается через младшее слово параметра mp1 и может быть извлечен следующим образом:

idTimer = SHORT1FROMMP (mp1);

Если ваше приложение запускает несколько таймеров, например, идущих с разным периодом времени, обработчик сообщения WM_TIMER должен анализировать содержимое параметра mp1.

Заметим, что функция главного окна вашего приложения может получать сообщения WM_TIMER даже в том случае, если вы не запускали ни одного таймера. Эти сообщения могут попадать в функцию окна из различных орагнов управления, создаваемых вашим приложением. Поэтому в общем случае обработчик сообщения WM_TIMER должен всегда проверять параметр mp1, передавая все необработанные сообщения от таймеров, которые ваше приложение не создавало, функции WinDefWindowProc . В противном случае некоторые органы управления, созданные в вашем приложении, будут работать неправильно.

7.3. Приложение BEEP

Для иллюстрации методов работы с таймером мы подготовили несложное приложение BEEP, исходный текст которого приведен в листинге 7.1.

Листинг 7.1. Файл beep\beep.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include "beep.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Timer Beep";

// =================================================
// Главная функция приложения main 
// =================================================

int main ()
{
  HMQ   hmq;
  QMSG   qmsg;
  BOOL  fRc;

  // Флаги для создания окна Frame Window 
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR       | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "TIMERBEEP";

  hab = WinInitialize (0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow  (HWND_DESKTOP,
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  switch (msg)
  {
    case WM_CREATE :
    {
      // Запускаем таймер с интервалом 1000 мс
      WinStartTimer (hab, hWnd, ID_APP_TIMER1, 1000);

      // Запускаем таймер с интервалом 3000 мс
      WinStartTimer (hab, hWnd, ID_APP_TIMER2, 3000);
      return FALSE;
    }

    case WM_DESTROY :
    {
      // Останавливаем таймеры
      WinStopTimer (hab, hWnd, ID_APP_TIMER1);
      WinStopTimer (hab, hWnd, ID_APP_TIMER2);
      return 0;
    }

    case WM_TIMER :
    {
      // Для каждого таймера выдаем звуковой
      // сигнал своей частоты и длительности
      switch(SHORT1FROMMP (mp1))
      {
        case ID_APP_TIMER1:
        {
          DosBeep(1000, 100);
          break;
        }

        case ID_APP_TIMER2:
        {
          DosBeep(400, 300);
          break;
        }

        default:
          return(WinDefWindowProc (hWnd, msg, 
            mp1, mp2));
      }
      return 0;
    }

    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

Функция WndProc

Функция WndProc выполняет всю работу с таймером. Рассмотрим по отдельности обработчики различных сообщений.

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE запускает два таймера с идентификаторами, соответственно, ID_APP_TIMER1 и ID_APP_TIMER2:

WinStartTimer (hab, hWnd, ID_APP_TIMER1, 1000);
WinStartTimer (hab, hWnd, ID_APP_TIMER2, 3000);

Первый из них будет посылать функции окна hWnd сообщение WM_TIMER с интервалом 1000 мс, т. е. каждую секунду. Второй будет посылать сообщение в три раза реже, так как для него установлена задержка 3000 мс.

Сообщение WM_DESTROY

Перед уничтожением окна обработчик сообщения WM_DESTROY останавливает оба таймера, запущенных ранее. Для этого используется функция WinStopTimer :

WinStopTimer (hab, hWnd, ID_APP_TIMER1);
WinStopTimer (hab, hWnd, ID_APP_TIMER2);

Сообщение WM_TIMER

Обработчик сообщения WM_TIMER проверяет идентификатор таймера.

Для первого таймера вызывается функция DosBeep, издающая звуковой сигнал с частотой 1000 Гц и продолжительностью 100 мс. Для второго таймера эта же функция издает звуковой сигнал с частотой 400 Гц и длительностью 300 мс. Так как первый таймер посылает сообщения в три раза чаще второго, на каждый сигнал низкого тона вы услышите три сигнала высокого тона.

Если в функцию окна пришло сообщение WM_TIMER с идентификатором, отличным от определенных в приложении, оно передается без изменений функции WinDefWindowProc .

Функция DosBeep входит в программный интерфейс ядра операционной системы IBM OS/2 и не имеет никакого отношения к операционной системе MS-DOS или IBM DOS. Префикс имени Dos говорит лишь о принадлжедности этой функции к указанному программному интерфейсу.

В системе Presentation Manager есть еще одна функция, которую можно использовать для выдачи звуковых сигналов. Это фукнция WinAlarm:

BOOL WinAlarm(
  HWND hwndDeskTop,// идентификатор окна Desktop Window 
  ULONG flStyle);  // вид звукового сигнала

Параметр flStyle определяет вид звукового сигнала. Для подачи предупреждающего звукового сигнала этот параметр должен иметь значение WA_WARNING , для сигнализации ошибки - WA_ERROR , а для сопровождения информационного сообщения - WA_NOTE .

Файл beep.h

Файл beep.h (листинг 7.2) содержит определения константы ID_APP_FRAMEWND, а также идентификаторов двух таймеров ID_APP_TIMER1 и ID_APP_TIMER2.

Листинг 7.2. Файл beep\beep.h

#define ID_APP_FRAMEWND 1
#define ID_APP_TIMER1   1
#define ID_APP_TIMER2   2

Файл beep.rc

Файл описания ресуросв приложения beep.rc приведен в листинге 7.3.

Листинг 7.3. Файл beep\beep.rc

#include <os2.h>
#include "beep.h"
ICON ID_APP_FRAMEWND BEEP.ICO

Файл beep.def

Файл определения модуля приложения beep.def представлен в листинге 7.4.

Листинг 7.4. Файл beep\beep.def

NAME        BEEP   WINDOWAPI
DESCRIPTION 'Beep Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc

7.4. Другие функции для работы с таймером

В этом разделе мы расскажем о других функциях, предназначенных для работы с таймером, для определения текущего времени и даты.

Функция WinGetCurrentTime

Функция WinGetCurrentTime возвращает текущее время, измеренное в миллисекундах от момента загрузки операционной системы IBM OS/2. Учтите, что если компьютер работает много недель, это значение может переполняться и сбрасываться в ноль.

Прототип функции WinGetCurrentTime приведен ниже:

ULONG WinGetCurrentTime(HAB hab);

Этой функцией удобно пользоваться, например, для измерения продолжительности какого-либо процесса. Для этого нужно вызвать функцию до начала процесса и после, а затем сравнить возвращенные значения. Что же касается определения абсолютного текущего времени и даты, для этого удобнее использовать функцию DosGetDateTime , которую мы опишем ниже.

Функция WinQueryMsgTime

Эта функция возвращает время в миллисекундах для последнего сообщения, извлеченного из очереди сообщений приложения. Возвращаемое значение времени отсчитывается от момента загрузки операционной системы.

Прототип функции WinQueryMsgTime представлен ниже:

ULONG WinQueryMsgTime(HAB hab);

Вы можете использовать эту функцию для измерения времени, прошедшего между обработкой двух сообщений.

Функция DosGetDateTime

Для определения текущего времени и даты удобнее всего использовать функцию DosGetDateTime :

APIRET DosGetDateTime(PDATETIME PDateTime);

Эта функция записывает в стркутуру типа DATETIME информацию о текущем времени и дате. Формат этой структуры приведен ниже:

typedef struct _DATETIME 
{
  UCHAR  hours;       // часы
  UCHAR  minutes;     // минуты
  UCHAR  seconds;     // секунды
  UCHAR  hundredths;  // сотые доли секунды
  UCHAR  day;         // число
  UCHAR  month;       // месяц
  USHORT year;        // год
  SHORT  timezone;    // временной пояс
  UCHAR  weekday;     // день недели
} DATETIME;

7.5. Приложение CLOCK

В приложении CLOCK, которое представляет собой цифровые часы, мы показываем текущее время. Показания часов обновляются раз в секунду, для чего используется сообщение WM_TIMER .

Окно нашего приложения не имеет рамки и сразу после запуска приложения появляется в левом нижнем углу экрана. С помощью левой клавиши мыши вы можете перенести это окно в любое удобное для вас место экрана (рис. 7.1).

Рис. 7.1. Окно приложения CLOCK на поверхности рабочего стола

Исходный текст приложения CLOCK представлен в листинге 7.5.

Листинг 7.5. Файл clock\clock.c

// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include <string.h>
#include <stdio.h>
#include "clock.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Clock Application";

// Координаты курсора мыши в момент нажатия
// левой клавиши мыши
SHORT cxPoint;
SHORT cyPoint;

// Координаты курсора мыши в момент отпускания
// левой клавиши мыши
SHORT cxNewPoint;
SHORT cyNewPoint;

// Признак начала процесса перемещения окна
BOOL  fDrag = FALSE;

// Идентификаторы указателей мыши
HPOINTER hptr, hptr1;

// Размеры символов выбранного шрифта
SHORT cxChar, cyChar, cyDesc;

// =================================================
// Главная функция приложения main 
// =================================================

int main ()
{
  HMQ   hmq;
  QMSG   qmsg;
  BOOL  fRc;
  FONTMETRICS fm;
  HPS hps;

  // Флаги для создания окна Frame Window .
  // Окно не имеет орагнов управления и
  // рамки для изменения размеров
  ULONG flFrameFlags = FCF_TASKLIST | FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "TIMERBEEP";

  hab = WinInitialize (0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue (hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate (hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass (hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow  (HWND_DESKTOP,
    WS_VISIBLE ,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox (HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue (hmq);
    WinTerminate (hab);

    return(-1);
  }

  // Получаем пространство отображения
  hps = WinGetPS (hWndFrame);

  // Выбираем в пространство отображения шрифт
  // с фиксированной шириной символов
  SetCourierFont(hps);

  // Определяем метрики шрифта
  GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

  cxChar = fm.lAveCharWidth;
  cyChar = fm.lMaxBaselineExt;
  cyDesc = fm.lMaxDescender;

  // Устанавливаем шрифт, выбранный в пространство
  // отображения по умолчанию
  ResetFont(hps);

  // Возвращаем пространство отображения
  WinReleasePS (hps);

  // Устанавливаем новые размеры и расположение
  // главного окна приложения
  WinSetWindowPos (hWndFrame, HWND_TOP ,
    0, 0, 10 * cxChar, cyChar + cyDesc,
    SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

  // Запускаем цикл обработки сообщений
  while(WinGetMsg (hab, &qmsg, 0, 0, 0))
    WinDispatchMsg (hab, &qmsg);

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue (hmq);
  WinTerminate (hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  HPS   hps;
  RECTL rec;
  SWP    swp;
  CHAR  pszBuf[256];
  DATETIME dt;

  switch (msg)
  {
    case WM_CREATE :
    {
      // Запускаем таймер с интервалом 1000 мс
      WinStartTimer (hab, hWnd, ID_APP_TIMER, 1000);

      // Загружаем идентификаторы курсоров мыши
      hptr  = WinLoadPointer(HWND_DESKTOP,
        NULLHANDLE, ID_APP_POINTER);

      hptr1 = WinLoadPointer(HWND_DESKTOP,
        NULLHANDLE, ID_APP_POINTER1);

      return FALSE;
    }

    case WM_DESTROY :
    {
      // Останавливаем таймер
      WinStopTimer (hab, hWnd, ID_APP_TIMER);

      // Удаляем курсоры мыши
      WinDestroyPointer (hptr);
      WinDestroyPointer (hptr1);
      return 0;
    }

    // Когда приходит сообщение от таймера,
    // перерисовываем главное окно приложения
    case WM_TIMER :
    {
      WinInvalidateRect (hWnd, NULL, TRUE);
      return 0;
    }

    case WM_PAINT :
    {
      // Получаем пространство отображения
      hps = WinBeginPaint (hWnd, NULLHANDLE, &rec);

      // Определяем размеры главного окна
      WinQueryWindow Rect(hWnd, &rec);

      // Определяем дату и время
      DosGetDateTime(&dt);

      sprintf (pszBuf, "%02d:%02d:%02d",
        dt.hours, dt.minutes, dt.seconds);

      // Устанавливаем шрифт для отображения
      SetCourierFont(hps);

      // Выводим текст в центре окна
      WinDrawText (hps, -1, pszBuf, &rec, 0L, 0L,
        DT_WORDBREAK | DT_CENTER | DT_VCENTER |
        DT_TEXTATTRS | DT_ERASERECT);

      // Восстанавливаем шрифт
      ResetFont(hps);

      // Освобождаем пространство отображения
      WinEndPaint (hps);
      return 0;
    }

    case WM_ERASEBACKGROUND :
      return(MRFROMLONG(1L));

    // Когда пользователь нажимает левую клавишу
    // мыши, запоминаем координаты курсора и
    // выдвигаем окно приложения на передний план
    case WM_BUTTON1DOWN :
    {
      cxPoint = MOUSEMSG(&msg) -> x;
      cyPoint = MOUSEMSG(&msg) -> y;

      // Изменяем расположение окна по оси Z
      WinSetWindowPos (hWndFrame, HWND_TOP ,
        0, 0, 0, 0, SWP _ZORDER );

      // Устанавливаем признак перемещения
      // главного окна приложения
      fDrag = TRUE;

      // Захватываем мышь
      WinSetCapture (HWND_DESKTOP, hWnd);
      return 0;
    }

    // При отпускании левой клавиши мыши сбрасываем
    // признак перемещения окна
    case WM_BUTTON1UP :
    {
      // Сбрасываем признак перемещения
      // главного окна приложения
      fDrag = FALSE;

      // Освобождаем мышь
      WinSetCapture (HWND_DESKTOP, NULLHANDLE);
      return 0;
    }

    // Это сообщение приходит при перемещении курсора
    // мыши
    case WM_MOUSEMOVE :
    {
      // Если включен признак перемещения, определяем
      // новые координаты курсора и передвигаем окно
      if(fDrag)
      {
        // Выбираем указатель в виде закрытой руки
        WinSetPointer (HWND_DESKTOP, hptr1);

        cxNewPoint = MOUSEMSG(&msg) -> x;
        cyNewPoint = MOUSEMSG(&msg) -> y;

        // Определяем текущие координаты окна
        WinQueryWindow Pos(hWndFrame, &swp);

        // Передвигаем окно
        WinSetWindowPos (hWndFrame, HWND_TOP ,
          swp.x + (cxNewPoint - cxPoint),
          swp.y + (cyNewPoint - cyPoint),
          0, 0, SWP _MOVE );
      }

      else
        // Выбираем указатель в виде открытой руки
        WinSetPointer (HWND_DESKTOP, hptr);

      return (MRESULT)TRUE;
    }

    // Если пользователь сделал двойной щелчок левой
    // клавише мыши, завершаем работу приложения
    case WM_BUTTON1DBLCLK :
    {
      WinPostMsg (hWnd, WM_QUIT , 0L, 0L);
      return 0;
    }

    default:
      return(WinDefWindowProc (hWnd, msg, mp1, mp2));
  }
}

// =================================================
// Выбор шрифта с фиксированной шириной символов
// =================================================

void SetCourierFont(HPS hps)
{
  FATTRS fat;

  // Заполняем структуру описанием нужного
  // нам шрифта
  fat.usRecordLength = sizeof(FATTRS);
  strcpy(fat.szFacename ,"Courier");
  fat.fsSelection = 0;
  fat.lMatch = 0L;
  fat.idRegistry = 0;
  fat.usCodePage = 850;
  fat.lMaxBaselineExt = 12L;
  fat.lAveCharWidth = 12L;
  fat.fsType = 0;
  fat.fsFontUse = FATTR_FONTUSE_NOMIX;

  // Создаем логический шрифт, имеющий идентификатор 1L
  GpiCreateLogFont(hps, NULL, 1L, &fat);

  // Выбираем созданный шрифт в пространство отображения
  GpiSetCharSet (hps, 1L);
}

// =================================================
// Установка шрифта, выбранного в пространство
// отображения по умолчанию
// =================================================
void ResetFont(HPS hps)
{
  GpiSetCharSet (hps, LCID_DEFAULT);
  GpiDeleteSetId(hps, 1L);
}

Глобальные переменные

Помимо идентификаторов hab, hWndFrame и hWndClient, обычных для всех наших приложений, в области глобальных переменных хранится заголовок окна приложения (который не отображается, так как окно не имеет заголовка). Переменные cxPoint, cyPoint, cxNewPoint, cyNewPoint, hptr, hptr1 и fDrag используются для перемещения окна мышью таким же образом, что и в рассмотренном нами ранее приложении MOUSEMOVE.

Переменные cxChar, cyChar и cyDesc хранят метрики шрифта с фиксированной шириной символов, который используется для отображения времени.

Функция main

В функции main приложения CLOCK для создания главного окна приложения используется самый маленький набор флагов:

ULONG flFrameFlags = FCF_TASKLIST | FCF_ICON;

Главное окно приложения не имеет рамки, поэтому пользователь не может изменять его размеры. У окна нет заголовка, системного меню и меню минимизации/максимизации, поэтому для перемещения окна используется та же методика, что и в описанном ранее приложении MOUSEMOVE. Эта методика основана на обработке сообщения WM_MOUSEMOVE .

Сразу после создания главного окна наше приложение устанавливает его размер. Для этого функция main получает пространство отображения, выбирает в него шрифт с фиксированной шириной символов и определяет метрики этого шрифта. Ширина главного окна приложения выбирается в 10 раз больше, чем ширина символов, а высота - равной высоте символов с учетом размера выступающей части:

WinSetWindowPos (hWndFrame, HWND_TOP ,
  0, 0, 10 * cxChar, cyChar + cyDesc,
  SWP _SIZE  | SWP_MOVE  | SWP_ZORDER );

Кроме изменения размеров онка, функция WinSetWindowPos изменяет расположение окна по оси Z, выдвигая его на передний план.

Далее функция main обычным образом запускает цикл обработки сообщений.

Функция окна WndProc

Для экономии места мы опишем только обработчики тех сообщений, которые имеют непосредственное отношение к таймеру. Способы перемещения окна мышью и процедура выбора шрифта в пространство отображения были описаны раньше.

Сообщение WM_CREATE

Обработчик сообщения WM_CREATE запускает таймер с идентификатором ID_APP_TIMER:

WinStartTimer (hab, hWnd, ID_APP_TIMER, 1000);

Этот таймер посылает функции окна раз в секунду сообщение WM_TIMER , которое будет использоваться для обновления показаний часов.

Кроме этого, при обработке сообщения WM_CREATE из ресурсов приложения загружаются курсоры мыши, которые используются при перемещении главного окна приложения.

Сообщение WM_DESTROY

При уничтожении главного окна приложения обработчик сообщения WM_DESTROY останавливает таймер:

WinStopTimer (hab, hWnd, ID_APP_TIMER);

Дополнительно он удаляет курсоры мыши, загруженные при обработке сообщения WM_CREATE .

Сообщение WM_TIMER

Обработчик сообщения WM_TIMER получает управление примерно раз в секунду (напомним, что так как это сообщение проходит через очередь приложения, оно может поступать в функцию окна нерегулярно).

В ответ на это сообщение функция окна перерисовывает окно приложения, для чего вызывает функцию WinInvalidateRect :

WinInvalidateRect (hWnd, NULL, TRUE);

В результате в функцию окна примерно раз в секунду будет посупать сообщение WM_PAINT .

Сообщение WM_PAINT

Задачей обработчика сообщения WM_PAINT является определение текущего времени (на момент прихода сообщения WM_PAINT) и его отображение в окне приложения.

Получив пространство отображения, обработчик определяет размеры главного окна приложения, а также дату и время. Последнее делается при помощи функции DosGetDateTime:

DosGetDateTime(&dt);

Далее функция окна формирует текстовую строку pszBuf, записывая в нее время (в формате Часы:Минуты:Секунды), устанавливает шрифт с фиксированной шириной символов и отображает время при помощи функции WinDrawText :

WinDrawText (hps, -1, pszBuf, &rec, 0L, 0L,
  DT_WORDBREAK | DT_CENTER | DT_VCENTER |
  DT_TEXTATTRS | DT_ERASERECT);

Параметры для этой функции подобраны таким образом, чтобы строка времени отображалась в центре окна, причем перед отображением содержимое окна стирается.

Перед завершением обработки сообщения WM_PAINT функция окна восстанавливает шрифт и освобождает полученное ранее пространство отображения.

Сообщение WM_BUTTON1DBLCLK

Так как главное окно приложения не имеет ни системного, ни обычного меню, для завершения работы приложения мы используем сообщение WM_BUTTON1DBLCLK . Это сообщение передается функции окна когда пользователь делает двойной щелчок левой клавишей мыши в окне приложения.

Файл clock.h

Файл clock.h (листинг 7.6) содержит определения константы ID_APP_FRAMEWND, идентификатора таймера ID_APP_TIMER, идентификаторов двух курсоров ID_APP_POINTER и ID_APP_POINTER1, которые используются для перемещения окна приложения, а также прототипы функций SetCourierFont и ResetFont.

Листинг 7.6. Файл clock\clock.h

#define ID_APP_FRAMEWND 1
#define ID_APP_TIMER    1
#define ID_APP_POINTER  2
#define ID_APP_POINTER1 3

void SetCourierFont(HPS hps);
void ResetFont(HPS hps);

Файл описания ресурсов clock.rc

Файл описания ресурсов clock.rc (листинг 7.7) содержит описание пиктограммы приложения и двух курсоров мыши с идентификаторами ID_APP_POINTER и ID_APP_POINTER1.

Листинг 7.7. Файл clock\clock.rc

#include <os2.h>
#include "clock.h"

ICON    ID_APP_FRAMEWND  CLOCK.ICO
POINTER ID_APP_POINTER   MOUSEMOV.PTR
POINTER ID_APP_POINTER1  MOUSE1.PTR

Файл определения модуля clock.def

Файл определения модуля приложения clock.def приведен в листинге 7.8.

Листинг 7.8. Файл clock\clock.def

NAME        CLOCK   WINDOWAPI
DESCRIPTION 'Clock Application (C) Frolov A., 1996'
HEAPSIZE    4096
STACKSIZE   32768
EXPORTS     WndProc