4. Метрики Windows4.1. Системные метрики 4.2. Приложение SMETRICS 4.3. Определение возможностей устройств ввода/вывода 4.4. Приложение DCAPS 4.6. Определение расположения окна 4.7. Метрики текста В программном интерфейсе Windows имеются функции, позволяющие получить информацию о размерах отдельных компонент Windows, таких, как ширина рамки окна, ширина и высота экрана, размеры шрифта и т. п. Эта информация необходима для определения габаритов и расположения создаваемых вами окон и других изображений. В приложении WSTYLE мы указывали абсолютные значения координат и размеров создаваемых окон. Однако Windows может работать в режимах с различным разрешением видеоадаптера. Если вы подберете все размеры для разрешения 640 х 480 точек, в режиме 1200 х 1024 все ваше изображение окажется в верхнем левом углу экрана и вам придется напрягать зрение для того, чтобы что-нибудь рассмотреть. Было бы логичнее определять размеры и расположение создаваемых окон исходя из общего размера экрана, определяя последние динамически при работе приложения. 4.1. Системные метрикиМетрики системных компонент Windows можно определить при помощи функции GetSystemMetrics, имеющей следующий прототип: int WINAPI GetSystemMetrics(int nIndex); Единственный параметр функции (nIndex) выбирает параметр, значение которого необходимо определить. Значение параметра возвращается функцией GetSystemMetrics. Для определения компоненты Windows в файле windows.h имеются символические константы с префиксом SM_:
4.2. Приложение SMETRICSНаше следующее приложение представляет собой пример простейшей программы для Windows, не создающей главного окна, не обрабатывающей сообщения, но тем не менее выполняющей некоторую полезную работу. Задача приложения SMETRICS заключается в вызове функции GetSystemMetrics для всех возможных параметров, перечисленных в предыдущем разделе. Исходный текст приложения представлен в листинге 4.1. Листинг 4.1. Файл smetrics\smetrics.cpp // ---------------------------------------- // Определение системных метрик Windows // ---------------------------------------- #define STRICT #include <windows.h> #include <stdio.h> // Таблица символических констант для // различных компонент Windows static struct { int Const; char String[40]; } SMTable[] = { { SM_CXBORDER, "SM_CXBORDER"}, { SM_CYBORDER, "SM_CYBORDER"}, { SM_CYCAPTION, "SM_CYCAPTION"}, { SM_CXCURSOR, "SM_CXCURSOR"}, { SM_CYCURSOR, "SM_CYCURSOR"}, { SM_CXDLGFRAME, "SM_CXDLGFRAME"}, { SM_CYDLGFRAME, "SM_CYDLGFRAME"}, { SM_CXDOUBLECLK, "SM_CXDOUBLECLK"}, { SM_CYDOUBLECLK, "SM_CYDOUBLECLK"}, { SM_CXFRAME, "SM_CXFRAME"}, { SM_CYFRAME, "SM_CYFRAME"}, { SM_CXFULLSCREEN, "SM_CXFULLSCREEN"}, { SM_CYFULLSCREEN, "SM_CYFULLSCREEN"}, { SM_CXHSCROLL, "SM_CXHSCROLL"}, { SM_CYHSCROLL, "SM_CYHSCROLL"}, { SM_CXHTHUMB, "SM_CXHTHUMB"}, { SM_CXICON, "SM_CXICON"}, { SM_CYICON, "SM_CYICON"}, { SM_CXICONSPACING, "SM_CXICONSPACING"}, { SM_CYICONSPACING, "SM_CYICONSPACING"}, { SM_CYKANJIWINDOW, "SM_CYKANJIWINDOW"}, { SM_CYMENU, "SM_CYMENU"}, { SM_CXMIN, "SM_CXMIN"}, { SM_CYMIN, "SM_CYMIN"}, { SM_CXMINTRACK, "SM_CXMINTRACK"}, { SM_CYMINTRACK, "SM_CYMINTRACK"}, { SM_CXSCREEN, "SM_CXSCREEN"}, { SM_CYSCREEN, "SM_CYSCREEN"}, { SM_CXSIZE, "SM_CXSIZE"}, { SM_CYSIZE, "SM_CYSIZE"}, { SM_CXVSCROLL, "SM_CXVSCROLL"}, { SM_CYVSCROLL, "SM_CYVSCROLL"}, { SM_CYVTHUMB, "SM_CYVTHUMB"}, { SM_DBCSENABLED, "SM_DBCSENABLED"}, { SM_DEBUG, "SM_DEBUG"}, { SM_MENUDROPALIGNMENT, "SM_MENUDROPALIGNMENT"}, { SM_MOUSEPRESENT, "SM_MOUSEPRESENT"}, { SM_PENWINDOWS, "SM_PENWINDOWS"}, { SM_RESERVED1, "SM_RESERVED1"}, { SM_RESERVED2, "SM_RESERVED2"}, { SM_RESERVED3, "SM_RESERVED3"}, { SM_RESERVED4, "SM_RESERVED4"}, { SM_SWAPBUTTON, "SM_SWAPBUTTON"} }; // =========================================== // Функция WinMain // =========================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { FILE *out; // файл для вывода int i; // рабочий счетчик char buf[80]; // рабочий буфер // Открываем выходной файл для вывода // текста потоком // Если открыть файл не удалось, выводим // сообщение об ошибке if ((out = fopen("sysmet.txt", "wt")) == NULL) { MessageBox(NULL, "Не могу открыть файл sysmet.txt", "Ошибка", MB_OK | MB_ICONSTOP); return 1; } // Выводим заголовок файла fputs("* ================================= *\n", out); fputs("* SYSMETRICS, (C) Frolov A.V., 1994 *\n", out); fputs("* ================================= *\n\n", out); // Перебираем в цикле всю таблицу констант // Для каждой константы определяем соответствующую // метрику и формируем текстовую строку for(i=0; i < sizeof(SMTable)/sizeof(SMTable[0]); i++) { sprintf(buf, "%s\t = %d\n", SMTable[i].String, GetSystemMetrics(SMTable[i].Const)); // Выводим строку в файл fputs(buf, out); } // Закрываем файл fclose(out); MessageBox(NULL, "Системные метрики Windows записаны " "в файл sysmet.txt", "SYSMETRIC", MB_OK); return 0; } В приложении SMETRICS определен массив структур SMTable, в котором для каждой константы хранится ее символическое имя в виде текстовой строки. Алгоритм работы понятен без дополнительных объяснений. Заметим только, что мы впервые в приложении Windows использовали функции для работы с файлами. Возможно, мы вас немного порадуем, сообщив, что для работы с файлами вы по- прежнему можете использовать хорошо знакомые вам из MS-DOS функции потокового ввода/вывода. Действительно, функции потокового ввода/вывода будут работать в приложениях Windows. Однако лучше использовать специальные функции файлового ввода/вывода, которые мы рассмотрим позже, в одном из следующих томов "Библиотеки системного программиста". Вы также можете пользоваться известной вам функцией sprintf (но не printf!). Эту функцию мы использовали для формирования текстовой строки. Исходный текст файла определения модуля представлен в листинге 4.2. Листинг 4.2. Файл smetrics\smetrics.def ; ============================= ; Файл определения модуля ; ============================= NAME SMETRICS DESCRIPTION 'Приложение SMETRICS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple В листинге 4.3 приведен образец выходного файла, полученного при работе приложения при разрешении 640 х 480 точек. Листинг 4.3. Образец файла sysmet.txt * ================================= * * SYSMETRICS, (C) Frolov A.V., 1994 * * ================================= * SM_CXBORDER = 1 SM_CYBORDER = 1 SM_CYCAPTION = 20 SM_CXCURSOR = 32 SM_CYCURSOR = 32 SM_CXDLGFRAME = 4 SM_CYDLGFRAME = 4 SM_CXDOUBLECLK = 4 SM_CYDOUBLECLK = 4 SM_CXFRAME = 3 SM_CYFRAME = 3 SM_CXFULLSCREEN = 640 SM_CYFULLSCREEN = 460 SM_CXHSCROLL = 17 SM_CYHSCROLL = 17 SM_CXHTHUMB = 17 SM_CXICON = 32 SM_CYICON = 32 SM_CXICONSPACING = 68 SM_CYICONSPACING = 72 SM_CYKANJIWINDOW = 0 SM_CYMENU = 18 SM_CXMIN = 100 SM_CYMIN = 24 SM_CXMINTRACK = 100 SM_CYMINTRACK = 24 SM_CXSCREEN = 640 SM_CYSCREEN = 480 SM_CXSIZE = 18 SM_CYSIZE = 18 SM_CXVSCROLL = 17 SM_CYVSCROLL = 17 SM_CYVTHUMB = 17 SM_DBCSENABLED = 0 SM_DEBUG = 0 SM_MENUDROPALIGNMENT = 0 SM_MOUSEPRESENT = 1 SM_PENWINDOWS = 0 SM_RESERVED1 = 0 SM_RESERVED2 = 0 SM_RESERVED3 = 0 SM_RESERVED4 = 0 SM_SWAPBUTTON = 0 Общие размеры экрана определяются метриками SM_CXSCREEN и SM_CYSCREEN. В приведенном выше листинге эти значения соответствуют разрешению 640 х 480. Максимальный размер внутренней области окна можно определить из метрик SM_CXFULLSCREEN и SM_CYFULLSCREEN. В нашем случае максимальная ширина внутренней области окна равна максимальной ширине экрана (640), в то время как максимальная высота меньше на высоту заголовка окна. Высота заголовка определяется метрикой SM_CYCAPTION и в нашем случае равна 20. При разрешении 1024 х 768 метрики SM_CXSCREEN, SM_CYSCREEN, SM_CXFULLSCREEN и SM_CYFULLSCREEN изменили свое значение: SM_CXFULLSCREEN = 1024 SM_CYFULLSCREEN = 748 SM_CXSCREEN = 1024 SM_CYSCREEN = 768 Остальные метрики не изменились и соответствовали значениям для разрешения 640 х 480. Для того чтобы сделать приложение нечувствительным к используемому разрешению, вы не должны предполагать, что экран видеомонитора имеет какие-либо конкретные размеры. Размеры экрана должны определяться динамически с использованием соответствующих метрик. В этом случае при изменении разрешения размеры всех элементов изображения, нарисованных в окне приложения (и размеры самого окна) будут пересчитаны заново. Обратите внимание на метрики SM_CXICON и SM_CYICON, определяющие размеры пиктограммы. Эти размеры потребуются вам при необходимости нарисовать в окне пиктограмму. Программный интерфейс Windows имеет специальную функцию DrawIcon, позволяющую нарисовать в окне приложения пиктограмму. Метрика SM_MOUSEPRESENT позволяет приложению определить, есть ли в составе оборудования компьютера мышь. К сожалению, в Windows не предусмотрено никакого средства для определения количества клавиш, имеющихся на корпусе мыши. 4.3. Определение возможностей устройств ввода/выводаВ программном интерфейсе Windows имеется функция GetDeviceCaps, позволяющая по контексту определить возможности и параметры драйвера, обслуживающего устройство ввода/вывода. Функция GetDeviceCaps имеет следующий прототип: int WINAPI GetDeviceCaps(HDC hdc, int iCapability); Первый параметр функции (hdc) задает контекст устройства, для которого необходимо получить информацию о его возможностях. Вы можете, например, указать значение, полученное от функции BeginPaint или GetDC. Второй параметр (iCapability) определяет параметр устройства, значение которого необходимо получить. Приведем список возможных значений для второго параметра функции GetDeviceCaps. Все эти значения определены как символьные константы в файле windows.h.
Не все перечисленные выше значения будут сразу вам нужны, поэтому мы остановимся только на некоторых из них. Значения ASPECTX, ASPECTY, ASPECTXY определяют размеры пиксела. Зачем вам могут понадобиться размеры пиксела? Дело в том, что пикселы не всегда квадратные (или круглые). Поэтому в некоторых режимах работы видеоадаптера масштаб изображения по оси x может отличаться от масштаба по оси y. Размеры пиксела позволят вам вычислить отношение сторон пиксела и выполнить правильное масштабирование. В этом случае отображаемые вами окружности будут круглыми, а квадраты - квадратными. Иногда бывает важно знать цветовое разрешение устройства вывода. Для этого можно использовать значение BITSPIXEL, которое соответствует количеству бит, используемых для представления цвета. Если возвести число 2 в степень значения BITSPIXEL, получится количество цветов, которое может быть представлено одним пикселом. Некоторые устройства работают с цветовыми плоскостями. Количество этих плоскостей можно определить, пользуясь значением PLANES. Об использовании цветовых плоскостей можно прочитать в третьем томе "Библиотеки системного программиста", который называется "Программирование видеоадаптеров CGA, EGA и VGA". Отметим, что количество цветов, которые могут быть представлены устройством с цветовыми плоскостями, равно 2n, где n - количество цветовых плоскостей. Если устройство работает с цветовыми плоскостями и использует несколько бит для представления цвета одного пиксела, количество одновременно отображаемых цветов можно определить по формуле: nColors = 2(nPixel * nPlanes), где nPixel - количество битов, используемых для представления цвета пиксела (значение BITSPIXEL); nPlanes - количество цветовых плоскостей (значение PLANES). Значение NUMCOLORS равно количеству цветов при использовании одной палитры. Так как палитра может быть перегружена, фактически вы можете использовать больше цветов, чем указано в NUMCOLORS. Но в этом случае вы должны сами перезагружать палитру. Для устройств, работающих с палитрами, правильное количество используемых цветов возвращается при использовании значения COLORRES. 4.4. Приложение DCAPSДля проведения экспериментов и демонстрации возможностей функции GetDeviceCaps мы подготовили приложение DCAPS (листинг 4.4). Листинг 4.4. Файл dcaps\dcaps.cpp // ---------------------------------------- // Определение возможностей устройств // ---------------------------------------- #define STRICT #include <windows.h> #include <stdio.h> #include <string.h> // Прототипы функций void PrintD(int, char *); void PrintH(int, char *); void PrintFlag(int, int, char *); FILE *out; // файл для вывода HDC hdc; // идентификатор контекста char buf[80]; // рабочий буфер int i; // рабочая переменная // =========================================== // Функция WinMain // =========================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Открываем выходной файл для вывода // текста потоком // Если открыть файл не удалось, выводим // сообщение об ошибке if ((out = fopen("devcap.txt", "wt")) == NULL) { MessageBox(NULL, "Не могу открыть файл sysmet.txt", "Ошибка", MB_OK | MB_ICONSTOP); return 1; } // Выводим заголовок файла fputs("* ============================ *\n", out); fputs("* DCAPS, (C) Frolov A.V., 1994 *\n", out); fputs("* ============================ *\n\n", out); // Создаем контекст устройства для дисплея hdc = CreateDC("DISPLAY", NULL, NULL, NULL); // Выводим основные характеристики устройства PrintH(DRIVERVERSION, "DRIVERVERSION"); PrintD(ASPECTX, "ASPECTX "); PrintD(ASPECTXY, "ASPECTXY "); PrintD(ASPECTY, "ASPECTY "); PrintD(BITSPIXEL, "BITSPIXEL "); PrintD(COLORRES, "COLORRES "); PrintD(HORZRES, "HORZRES "); PrintD(HORZSIZE, "HORZSIZE "); PrintD(LOGPIXELSX, "LOGPIXELSX "); PrintD(LOGPIXELSY, "LOGPIXELSY "); PrintD(NUMBRUSHES, "NUMBRUSHES "); PrintD(NUMCOLORS, "NUMCOLORS "); PrintD(NUMFONTS, "NUMFONTS "); PrintD(NUMMARKERS, "NUMMARKERS "); PrintD(NUMPENS, "NUMPENS "); PrintD(NUMRESERVED, "NUMRESERVED "); PrintD(PDEVICESIZE, "PDEVICESIZE "); PrintD(PLANES, "PLANES "); PrintD(SIZEPALETTE, "SIZEPALETTE "); PrintD(VERTRES, "VERTRES "); PrintD(VERTSIZE, "VERTSIZE "); fputs("-------------------------------\n", out); PrintH(CLIPCAPS, "CLIPCAPS "); fputs("-------------------------------\n", out); PrintFlag(CLIPCAPS, CP_NONE, "CP_NONE "); PrintFlag(CLIPCAPS, CP_RECTANGLE, "CP_RECTANGLE "); PrintFlag(CLIPCAPS, CP_REGION, "CP_REGION "); fputs("-------------------------------\n", out); PrintH(CURVECAPS, "CURVECAPS "); fputs("-------------------------------\n", out); PrintFlag(CURVECAPS, CC_CIRCLES, "CC_CIRCLES "); PrintFlag(CURVECAPS, CC_CHORD, "CC_CHORD "); PrintFlag(CURVECAPS, CC_ELLIPSES, "CC_ELLIPSES "); PrintFlag(CURVECAPS, CC_INTERIORS, "CC_INTERIORS "); PrintFlag(CURVECAPS, CC_NONE, "CC_NONE "); PrintFlag(CURVECAPS, CC_PIE, "CC_PIE "); PrintFlag(CURVECAPS, CC_ROUNDRECT, "CC_ROUNDRECT "); PrintFlag(CURVECAPS, CC_STYLED, "CC_STYLED "); PrintFlag(CURVECAPS, CC_WIDE, "CC_WIDE "); PrintFlag(CURVECAPS, CC_WIDESTYLED,"CC_WIDESTYLED "); fputs("-------------------------------\n", out); PrintH(LINECAPS, "LINECAPS "); fputs("-------------------------------\n", out); PrintFlag(LINECAPS, LC_INTERIORS, "LC_INTERIORS "); PrintFlag(LINECAPS, LC_MARKER, "LC_MARKER "); PrintFlag(LINECAPS, LC_NONE, "LC_NONE "); PrintFlag(LINECAPS, LC_POLYLINE, "LC_POLYLINE "); PrintFlag(LINECAPS, LC_POLYMARKER,"LC_POLYMARKER "); PrintFlag(LINECAPS, LC_STYLED, "LC_STYLED "); PrintFlag(LINECAPS, LC_WIDE, "LC_WIDE "); PrintFlag(LINECAPS, LC_WIDESTYLED,"LC_WIDESTYLED "); fputs("-------------------------------\n", out); PrintH(POLYGONALCAPS,"POLYGONALCAPS "); fputs("-------------------------------\n", out); PrintFlag(POLYGONALCAPS, PC_INTERIORS,"PC_INTERIORS "); PrintFlag(POLYGONALCAPS, PC_NONE, "PC_NONE "); PrintFlag(POLYGONALCAPS, PC_POLYGON, "PC_POLYGON "); PrintFlag(POLYGONALCAPS, PC_RECTANGLE,"PC_RECTANGLE "); PrintFlag(POLYGONALCAPS, PC_SCANLINE, "PC_SCANLINE "); PrintFlag(POLYGONALCAPS, PC_STYLED, "PC_STYLED "); PrintFlag(POLYGONALCAPS, PC_WIDE, "PC_WIDE "); PrintFlag(POLYGONALCAPS,PC_WIDESTYLED,"PC_WIDESTYLED "); fputs("-------------------------------\n", out); PrintH(RASTERCAPS,"RASTERCAPS "); fputs("-------------------------------\n", out); PrintFlag(RASTERCAPS, RC_BANDING, "RC_BANDING "); PrintFlag(RASTERCAPS, RC_BIGFONT, "RC_BIGFONT "); PrintFlag(RASTERCAPS, RC_BITBLT, "RC_BITBLT "); PrintFlag(RASTERCAPS, RC_BITMAP64, "RC_BITMAP64 "); PrintFlag(RASTERCAPS, RC_DEVBITS, "RC_DEVBITS "); PrintFlag(RASTERCAPS, RC_DI_BITMAP, "RC_DI_BITMAP "); PrintFlag(RASTERCAPS, RC_DIBTODEV, "RC_DIBTODEV "); PrintFlag(RASTERCAPS, RC_FLOODFILL, "RC_FLOODFILL "); PrintFlag(RASTERCAPS, RC_GDI20_OUTPUT,"RC_GDI20_OUTPUT "); PrintFlag(RASTERCAPS, RC_GDI20_STATE, "RC_GDI20_STATE "); PrintFlag(RASTERCAPS, RC_NONE, "RC_NONE "); PrintFlag(RASTERCAPS, RC_OP_DX_OUTPUT,"RC_PO_DX_OUTPUT "); PrintFlag(RASTERCAPS, RC_PALETTE, "RC_PALETTE "); PrintFlag(RASTERCAPS, RC_SAVEBITMAP, "RC_SAVEBITMAP "); PrintFlag(RASTERCAPS, RC_SCALING, "RC_SCALING "); PrintFlag(RASTERCAPS, RC_STRETCHBLT, "RC_STRETCHBLT "); PrintFlag(RASTERCAPS, RC_STRETCHDIB, "RC_STRETCHDIB "); fputs("-------------------------------\n", out); PrintH(TECHNOLOGY,"TECHNOLOGY"); fputs("-------------------------------\n", out); strcpy(buf, "Технология: "); i = GetDeviceCaps(hdc, TECHNOLOGY); switch (i) { case DT_CHARSTREAM: strcat(buf, "DT_CHARSTREAM"); break; case DT_DISPFILE: strcat(buf, "DT_DISPFILE"); break; case DT_METAFILE: strcat(buf, "DT_METAFILE"); break; case DT_PLOTTER: strcat(buf, "DT_PLOTTER"); break; case DT_RASDISPLAY: strcat(buf, "DT_RASDISPLAY"); break; case DT_RASPRINTER: strcat(buf, "DT_RASPRINTER"); break; case DT_RASCAMERA: strcat(buf, "DT_RASCAMERA"); break; default: strcat(buf, "Неизвестная технология"); break; } strcat(buf, "\n"); fputs(buf, out); fputs("-------------------------------\n", out); PrintH(TEXTCAPS,"TEXTCAPS "); fputs("-------------------------------\n", out); PrintFlag(TEXTCAPS, TC_OP_CHARACTER, "TC_OP_CHARACTER "); PrintFlag(TEXTCAPS, TC_OP_STROKE, "TC_OP_STROKE "); PrintFlag(TEXTCAPS, TC_CP_STROKE, "TC_CP_STROKE "); PrintFlag(TEXTCAPS, TC_CR_90, "TC_CR_90 "); PrintFlag(TEXTCAPS, TC_CR_ANY, "TC_CR_ANY "); PrintFlag(TEXTCAPS, TC_SF_X_YINDEP, "TC_SF_X_YINDEP "); PrintFlag(TEXTCAPS, TC_SA_DOUBLE, "TC_SA_DOUBLE "); PrintFlag(TEXTCAPS, TC_SA_INTEGER, "TC_SA_INTEGER "); PrintFlag(TEXTCAPS, TC_SA_CONTIN, "TC_SA_CONTIN "); PrintFlag(TEXTCAPS, TC_EA_DOUBLE, "TC_EA_DOUBLE "); PrintFlag(TEXTCAPS, TC_IA_ABLE, "TC_IA_ABLE "); PrintFlag(TEXTCAPS, TC_UA_ABLE, "TC_UA_ABLE "); PrintFlag(TEXTCAPS, TC_SO_ABLE, "TC_SO_ABLE "); PrintFlag(TEXTCAPS, TC_RA_ABLE, "TC_RA_ABLE "); PrintFlag(TEXTCAPS, TC_VA_ABLE, "TC_VA_ABLE "); PrintFlag(TEXTCAPS, TC_RESERVED, "TC_RESREVED "); // Стираем созданный контекст устройства DeleteDC(hdc); // Закрываем файл fclose(out); MessageBox(NULL, "Сведения о возможностях устройства записаны " "в файл devcap.txt", "DCAPS", MB_OK | MB_ICONINFORMATION); return 0; } // ------------------------------------------ // Функция для вывода параметра с использованием // десятичной системы счисления // ------------------------------------------ void PrintD(int nIndex, char *str) { sprintf(buf, "%s\t%d\n", str, GetDeviceCaps(hdc, nIndex)); fputs(buf, out); return; } // ------------------------------------------ // Функция для вывода параметра с использованием // шестнадцатеричной системы счисления // ------------------------------------------ void PrintH(int nIndex, char *str) { sprintf(buf, "%s\t0x%x\n", str, GetDeviceCaps(hdc, nIndex)); fputs(buf, out); return; } // ------------------------------------------ // Функция для вывода значения отдельных // битов параметра // ------------------------------------------ void PrintFlag(int nIndex, int nFlag, char *str) { if((GetDeviceCaps(hdc, nIndex) & nFlag) == 0) sprintf(buf, "\t%s\t-\n", str); else sprintf(buf, "\t%s\t+\n", str); fputs(buf, out); return; } Это приложение интересно тем, что оно получает контекст устройства (видеоадаптера) новым для вас способом - при помощи функции CreateDC: hdc = CreateDC("DISPLAY", NULL, NULL, NULL); Контекст, полученный этим способом, может быть использован для рисования на всей поверхности экрана видеомонитора, а не только в окне приложения. В качестве первого параметра функции CreateDC указывается имя драйвера устройства. Драйвер видеоадаптера имеет имя DISPLAY. Перед завершением работы приложения мы уничтожаем созданный нами контекст: DeleteDC (hdc); Для вывода числовых характеристик драйвера устройства в шестнадцатеричном формате используется функция PrintH, для вывода в десятичном формате - функция PrintD. Обе эти функции формируют и выводят в заранее открытый текстовый файл строку, содержащую имя параметра и значение, полученное от функции GetDeviceCaps. Для отображения параметров, представленных набором флагов, используется функция PrintFlag. Функция записывает в файл имя флага и его состояние. Если флаг сброшен (значение равно 0), он помечается знаком "-", в противном случае - знаком "+". Для отображения технологии, использованной драйвером, выполняется анализ значения, возвращаемого функцией GetDeviceCaps при передаче ей параметра TECHNOLOGY. В файл записывается возвращаемое этой функцией значение и его символьная расшифровка. Файл определения модуля для приложения DCAPS представлен в листинге 4.5. Листинг 4.5. Файл dcaps\dcaps.def ; ============================= ; Файл определения модуля ; ============================= NAME DCAPS DESCRIPTION 'Приложение DCAPS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple В листинге 4.6 приведен результат работы программы при использовании стандартного драйвера видеоконтроллера VGA, поставляющегося вместе с Windows версии 3.1. Листинг 4.6. Образец файла devcap.txt для драйвера VGA, разрешение 640x480, 16 цветов * ============================ * * DCAPS, (C) Frolov A.V., 1994 * * ============================ * DRIVERVERSION 0x30a ASPECTX 36 ASPECTXY 51 ASPECTY 36 BITSPIXEL 1 COLORRES 0 HORZRES 640 HORZSIZE 208 LOGPIXELSX 96 LOGPIXELSY 96 NUMBRUSHES -1 NUMCOLORS 16 NUMFONTS 0 NUMMARKERS 0 NUMPENS 80 NUMRESERVED 0 PDEVICESIZE 35 PLANES 4 SIZEPALETTE 0 VERTRES 480 VERTSIZE 156 ------------------------------- CLIPCAPS 0x1 ------------------------------- CP_NONE - CP_RECTANGLE + CP_REGION - ------------------------------- CURVECAPS 0x0 ------------------------------- CC_CIRCLES - CC_CHORD - CC_ELLIPSES - CC_INTERIORS - CC_NONE - CC_PIE - CC_ROUNDRECT - CC_STYLED - CC_WIDE - CC_WIDESTYLED - ------------------------------- LINECAPS 0x22 ------------------------------- LC_INTERIORS - LC_MARKER - LC_NONE - LC_POLYLINE + LC_POLYMARKER - LC_STYLED + LC_WIDE - LC_WIDESTYLED - ------------------------------- POLYGONALCAPS 0x8 ------------------------------- PC_INTERIORS - PC_NONE - PC_POLYGON - PC_RECTANGLE - PC_SCANLINE + PC_STYLED - PC_WIDE - PC_WIDESTYLED - ------------------------------- RASTERCAPS 0x46d9 ------------------------------- RC_BANDING - RC_BIGFONT + RC_BITBLT + RC_BITMAP64 + RC_DEVBITS - RC_DI_BITMAP + RC_DIBTODEV + RC_FLOODFILL - RC_GDI20_OUTPUT + RC_GDI20_STATE - RC_NONE - RC_PO_DX_OUTPUT + RC_PALETTE - RC_SAVEBITMAP + RC_SCALING - RC_STRETCHBLT - RC_STRETCHDIB - ------------------------------- TECHNOLOGY 0x1 ------------------------------- Технология: DT_RASDISPLAY ------------------------------- TEXTCAPS 0x2204 ------------------------------- TC_OP_CHARACTER - TC_OP_STROKE - TC_CP_STROKE + TC_CR_90 - TC_CR_ANY - TC_SF_X_YINDEP - TC_SA_DOUBLE - TC_SA_INTEGER - TC_SA_CONTIN - TC_EA_DOUBLE + TC_IA_ABLE - TC_UA_ABLE - TC_SO_ABLE - TC_RA_ABLE + TC_VA_ABLE - TC_RESREVED - В поле DRIVERVERSION стоит значение 30a, что соответствует версии драйвера 3.1. Для вывода используется квадратный пиксел с размерами (36, 36), так что никаких мер для обеспечения равного масштаба по осям x и y принимать не надо. Стандартный драйвер обеспечивает вывод 16 цветов. Из приведенного выше листинга видно, что для представления цвета пиксела используется один бит (значение BITSPIXEL равно 1). Количество цветовых плоскостей (значение PLANES) равно 4, следовательно, количество возможных цветов будет равно 24, или 16. Значение бита RC_PALETTE значения RASTERCAPS равно нулю, следовательно, драйвер не использует палитры. Поэтому значение COLORRES (цветовое разрешение в битах на пиксел) равно нулю. Значение SIZEPALETTE также равно 0. Количество возможных цветов, определяемых при помощи NUMCOLORS, равно 16. Это значение согласуется с полученным при помощи BITSPIXEL и PLANES. Для сравнения приведем возможности драйверов контроллера Cirrus Logic в режимах с использованием 65536 цветов и 16,7 млн. цветов (точное значение - 224 цветов). Результаты работы нашего приложения для этих драйверов приведены соответственно в листинге 4.7 и 4.8. Листинг 4.7. Образец файла devcap.txt для драйвера Cirrus Logic, разрешение 640x480, 65536 цветов * ============================ * * DCAPS, (C) Frolov A.V., 1994 * * ============================ * DRIVERVERSION 0x30a ASPECTX 36 ASPECTXY 51 ASPECTY 36 BITSPIXEL 16 COLORRES 0 HORZRES 640 HORZSIZE 208 LOGPIXELSX 96 LOGPIXELSY 96 NUMBRUSHES -1 NUMCOLORS 4096 NUMFONTS 0 NUMMARKERS 0 NUMPENS 100 NUMRESERVED 0 PDEVICESIZE 32 PLANES 1 SIZEPALETTE 0 VERTRES 480 VERTSIZE 152 ------------------------------- CLIPCAPS 0x1 ------------------------------- CP_NONE - CP_RECTANGLE + CP_REGION - ------------------------------- CURVECAPS 0x0 ------------------------------- CC_CIRCLES - CC_CHORD - CC_ELLIPSES - CC_INTERIORS - CC_NONE - CC_PIE - CC_ROUNDRECT - CC_STYLED - CC_WIDE - CC_WIDESTYLED - ------------------------------- LINECAPS 0x22 ------------------------------- LC_INTERIORS - LC_MARKER - LC_NONE - LC_POLYLINE + LC_POLYMARKER - LC_STYLED + LC_WIDE - LC_WIDESTYLED - ------------------------------- POLYGONALCAPS 0x8 ------------------------------- PC_INTERIORS - PC_NONE - PC_POLYGON - PC_RECTANGLE - PC_SCANLINE + PC_STYLED - PC_WIDE - PC_WIDESTYLED - ------------------------------- RASTERCAPS 0x2d9 ------------------------------- RC_BANDING - RC_BIGFONT - RC_BITBLT + RC_BITMAP64 + RC_DEVBITS - RC_DI_BITMAP + RC_DIBTODEV + RC_FLOODFILL - RC_GDI20_OUTPUT + RC_GDI20_STATE - RC_NONE - RC_PO_DX_OUTPUT - RC_PALETTE - RC_SAVEBITMAP + RC_SCALING - RC_STRETCHBLT - RC_STRETCHDIB - ------------------------------- TECHNOLOGY 0x1 ------------------------------- Технология: DT_RASDISPLAY ------------------------------- TEXTCAPS 0x2004 ------------------------------- TC_OP_CHARACTER - TC_OP_STROKE - TC_CP_STROKE + TC_CR_90 - TC_CR_ANY - TC_SF_X_YINDEP - TC_SA_DOUBLE - TC_SA_INTEGER - TC_SA_CONTIN - TC_EA_DOUBLE - TC_IA_ABLE - TC_UA_ABLE - TC_SO_ABLE - TC_RA_ABLE + TC_VA_ABLE - TC_RESREVED - Из листинга 4.7 видно, что драйвер использует для представления цвета пиксела 16 бит (значение BITSPIXEL). Количество цветовых слоев равно 1 (значение PLANES), поэтому общее количество доступных цветов равно 65536. Тем не менее значение NUMCOLORS равно 4096, что не соответствует цветовым возможностям данного драйвера. Здесь нет ошибки, дело в том, что, как мы уже говорили, значение NUMCOLORS соответствует количеству цветов в одной палитре, но не количеству доступных цветов. Обратим теперь внимание на результаты тестирования драйвера, работающего в режиме TrueColor с использованием 16,7 млн. цветов (листинг 4.8). Листинг 4.8. Образец файла devcap.txt для драйвера Cirrus Logic, разрешение 640x480, 16,7 млн. цветов * ============================ * * DCAPS, (C) Frolov A.V., 1994 * * ============================ * DRIVERVERSION 0x300 ASPECTX 36 ASPECTXY 51 ASPECTY 36 BITSPIXEL 24 COLORRES 0 HORZRES 640 HORZSIZE 208 LOGPIXELSX 96 LOGPIXELSY 96 NUMBRUSHES -1 NUMCOLORS 4096 NUMFONTS 0 NUMMARKERS 0 NUMPENS 100 NUMRESERVED 0 PDEVICESIZE 32 PLANES 1 SIZEPALETTE 0 VERTRES 480 VERTSIZE 152 ------------------------------- CLIPCAPS 0x1 ------------------------------- CP_NONE - CP_RECTANGLE + CP_REGION - ------------------------------- CURVECAPS 0x0 ------------------------------- CC_CIRCLES - CC_CHORD - CC_ELLIPSES - CC_INTERIORS - CC_NONE - CC_PIE - CC_ROUNDRECT - CC_STYLED - CC_WIDE - CC_WIDESTYLED - ------------------------------- LINECAPS 0x22 ------------------------------- LC_INTERIORS - LC_MARKER - LC_NONE - LC_POLYLINE + LC_POLYMARKER - LC_STYLED + LC_WIDE - LC_WIDESTYLED - ------------------------------- POLYGONALCAPS 0x8 ------------------------------- PC_INTERIORS - PC_NONE - PC_POLYGON - PC_RECTANGLE - PC_SCANLINE + PC_STYLED - PC_WIDE - PC_WIDESTYLED - ------------------------------- RASTERCAPS 0x2d9 ------------------------------- RC_BANDING - RC_BIGFONT - RC_BITBLT + RC_BITMAP64 + RC_DEVBITS - RC_DI_BITMAP + RC_DIBTODEV + RC_FLOODFILL - RC_GDI20_OUTPUT + RC_GDI20_STATE - RC_NONE - RC_PO_DX_OUTPUT - RC_PALETTE - RC_SAVEBITMAP + RC_SCALING - RC_STRETCHBLT - RC_STRETCHDIB - ------------------------------- TECHNOLOGY 0x1 ------------------------------- Технология: DT_RASDISPLAY ------------------------------- TEXTCAPS 0x2004 ------------------------------- TC_OP_CHARACTER - TC_OP_STROKE - TC_CP_STROKE + TC_CR_90 - TC_CR_ANY - TC_SF_X_YINDEP - TC_SA_DOUBLE - TC_SA_INTEGER - TC_SA_CONTIN - TC_EA_DOUBLE - TC_IA_ABLE - TC_UA_ABLE - TC_SO_ABLE - TC_RA_ABLE + TC_VA_ABLE - TC_RESREVED - Для этого режима цвет одного пиксела определяется 24 битами (значение BITSPIXEL) при использовании одной цветовой плоскости (значение PLANES). Поэтому общее количество доступных цветов равно 224, или, примерно 16,7 млн. Значение NUMCOLORS по-прежнему равно 4096. Приведем сравнительную таблицу параметров, связанных с размерами экрана и пикселов в различных режимах и для различных типов видеоадаптеров.
Размеры экрана в миллиметрах (HORZSIZE и VERTSIZE) относятся скорее к стандартному типу видеомонитора, чем к конкретному типу видеомонитора, подключенного к вашему компьютеру. В любом из перечисленных выше режимов вы можете использовать как 14-дюймовые, так и 17- или даже 20-дюймовые видеомониторы. В любом случае функция GetDeviceCaps будет возвращать значения размеров экрана, приведенные в таблице. Из таблицы также видно, что для наиболее часто встречающихся типов видеоадаптеров (VGA, SVGA 800 x 600, SVGA 1024 x 768) используются квадратные пикселы с размером (36,36). В то же время видеоадаптеры CGA, EGA и 8514/A используют пикселы в форме прямоугольника. Некоторые программы (например, Microsoft Word for Windows) способны рисовать дюймовые или миллиметровые линейки, которые можно использовать для измерения размеров объектов в естественных для человека единицах измерения (но не в пикселах). Значения LOGPIXELSX и LOGPIXELSY определяют количество пикселов в так называемом логическом дюйме. Логический дюйм не является "настоящим" дюймом, имеющим длину 25,4 мм. Его размеры искусственно увеличены примерно в 1,5 раза для удобства изображения текста на экране обычного размера (14 дюймов по диагонали). Если бы размер логического дюйма был равен в точности одному "физическому" дюйму, буквы, набранные шрифтом стандартного размера (порядка 10 - 12 пунктов) были бы слишком мелкими. 4.5. Определение размера окнаПрограммы MS-DOS работали с экраном, который имел фиксированные размеры. В зависимости от видеорежима они использовали либо текстовый экран размером 80 х 25 символов, либо графический экран размером от 640х200 для видеоконтроллеров CGA, до 1024 х 768 или больше для SVGA. Приложение Windows, как правило, не знает заранее размеров отведенного ей окна (или окон, если их несколько). К тому же в любой момент времени вы можете изменить эти размеры при помощи мыши. Разумеется, приложение может запретить изменять размер окна мышью (указав стиль окна без толстой рамки), но такой подход нельзя приветствовать. Поэтому приложение Windows должно уметь определять размеры своих окон. Существует два принципиально разных метода определения размера окна. Первый метод основан на том факте, что, когда вы изменяете размер окна мышью, функция окна получает сообщение с кодом WM_SIZE, параметры которого передают функции новые размеры окна. Второй метод заключается в вызове функций, возвращающих размеры окна по его идентификатору. Например, для определения размеров окна можно воспользоваться функцией GetClientRect. Мы приведем исходные тексты приложения WSIZE, демонстрирующего использование обоих методов. Приложение WSIZE состоит из трех файлов. Первый файл с именем wsize.cpp (листинг 4.9) создает главное окно приложения и запускает цикл обработки сообщений. Файл wndproc.cpp (листинг 4.10) содержит исходный текст функции окна, выполняющий всю интересную для нас работу по определению и отображению текущих размеров главного окна приложения. И наконец, файл wsize.def (листинг 4.11) является файлом определения модуля для нашего приложения. Листинг 4.9. Файл wsize\wsize.cpp // ---------------------------------------- // Определение размеров окна // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "WSIZEAppClass"; char const szWindowTitle[] = "WSIZE Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Выполняем инициализацию приложения if(!InitApp(hInstance)) return FALSE; // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Вызывается из функции WinMain для // инициализации приложения. // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); wc.style = 0; 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); } Файл wsize.cpp не имеет никаких дополнительных особенностей по сравнению с аналогичными файлами предыдущих приложений. Поэтому мы не будем его описывать, а перейдем сразу к функции окна (листинг 4.10). Листинг 4.10. Файл wsize\wndproc.cpp // ===================================== // Функция WndProc // Функция выполняет обработку сообщений // главного окна приложения // ===================================== #define STRICT #include <windows.h> #include <stdio.h> LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static WPARAM fwSizeType; static WORD nWidth; static WORD nHeight; switch (msg) { case WM_PAINT: { char buf[80]; hdc = BeginPaint(hwnd, &ps); sprintf(buf, "%4.4dx%4.4d (%1.1d)", nWidth, nHeight, fwSizeType); // Выводим размеры окна TextOut(hdc, 0, 0, buf, 13); EndPaint(hwnd, &ps); return 0; } case WM_SIZE: { // Способ изменения размера окна fwSizeType = wParam; // Ширина внутренней области окна nWidth = LOWORD(lParam); // Высота внутренней области окна nHeight = HIWORD(lParam); // Отмечаем все окно как требующее // обновления InvalidateRect(hwnd, NULL, TRUE); // Посылаем себе сообщение WM_PAINT UpdateWindow(hwnd); return 0; } case WM_LBUTTONDOWN: { RECT rcWndSize; char buf[80]; hdc = GetDC(hwnd); // Определяем размеры внутренней // области окна GetClientRect(hwnd, &rcWndSize); sprintf(buf, "%4.4dx%4.4d", rcWndSize.right, rcWndSize.bottom); // Выводим размеры внутренней области окна TextOut(hdc, 0, 20, buf, 9); ReleaseDC(hwnd, hdc); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } В области данных функции окна определены статические переменные, предназначенные для хранения размера окна и способа изменения этих размеров (о способах изменения размеров окна мы расскажем немного позже): static WPARAM fwSizeType; static WORD nWidth; static WORD nHeight; При обработке сообщения WM_PAINT содержимое этих переменных выводится в левом верхнем углу окна. Вам может показаться странным, что перед выводом содержимого переменных по сообщению WM_PAINT сами переменные не инициализируются. Это не совсем так. Рассмотрим последовательность действий при создании и отображении окна и перечислим сообщения, получаемые функцией окна на разных стадиях создания. Окно создается функцией CreateWindow. При этом функция окна получает следующие сообщения:
Обычно все эти сообщения (кроме последнего) передаются функции DefWindowProc. В некоторых случаях, когда с окном связаны структуры данных, требующие инициализации при создании окна, функция окна перехватывает сообщение WM_CREATE, определяя по нему момент завершения создания окна и выполняя собственные инициализирующие действия. Для того, чтобы сделать окно видимым, наше приложение вызывает функцию ShowWindow. Эта функция посылает в функцию окна целый каскад сообщений:
Все эти сообщения, кроме двух последних, обычно передаются функции DefWindowProc. Если функция окна должна реагировать на изменение размеров или расположения окна, она должна обрабатывать сообщения WM_SIZE и WM_MOVE. При вызове функции UpdateWindow функция окна получает единственное сообщение WM_PAINT (только в том случае, если окно содержит области, помеченные для обновления). Так как при отображении окна функцией ShowWindow функция окна получает в числе прочих сообщение WM_SIZE, и наше приложение WSIZE его обрабатывает, мы можем инициализировать переменные fwSizeType, nWidth и nHeight до прихода сообщения WM_PAINT, что мы и делаем в нашем обработчике сообщения WM_SIZE: case WM_SIZE: { fwSizeType = wParam; nWidth = LOWORD(lParam); nHeight = HIWORD(lParam); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; } Как мы говорили раньше, вместе с сообщением обычно приходит дополнительная информация, которая передается функции окна через параметры wParam и lParam. До сих пор мы игнорировали эту информацию. Теперь она нам нужна, так как новые размеры окна передаются функции окна вместе с сообщением WM_SIZE именно через параметры wParam и lParam. В операционной системе Windows версии 3.1 тип WPARAM соответствует 16-разрядному слову, а тип LPARAM - 32-разрядному двойному слову. Однако не следует думать, что так будет и в других версиях Windows. Операционная система Windows NT, например, использует для типа WPARAM 32-разрядное слово. Через параметр lParam передается два значения. В операционной системе Windows версии 3.1 эти значения соответствуют младшему и старшему слову lParam. Для обеспечения совместимости со следующими версиями Windows не следует самостоятельно извлекать эти два значения. Нужно пользоваться специально предназначенными для этого макросами LOWORD и HIWORD. Эти макросы определены в файле windows.h: #define LOWORD(l) ((WORD)(l)) #define HIWORD(l) ((WORD)((DWORD)(l) >> 16)) Параметры сообщения WM_SIZE описывают новый размер окна и способ, которым окно изменило свой размер:
Параметр wParam может принимать одно из нескольких значений, символические имена которых начинаются с префикса SIZE_ и определены в файле windows.h следующим образом:
После сохранения параметров сообщения WM_SIZE функция окна нашего приложения посылает сама себе сообщение WM_PAINT. Для этого при помощи функции InvalidateRect она объявляет все окно как требующее обновления и затем вызывает функцию UpdateWindow: InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); Функция InvalidateRect добавляет прямоугольную область, заданную в качестве параметра, к областям окна, требующим обновления (перерисовки). Эта функция имеет следующий прототип: void WINAPI InvalidateRect(HWND, const RECT FAR*, BOOL); Первый параметр указывает идентификатор окна, для которого нужно выполнить операцию добавления области, требующей обновления. Второй параметр - дальний указатель на структуру типа RECT, описывающую координаты области. Если в качестве этого параметра использовать значение NULL (как в нашем примере), вся внутренняя область окна (client area) будет объявлена как требующая обновления. Третий параметр указывает, нужно ли при обновлении стирать фон окна. Если указать значение TRUE, фон окна будет стерт. Для того чтобы оставить фон окна без изменения, укажите в качестве третьего параметра значение FALSE. Функция UpdateWindow вызывается в нашем приложении после того, как вся внутренняя область окна объявлена как требующая обновления, поэтому она передает функции окна сообщение WM_PAINT. Обработчик этого сообщения выводит в левом верхнем углу окна новые координаты окна и код способа, которым были изменены размеры окна. Функция окна нашего приложения обрабатывает сообщение WM_LBUTTONDOWN. Это сообщение передается функции окна, когда вы располагаете курсор мыши над окном и нажимаете левую клавишу мыши. Обработчик этого сообщения с помощью функции GetClientRect определяет текущие размеры окна и выводит их в левом верхнем углу окна под строкой, выведенной обработчиком сообщения WM_PAINT: case WM_LBUTTONDOWN: { RECT rcWndSize; char buf[80]; hdc = GetDC(hwnd); GetClientRect(hwnd, &rcWndSize); sprintf(buf, "%4.4dx%4.4d", rcWndSize.right, rcWndSize.bottom); TextOut(hdc, 0, 20, buf, 9); ReleaseDC(hwnd, hdc); return 0; } Функция GetClientRect предназначена для определения координат внутренней области окна и имеет следующий прототип: void WINAPI GetClientRect(HWND hwnd, RECT FAR* lprc); Первый параметр функции (hwnd) определяет идентификатор окна, для которого требуется определить координаты внутренней области. Второй параметр (lprc) является дальним указателем на структуру типа RECT, в которую записываются координаты внутренней области окна. Эти координаты вычисляются относительно левого верхнего угла внутренней области окна, поэтому в полях left и top структуры RECT всегда записываются нулевые значения. Поля right и bottom содержат соответственно ширину и высоту внутренней области окна. На рис. 4.1 показан внешний вид главного окна приложения WSIZE после того, как в нем щелкнули левой клавишей мыши.
Рис. 4.1. Главное окно приложения WSIZE Из рисунка видно, что оба способа определения размера окна (при обработке сообщения WM_SIZE и при помощи функции GetClientRect) дали одинаковые результаты. Файл определения модуля, который был использован при создании приложения WSIZE, не имеет никаких особенностей и приведен в листинге 4.11. Листинг 4.11. Файл wsize\wsize.def ; ============================= ; Файл определения модуля ; ============================= NAME WSIZE DESCRIPTION 'Приложение WSIZE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple 4.6. Определение расположения окнаВ предыдущем разделе мы научили вас определять размеры окна. Другая важная задача - определение расположения окна на экране видеомонитора. Когда вы перемещаете окно (например, при помощи мыши), функция окна получает сообщение WM_MOVE. Вместе с этим сообщением функция окна получает новые координаты внутренней области окна:
Для перекрывающихся (overlapped) и временных (pop-up) окон координаты отсчитываются от верхнего левого угла экрана. Для дочерних (child) окон эти координаты отсчитываются от верхнего левого угла внутренней области родительского окна. В любой момент времени приложение может определить расположение и размеры окна, вызвав функцию GetWindowRect. Эта функция имеет следующий прототип: void WINAPI GetWindowRect(HWND, RECT FAR*); Первый параметр задает идентификатор окна, для которого необходимо определить расположение и размер. Второй параметр является дальним указателем на структуру типа RECT. В эту структуру будет записана информация о расположении и размере прямоугольной области, ограничивающей окно, с учетом заголовка, рамки и полос просмотра (если они заданы). Все координаты отсчитываются от левого верхнего угла экрана. Для демонстрации использования сообщения WM_MOVE и функции GetWindowRect мы подготовили приложение WPOS. Это приложение по своей структуре аналогично приложению WSIZE, которое было рассмотрено в предыдущем разделе. В файле wpos.cpp (листинг 4.12) расположена функция WinMain, создающая главное окно приложения и цикл обработки сообщений. Листинг 4.12. Файл wpos\wpos.cpp // ---------------------------------------- // Определение расположения окна // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "WPOSAppClass"; char const szWindowTitle[] = "WPOSApplication"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE; 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 = 0; 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); } Функция окна (листинг 4.13) обрабатывает сообщения WM_PAINT, WM_MOVE, WM_SIZE, WM_LBUTTONDOWN и, конечно, WM_DESTROY. Листинг 4.13. Файл wpos\wndproc.cpp // ===================================== // Функция WndProc // ===================================== #define STRICT #include <windows.h> #include <stdio.h> LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования // Координаты окна static WORD xPos; static WORD yPos; switch (msg) { case WM_PAINT: { char buf[80]; hdc = BeginPaint(hwnd, &ps); sprintf(buf, "%4.4dx%4.4d ", xPos, yPos); // Выводим координаты окна TextOut(hdc, 0, 0, buf, 12); EndPaint(hwnd, &ps); return 0; } case WM_MOVE: { // X-координата левого верхнего угла // внутренней области окна xPos = LOWORD(lParam); // Y-координата левого верхнего угла // внутренней области окна yPos = HIWORD(lParam); // Отмечаем все окно как требующее // обновления InvalidateRect(hwnd, NULL, TRUE); // Посылаем себе сообщение WM_PAINT UpdateWindow(hwnd); return 0; } case WM_SIZE: case WM_LBUTTONDOWN: { RECT rc; char buf[80]; hdc = GetDC(hwnd); // Определяем размеры прямоугольника, // ограничивающего окно GetWindowRect(hwnd, &rc); sprintf(buf, "%4.4dx%4.4d %4.4dx%4.4d ", rc.top, rc.left, rc.right, rc.bottom); // Выводим размеры внутренней области окна TextOut(hdc, 0, 20, buf, 25); ReleaseDC(hwnd, hdc); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } Сообщение WM_MOVE передается окну, когда оно отображается функцией ShowWindow, или при изменении размера окна. Наш обработчик сообщения WM_MOVE сохраняет экранные координаты внутренней области окна в переменных с именами xPos и yPos. После сохранения координат функция окна объявляет все окно как требующее обновления и посылает само себе сообщение WM_PAINT, для чего вызывает функцию UpdateWindow: case WM_MOVE: { xPos = LOWORD(lParam); yPos = HIWORD(lParam); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; } Обработчик сообщения WM_PAINT выводит координаты в левом верхнем углу внутренней области окна. Перемещая окно при помощи мыши, вы можете проследить за изменением текущих координат внутренней области окна. Для сообщений WM_SIZE и WM_LBUTTONDOWN используется общий обработчик. Он вызывает функцию GetWindowRect, с помощью которой определяет координаты и размер прямоугольной области, ограничивающей окно. Координаты левого верхнего угла и правого нижнего угла этой области выводятся во второй строке внутренней области окна (рис. 4.2).
Рис. 4.2. Главное окно приложения WPOS При перемещении главного окна приложения WPOS первая строка обновляется автоматически, при этом вторая строка стирается. Если после перемещения сделать щелчок мышью внутри окна или изменить его размер, появится вторая строка. В ней будут отображены координаты прямоугольной области, ограничивающей окно. Для создания приложения был использован файл определения модуля, приведенный в листинге 4.14. Листинг 4.14. Файл wpos\wpos.def ; ============================= ; Файл определения модуля ; ============================= NAME WPOS DESCRIPTION 'Приложение WPOS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Программный интерфейс операционной системы Windows версии 3.1 содержит еще одну функцию, полезную при определении расположения на экране и размеров окна. Эта функция имеет имя GetWindowPlacement и следующий прототип: BOOL WINAPI GetWindowPlacement (HWND hwnd, WINDOWPLACEMENT FAR* lpwndpl); Первый параметр функции (hwnd) задает идентификатор окна, для которого нужно определить расположение и размеры. Второй параметр (lpwndpl) является дальним указателем на структуру типа WINDOWPLACEMENT, определенную в файле windows.h: typedef struct tagWINDOWPLACEMENT { UINT length; UINT flags; UINT showCmd; POINT ptMinPosition; POINT ptMaxPosition; RECT rcNormalPosition; } WINDOWPLACEMENT; Поле length определяет размер структуры WINDOWPLACEMENT в байтах. Поле flags после вызова функции GetWindowPlacement всегда равно нулю. Это поле содержит флаги, определяющие положение минимизированного окна и способ, которым окно будет восстановлено. Поле showCmd после вызова функции GetWindowPlacement имеет значение SW_SHOWMAXIMIZED для максимизированного окна, SW_SHOWMINIMIZED для минимизированного окна и SW_SHOWNORMAL в остальных случаях. Поле ptMinPosition определяет положение верхнего левого угла окна, когда оно минимизировано. Поле ptMaxPosition определяет положение верхнего левого угла окна, когда оно максимизировано. Поле rcNormalPosition определяет положение верхнего левого угла окна, размеры которого восстановлены до нормальных. Для указания положения верхнего левого угла максимизированного и минимизированного окна используется структура POINT, определенная в файле windows.h: typedef struct tagPOINT { int x; int y; } POINT; 4.7. Метрики текстаОперационная система Windows содержит в себе сложную подсистему для работы со шрифтами. В деталях эта система будет описана позже, так как она нетривиальна. Приложения Windows могут выводить текст с использованием различных шрифтов. Многие программы, такие, как текстовые процессоры, позволяют выбирать произвольную (до некоторой степени) высоту букв. Но характеристики шрифтов в Windows не ограничиваются высотой и шириной букв. Для точного определения внешнего вида шрифта используется больше дюжины различных характеристик. Прежде всего следует отметить, что все шрифты в Windows можно разделить на две группы. К первой группе относятся шрифты с фиксированной шириной букв (fixed-pitch font). Все буквы (и знаки, такие, как запятая, точка и т. д.) такого шрифта имеют одинаковую ширину. Вторая группа шрифтов - шрифты с переменной шириной букв (variable-pitch font). Для таких шрифтов каждая буква имеет свою ширину. Буква "Ш", например, шире буквы "i". Кроме того, шрифты Windows можно разделить на растровые (raster font), контурные (stroke font) и масштабируемые (типа TrueType). Образцы этих шрифтов представлены на рис. 4.3.
Рис. 4.3. Растровый, контурный и масштабируемый шрифты Растровые шрифты состоят из отдельных пикселов и используются при выводе текста на экран видеомонитора или принтер. Для обеспечения приемлемого качества текста в Windows имеется набор одинаковых растровых шрифтов для нескольких размеров букв. Если попытаться выполнить масштабирование растрового шрифта в сторону увеличения размера букв, наклонные линии и закругления будут изображаться в виде "лестницы" (см. рис. 4.3). Контурные шрифты больше подходят для плоттеров. При масштабировании таких шрифтов можно достигнуть лучших результатов, чем при масштабировании растровых. Однако при большом размере букв результат все равно получается неудовлетворительный. Масштабируемые шрифты TrueType сохраняют начертание символов при любом изменении размера, поэтому они чаще всего используются, например, при подготовке текстовых документов. Любой из перечисленных выше шрифтов может быть с фиксированной или переменной шириной букв. Первое время в наших примерах приложений мы будем использовать так называемый системный шрифт SYSTEM_FONT. Этот шрифт выбран в контекст отображения по умолчанию, поэтому нам не потребуются никакие функции для выбора шрифта. Системный шрифт используется в Windows, например, для текста в заголовках окон, меню и диалоговых панелях. Системный шрифт относится к растровым шрифтам с переменной шириной букв. Так как ширина букв переменная, вы не сможете выполнять вывод таблицы в несколько столбцов, ориентируясь на ширину букв. Вам придется либо выводить каждый столбец таблицы по отдельности, начиная с некоторой позиции в окне, либо подсчитывать длину каждой ячейки и дополнять ее пробелами до некоторой фиксированной ширины (первый способ нам кажется удобнее). Переменная ширина букв усложняет задачу вывода текста, так как длина текстовой строки зависит не только от количества букв в строке, но и от того, из каких букв состоит строка. К счастью, в составе программного интерфейса Windows имеется специальная функция GetTextExtent, предназначенная для подсчета длины текстовой строки. Для получения информации о шрифте, выбранном в контекст устройства, предназначена функция GetTextMetrics. Она имеет следующий прототип: BOOL WINAPI GetTextMetrics(HDC hdc, TEXTMETRIC FAR* lptm); Первый параметр функции (hdc) указывает контекст устройства, для которого требуется получить информацию о метрике шрифта. В качестве этого параметра можно использовать значение, возвращаемое функцией BeginPaint или GetDC. Второй параметр функции (lptm) является дальним указателем на структуру типа TEXTMETRIC, в которую будет записана информация о метриках шрифта, выбранного в указанный контекст устройства. В случае успешного завершения функция возвращает значение TRUE, в противном случае - FALSE. Структура TEXTMETRIC описана в файле windows.h следующим образом: typedef struct tagTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY; } TEXTMETRIC; Описание этой структуры мы начнем с полей, определяющих вертикальные размеры шрифта. Программисты MS-DOS пользовались, как правило, одним параметром - высотой букв шрифта. Для приложений Windows используются целых пять параметров, имеющих отношение к вертикальным размерам букв (рис. 4.4).
Рис. 4.4. Метрики шрифта Отсчет всех размеров выполняется от так называемой базовой линии шрифта. Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight. Величина tmInternalLeading определяет размер выступающих элементов букв. Эта величина может быть равно нулю. Величина tmExternalLeading определяет минимальный межстрочный интервал, рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать межстрочный интервал, однако в этом случае строки будут соприкасаться друг с другом, что не улучшит внешнего вида окна. А как определить ширину букв? В структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта. Хорошо спроектированные приложения позволяют вам выбирать для отображения текста произвольные шрифты. Поэтому приложение никогда не должно ориентироваться на конкретные размеры шрифта. Вместо этого следует определять эти размеры динамически во время работы приложения с помощью специально предназначенных для этого средств, таких, как функция GetTextMetrics. Другие поля структуры TEXTMETRIC мы рассмотрим позже, в главе, посвященной шрифтам. Для иллюстрации использования метрик шрифта мы подготовили приложение TMETRICS. Главный файл приложения, содержащий функцию WinMain, приведен в листинге 4.15. Листинг 4.15. Файл tmetrics\tmetrics.cpp // ---------------------------------------- // Определение метрики шрифта // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); char const szClassName[] = "TMETRICSAppClass"; char const szWindowTitle[] = "TMETRICS Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE; 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); } При регистрации класса окна мы, указав стиль класса окна, потребовали, чтобы при изменении горизонтального или вертикального размера окна окно отмечалось как требующее обновления: wc.style = CS_HREDRAW | CS_VREDRAW; Функция окна будет получать сообщение WM_PAINT при изменении ширины или высоты окна. Как и раньше, вся полезная работа выполняется функцией главного окна приложения (листинг 4.16). Листинг 4.16. Файл tmetrics\wndproc.cpp // ===================================== // Функция WndProc // ===================================== #define STRICT #include <windows.h> #include <stdio.h> #include <string.h> void Print(HDC, int, char *); static int cxChar, cyChar; static int cxCurrentPosition; static int cyCurrentPosition; LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static TEXTMETRIC tm; // структура для записи метрик // шрифта switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd); // Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm); // Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth; // Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading; // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar; // Освобождаем контекст ReleaseDC(hwnd, hdc); return 0; } case WM_PAINT: { // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar; hdc = BeginPaint(hwnd, &ps); // Выводим параметры шрифта, полученные во // время создания окна при обработке // сообщения WM_CREATE Print(hdc, tm.tmHeight, "tmHeight"); Print(hdc, tm.tmAscent, "tmAscent"); Print(hdc, tm.tmDescent, "tmDescent"); Print(hdc, tm.tmInternalLeading, "tmInternalLeading"); Print(hdc, tm.tmExternalLeading, "tmExternalLeading"); Print(hdc, tm.tmAveCharWidth, "tmAveCharWidth"); Print(hdc, tm.tmMaxCharWidth, "tmMaxCharWidth"); Print(hdc, tm.tmWeight, "tmWeight"); Print(hdc, tm.tmItalic, "tmItalic"); Print(hdc, tm.tmUnderlined, "tmUnderlined"); Print(hdc, tm.tmStruckOut, "tmStruckOut"); Print(hdc, tm.tmFirstChar, "tmFirstChar"); Print(hdc, tm.tmLastChar, "tmLastChar"); Print(hdc, tm.tmDefaultChar, "tmDefaultChar"); Print(hdc, tm.tmBreakChar, "tmBreakChar"); Print(hdc, tm.tmPitchAndFamily, "tmPitchAndFamily"); Print(hdc, tm.tmCharSet, "tmCharSet"); Print(hdc, tm.tmOverhang, "tmOverhang"); Print(hdc, tm.tmDigitizedAspectX,"tmDigitizedAspectX"); Print(hdc, tm.tmDigitizedAspectY,"tmDigitizedAspectY"); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ========================================== // Функция для вывода параметров шрифта // в окно // ========================================== void Print(HDC hdc, int tmValue, char *str) { char buf[80]; int i; // Подготавливаем в рабочем буфере // и выводим в окно начиная с текущей // позиции название параметра sprintf(buf, "%s", str); i = strlen(str); TextOut(hdc, cxCurrentPosition, cyCurrentPosition, buf, i); // Подготавливаем в рабочем буфере // и выводим в текущей строке окна // со смещением значение параметра sprintf(buf, "= %d", tmValue); i = strlen(buf); TextOut(hdc, cxCurrentPosition + 12 * cxChar, cyCurrentPosition, buf, i); // Увеличиваем текущую позицию по // вертикали на высоту символа cyCurrentPosition += cyChar; } Как вы уже знаете, при создании окна в функцию окна передается сообщение WM_CREATE. Это сообщение удобно использовать для инициализации данных, имеющих отношение к окну. В нашем случае при получении сообщения WM_CREATE функция окна получает контекст отображения (с помощью функции GetDC) и вызывает функцию GetTextMetrics, которая записывает сведения о метрике текущего выбранного в контекст шрифта в структуру с именем tm. На основании информации, хранящейся в этой структуре, вычисляются значения переменных cxChar и cyChar, используемых соответственно в качестве ширины и высоты символов. Далее в обработчике сообщения WM_CREATE инициализируются переменные, которые будут использованы для указания текущей позиции вывода текста: cxCurrentPosition = cxChar; cyCurrentPosition = cyChar; Перед возвратом обработчик сообщения WM_CREATE освобождает полученный контекст, вызывая функцию ReleaseDC. Обработчик сообщения WM_PAINT выполняет инициализацию текущей позиции вывода текста и получает контекст отображения (с помощью функции BeginPaint). Вслед за этим с помощью функции Print, определенной в нашем приложении, обработчик выводит в окно названия параметров и их значения. Функция Print выводит параметры в виде таблицы, состоящей из двух колонок. Для этого она вызывает уже знакомую вам функцию TextOut два раза для каждого параметра. В первый раз выводится название параметра, во второй - знак "=" и значение параметра. Перед возвратом из функции текущая позиция вывода текста по вертикали увеличивается на одну строку: cyCurrentPosition += cyChar; Файл определения модуля для приложения TMETRICS приведен в листинге 4.17. Листинг 4.17. Файл tmetrics\tmetrics.def ; ============================= ; Файл определения модуля ; ============================= NAME TMETRICS DESCRIPTION 'Приложение TMETRICS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple Внешний вид главного окна приложения TMETRICS показан на рис. 4.5.
Рис. 4.5. Главное окно приложения TMETRICS Из этого рисунка видно, что для системного шрифта, выбранного по умолчанию, значение tmExternalLeading равно нулю, поэтому возможно "слипание" строк, содержащих символы с "хвостиками", тильдами и надстрочными точками. При уменьшении размера окна может получиться так, что часть строк будет обрезана нижней или правой границей окна (рис. 4.6).
Рис. 4.6. Окно уменьшенного размера Через некоторое время мы научимся добавлять к
окну полосы просмотра, с помощью которых в окне
практически любого размера можно просматривать
сколь угодно много текстовых строк. |