Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 11, М.: Диалог-МИФИ, 1993, 269 стр. 7.6. Приложение TMCLOCKПриложение TMCLOCK представляет собой простейшие часы с цифровой индикацией системным шрифтом с фиксированной шириной символов. Это приложение демонстрирует использование функции таймера, а также способ обработки сообщения WM_NCHITTEST, позволяющий изменять размеры и расположение окна, не имеющего заголовка, системного меню и кнопок изменения размера. Главный файл приложения, содержащий функцию WinMain, представлен в листинге 7.4. Листинг 7.4. Файл tmclock\tmclock.cpp // ---------------------------------------- // Простейшие часы // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "TMCLOCKAppClass"; char const szWindowTitle[] = "TMCLOCK Application"; TEXTMETRIC tm; int cxChar, cyChar; RECT rc; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; // Получаем координаты окна Desktop. // Это окно занимает всю поверхность экрана, // и на нем расположены все остальные окна GetWindowRect(GetDesktopWindow(), &rc); // Создаем временное окно с толстой // рамкой для изменения размера, но без // заголовка и системного меню. // При создании окна указываем произвольные // размеры окна и произвольное расположение hwnd = CreateWindow( szClassName, szWindowTitle, WS_POPUPWINDOW | WS_THICKFRAME, 100, 100, 100, 100, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; // Передвигаем окно в правый нижний // угол экрана MoveWindow(hwnd, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, TRUE); // Отображаем окно в новом месте ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); } По сравнению с предыдущими приложениями функция WinMain приложения TMCLOCK имеет некоторые особенности. Задачей функции является создание временного окна без заголовка и расположение его в правом нижнем углу экрана. Размеры окна и его расположение должны определяться в единицах, зависящих от размера использованного нами системного шрифта. Вначале мы получаем и запоминаем размеры так называемого окна Desktop, на поверхности которого расположены все окна (и пиктограммы) Windows. Функция GetDesktopWindow возвращает идентификатор окна Desktop, который мы используем для определения размера, передавая его функции GetWindowRect. Далее мы создаем временное окно с толстой рамкой для изменения размера. Расположения и размеры окна на данном этапе нас не интересуют, так как мы скоро их изменим: hwnd = CreateWindow( szClassName, szWindowTitle, WS_POPUPWINDOW | WS_THICKFRAME, 100, 100,100, 100, 0, 0, hInstance, NULL); Во время своего создания главное окно приложения получает сообщение WM_CREATE, обработчик которого определяет метрики шрифта, а также записывает размеры шрифта в переменные cxChar и cyChar. После этого главное окно приложения уменьшается в размерах и перемещается в правый нижний угол окна Desktop: MoveWindow(hwnd, rc.right - cxChar * 15, rc.bottom - cyChar * 3, cxChar * 10, cyChar * 2, TRUE); Функция MoveWindow определяет новое расположение и размеры окна: BOOL WINAPI MoveWindow(HWND hwnd, int nLeft, int nTop, int nWidth, int nHeight, BOOL fRepaint); Первый параметр функции (hwnd) указывает идентификатор перемещаемого окна. Второй параметр (nLeft) указывает координату левой границы окна, третий (nTop) - координаты нижней границы окна. Четвертый (nWidth) и пятый (nHeight) параметры определяют соответственно ширину и высоту окна. Последний, шестой параметр (fRepaint) - флаг, определяющий, надо ли перерисовывать окно после его перемещения. Если значение этого параметра равно TRUE, функция окна после перемещения окна получит сообщение WM_PAINT. Если указать это значение как FALSE, никакая часть окна не будет перерисована. После перемещения окна оно отображается (уже на новом месте). Далее запускается обычный цикл обработки сообщений. Исходные тексты функции главного окна и функции таймера приведены в листинге 7.5. Листинг 7.5. Файл tmclock\wndproc.cpp #define STRICT #include <windows.h> #include <time.h> // Идентификатор таймера, который используется // для измерения времени #define CLOCK_TIMER 1 // Прототип функции таймера void CALLBACK _export TimerProc(HWND, UINT, UINT, DWORD); // Переменная для хранения идентификатора таймера, // который используется для выдачи звукового сигнала int nBeepTimerID; // Внешние переменные extern TEXTMETRIC tm; extern int cxChar, cyChar; // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch (msg) { case WM_CREATE: { // Создаем таймер, посылающий сообщения // функции окна примерно раз в секунду SetTimer(hwnd, CLOCK_TIMER, 1000, NULL); // Создаем таймер, который периодически // раз в секунду посылает сообщения в // функцию таймера TimerProc nBeepTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)TimerProc); hdc = GetDC(hwnd); // Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm); // Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth; // Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading; ReleaseDC(hwnd, hdc); return 0; } // Для обеспечения возможности перемещения // окна, не имеющего заголовка, встраиваем // свой обработчик сообщения WM_NCHITTEST case WM_NCHITTEST: { long lRetVal; // Вызываем функцию DefWindowProc и проверяем // возвращаемое ей значение lRetVal = DefWindowProc(hwnd, msg, wParam, lParam); // Если курсор мыши находится на одном из // элементов толстой рамки, предназначенной // для изменения размера окна, возвращаем // неизмененное значение, полученное от // функции DefWindowProc if(lRetVal == HTLEFT || lRetVal == HTRIGHT || lRetVal == HTTOP || lRetVal == HTBOTTOM || lRetVal == HTBOTTOMRIGHT || lRetVal == HTTOPRIGHT || lRetVal == HTTOPLEFT || lRetVal == HTBOTTOMLEFT) { return lRetVal; } // В противном случае возвращаем значение // HTCAPTION, которое соответствует // заголовку окна. else { return HTCAPTION; } } // Каждую секунду перерисовываем // внутреннюю область окна case WM_TIMER: { InvalidateRect(hwnd, NULL, FALSE); return 0; } case WM_DESTROY: { // Перед уничтожением окна уничтожаем // созданные ранее таймеры KillTimer(hwnd, CLOCK_TIMER); KillTimer(hwnd, nBeepTimerID); PostQuitMessage(0); return 0; } case WM_PAINT: { BYTE szBuf[80]; int nBufSize; time_t t; struct tm *ltime; RECT rc; hdc = BeginPaint(hwnd, &ps); // Определяем время и его отдельные компоненты time(&t); ltime = localtime(&t); // Подготавливаем буфер, заполняя его // строкой с текущим временем nBufSize = wsprintf(szBuf, "%02d:%02d:%02d", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); // Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); // Получаем координаты и размер окна GetClientRect(hwnd, &rc); // Выводим время в центре окна DrawText(hdc, (LPSTR)szBuf, nBufSize, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE); EndPaint(hwnd, &ps); } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ===================================== // Функция TimerProc // ===================================== #pragma argsused void CALLBACK _export TimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime) { // Просто выдаем звуковой сигнал MessageBeep(-1); return; } Обработчик сообщения WM_CREATE функции главного окна создает два таймера. Первый таймер посылает сообщения в функцию главного окна (раз в секунду): SetTimer(hwnd, CLOCK_TIMER, 1000, NULL); Второй таймер посылает сообщения в функцию TimerProc (также один раз в секунду): nBeepTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)TimerProc); Далее обработчик определяет и сохраняет метрики системного шрифта с фиксированной шириной букв, которые будут использованы функцией WinMain при определении размеров и расположения главного окна. Обработчик сообщения WM_NCHITTEST вызывает функцию DefWindowProc и проверяет возвращенное ей значение. Напомним, что это сообщение посылается для определения элемента окна, над которым расположен курсор мыши. Если возвращенное функцией DefWindowProc значение соответствует толстой рамке окна, наш обработчик передает это значение Windows без изменения, позволяя операционной системе выполнить изменение размеров. Если же вы нажали клавишу мыши во внутренней области окна, наш обработчик возвращает значение HTCAPTION, соответствующее заголовку окна. Благодаря этому Windows сможет перемещать окно, не имеющее заголовка. Обработчик сообщения WM_TIMER получает управление каждую секунду. Его задача сводится просто к тому, что он объявляет все окно как требующее перерисовки. В этом случае каждую секунду функция окна будет получать сообщение WM_PAINT. Задача обработчика сообщения WM_PAINT сводится к отображению по центру окна времени в формате ЧЧ:ММ:СС, где ЧЧ - часы, ММ - минуты, СС - секунды. Для определения текущего времени обработчик сообщения вызывает функцию time, которая записывает информацию о времени на момент своего вызова в структуру типа time_t, определенную в файле time.h следующим образом: typedef long time_t; Для раскодирования информации о времени и представления ее в удобном для обработки виде приложение вызывает функцию localtime. Эта функция возвращает указатель на статическую структуру типа tm, содержащую отдельные компоненты времени. Тип tm описан в файле time.h: struct tm { int tm_sec; // секунды int tm_min; // минуты int tm_hour; // часы (0...23) int tm_mday; // день месяца (1...31) int tm_mon; // месяц (0...11) int tm_year; // год (календарный год минус 1900) int tm_wday; // номер дня недели // (0...6, 0 - воскресенье) int tm_yday; // день года (0...365) int tm_isdst; // флаг летнего времени (0 - летнее время // не используется) }; Перед выводом обработчик сообщения определяет координаты и размер окна: GetClientRect(hwnd, &rc); Полученные координаты используются для вывода текстовой строки, содержащей время: DrawText(hdc, (LPSTR)szBuf, nBufSize, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE); Параметры функции DrawText подобраны таким образом, чтобы текстовая строка выводилась в центре окна. Параметр DT_CENTER используется для центрирования текста по горизонтали, параметр DT_VCENTER - для центрирования текста по вертикали. Если не указать параметр DT_SINGLELINE, означающий, что в окно выводится только одна строка текста, эта строка окажется в верхней части экрана, то есть не будет отцентрована по вертикали. Параметр DT_NOCLIP обеспечивает более быстрый вывод текста, запрещая проверку области ограничения при выводе Когда приложение завершает свою работу, обработчик сообщения WM_DESTROY уничтожает оба созданных по сообщению WM_CREATE таймера: KillTimer(hwnd, CLOCK_TIMER); KillTimer(hwnd, nBeepTimerID); Функция таймера, предназначенная для выдачи звукового сигнала, очень проста. Ее роль ограничивается именно выдачей звукового сигнала: void CALLBACK _export TimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime) { // Просто выдаем звуковой сигнал MessageBeep(-1); return; } Файл определения модуля приложения TMCLOCK приведен в листинге 7.6. Листинг 7.6. Файл tmclock\tmclock.def ; ============================= ; Файл определения модуля ; ============================= NAME TMCLOCK DESCRIPTION 'Приложение TMCLOCK, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |