Приложения Presentation Manager могут следить за временем или выполнять какие-либо периодические действия с использованием таймера. Однако в отличие от программы MS-DOS , где для использования таймера было необходимо перехватывать соответствующее аппаратное прерывание, работа с таймером в Presentation Manager основана на передаче сообщений.
В своем приложении вы можете запустить несколько таймеров, каждый из которых будет периодически посылать в функцию окна сообщение с кодом WM_TIMER . В первом параметре этого сообщения передается идентификатор созданного таймера, поэтому нетрудно определить, от какого таймера пришло сообщение.
Заметим, что таймеры, запущенные с использованием функций Presentation Manager, не подходят для прецизионного измерения времени. Дело в том, что сообщения от таймера попадает в очередь сообщений приложения и обрабатываются наряду с другими сообщениями, поэтому они могут оказаться задержанными.
Если вы собираетесь создавать на базе IBM OS/2 систему управления, работающую в реальном режиме времени, не исключено, что вам придется создавать собственный драйвер или использовать другие приемы работы с физическим таймером компьютера. Однако для диалоговых программ задержки в поступлении сообщений от таймера не играют большой роли.
Для запуска таймера вы должны использовать функцию 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 .
Сообщение WM_TIMER имеет один параметр - идентификатор таймера. Этот параметр передается через младшее слово параметра mp1 и может быть извлечен следующим образом:
idTimer = SHORT1FROMMP (mp1);
Если ваше приложение запускает несколько таймеров, например, идущих с разным периодом времени, обработчик сообщения WM_TIMER должен анализировать содержимое параметра mp1.
Заметим, что функция главного окна вашего приложения может получать сообщения WM_TIMER даже в том случае, если вы не запускали ни одного таймера. Эти сообщения могут попадать в функцию окна из различных орагнов управления, создаваемых вашим приложением. Поэтому в общем случае обработчик сообщения WM_TIMER должен всегда проверять параметр mp1, передавая все необработанные сообщения от таймеров, которые ваше приложение не создавало, функции WinDefWindowProc . В противном случае некоторые органы управления, созданные в вашем приложении, будут работать неправильно.
Для иллюстрации методов работы с таймером мы подготовили несложное приложение 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 выполняет всю работу с таймером. Рассмотрим по отдельности обработчики различных сообщений.
Обработчик сообщения 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 останавливает оба таймера, запущенных ранее. Для этого используется функция WinStopTimer :
WinStopTimer (hab, hWnd, ID_APP_TIMER1); WinStopTimer (hab, hWnd, ID_APP_TIMER2);
Обработчик сообщения 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 (листинг 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 приведен в листинге 7.3.
Листинг 7.3. Файл beep\beep.rc
#include <os2.h> #include "beep.h" ICON ID_APP_FRAMEWND BEEP.ICO
Файл определения модуля приложения 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
В этом разделе мы расскажем о других функциях, предназначенных для работы с таймером, для определения текущего времени и даты.
Функция WinGetCurrentTime возвращает текущее время, измеренное в миллисекундах от момента загрузки операционной системы IBM OS/2. Учтите, что если компьютер работает много недель, это значение может переполняться и сбрасываться в ноль.
Прототип функции WinGetCurrentTime приведен ниже:
ULONG WinGetCurrentTime(HAB hab);
Этой функцией удобно пользоваться, например, для измерения продолжительности какого-либо процесса. Для этого нужно вызвать функцию до начала процесса и после, а затем сравнить возвращенные значения. Что же касается определения абсолютного текущего времени и даты, для этого удобнее использовать функцию DosGetDateTime , которую мы опишем ниже.
Эта функция возвращает время в миллисекундах для последнего сообщения, извлеченного из очереди сообщений приложения. Возвращаемое значение времени отсчитывается от момента загрузки операционной системы.
Прототип функции WinQueryMsgTime представлен ниже:
ULONG WinQueryMsgTime(HAB hab);
Вы можете использовать эту функцию для измерения времени, прошедшего между обработкой двух сообщений.
Для определения текущего времени и даты удобнее всего использовать функцию 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;
В приложении 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 приложения 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 обычным образом запускает цикл обработки сообщений.
Для экономии места мы опишем только обработчики тех сообщений, которые имеют непосредственное отношение к таймеру. Способы перемещения окна мышью и процедура выбора шрифта в пространство отображения были описаны раньше.
Обработчик сообщения WM_CREATE запускает таймер с идентификатором ID_APP_TIMER:
WinStartTimer (hab, hWnd, ID_APP_TIMER, 1000);
Этот таймер посылает функции окна раз в секунду сообщение WM_TIMER , которое будет использоваться для обновления показаний часов.
Кроме этого, при обработке сообщения WM_CREATE из ресурсов приложения загружаются курсоры мыши, которые используются при перемещении главного окна приложения.
При уничтожении главного окна приложения обработчик сообщения WM_DESTROY останавливает таймер:
WinStopTimer (hab, hWnd, ID_APP_TIMER);
Дополнительно он удаляет курсоры мыши, загруженные при обработке сообщения WM_CREATE .
Обработчик сообщения WM_TIMER получает управление примерно раз в секунду (напомним, что так как это сообщение проходит через очередь приложения, оно может поступать в функцию окна нерегулярно).
В ответ на это сообщение функция окна перерисовывает окно приложения, для чего вызывает функцию WinInvalidateRect :
WinInvalidateRect (hWnd, NULL, TRUE);
В результате в функцию окна примерно раз в секунду будет посупать сообщение 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 . Это сообщение передается функции окна когда пользователь делает двойной щелчок левой клавишей мыши в окне приложения.
Файл 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 (листинг 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 приведен в листинге 7.8.
Листинг 7.8. Файл clock\clock.def
NAME CLOCK WINDOWAPI DESCRIPTION 'Clock Application (C) Frolov A., 1996' HEAPSIZE 4096 STACKSIZE 32768 EXPORTS WndProc