Программирование для IBM OS/2© Александр Фролов, Григорий ФроловТом 25, М.: Диалог-МИФИ, 1993, 286 стр. 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 |