Операционная система Microsoft Windows 3.1 для программиста -том 1

         

Отладка приложений Windows


До появления такой среды разработки приложений Windows, как Borland C++ версии 2.0, отладка приложений требовала наличия двух мониторов или даже двух компьютеров. На одном мониторе отображался ход выполнения программы, другой использовался для работы приложения.

Начиная с версии 2.0 в системе разработки Borland C++ появился одноэкранный отладчик Turbo Debugger for Windows, значительно упрощающий процесс отладки приложений. Для работы этого отладчика достаточно одного компьютера и одного экрана (хотя наилучшие результаты могут быть достигнуты только с двумя компьютерами).

Самый простой способ отладки приложений Windows тем не менее вообще не требует применения отладчика. Он заключается в том, что вы вставляете в разные места исходного текста приложения вызовы функции MessageBox, с помощью которых прослеживаете ход выполнения программы. Если же вам требуется узнать содержимое каких-либо переменных, это можно сделать с помощью функции wsprintf. Вначале с помощью функции wsprintf вы преобразуете интересующее вас значение в текстовую строку, а затем выводите эту строку функцией MessageBox. В некоторых случаях достаточно ограничиться выдачей звукового сигнала, для чего можно вызвать функцию MessageBeep. Примеры вызова функций MessageBox, wsprintf и MessageBeep вы можете найти в приложениях, описанных в нашей книге.

Однако для отладки более сложных приложений вы не сможете ограничиться выдачей диагностических сообщений. Тогда, если вы программист, который только начинает создавать приложения для Windows, мы рекомендуем вам воспользоваться одноэкранным отладчиком Turbo Debugger for Windows, который входит в комплект поставки системы разработки Borland C++ for Windows версии 3.1 или Turbo C++ for Windows.

Учтите, что указанный отладчик не работает с видеоадаптером EGA. Поэтому, если у вас установлен такой видеоадаптер, мы рекомендуем заменить его на другой. Лучше всего приобрести видеоадаптер SVGA с ускорителем для Windows, в котором многие графические операции выполняются аппаратно, благодаря чему скорость вывода изображения сильно повышается.
Стоимость такого видеоадаптера может составлять порядка 100 долларов. В крайнем случае на первом этапе вы можете использовать видеоадаптер VGA, однако в этом случае вам не будут доступны режимы с высоким разрешением и с большим количеством цветов.

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

Для запуска отладчика из среды разработки выберите из меню "Run" строку "Debugger". Перед этим вы можете задать параметры командной строки для отлаживаемого приложения. Для этого из указанного меню надо выбрать строку "Debugger Arguments...".

После выбора строки "Debugger" на экране появится сообщение о загрузке отладчика, после чего экран переключится в текстовый режим. К сожалению, отладчик Turbo Debugger for Windows, который поставляется в комплекте со средой разработки Borland C++ for Windows версии 3.1, может работать только в текстовом режиме (среда разработки Borland C++ for Windows версии 4.0 содержит, помимо отдельного отладчика Turbo Debugger for Windows, отладчик, который встроен в среду разработки и работает в обычном окне Windows в графическом режиме). Несмотря на это, указанный отладчик не способен работать с видеоконтроллером EGA.

После запуска отладчика вы попадаете в среду, знакомую вам по отладчику Turbo Debugger для MS-DOS. В отладчике Turbo Debugger for Windows используются почти такие же функциональные клавиши и меню, что и в версии для MS-DOS. Из-за недостатка места в книге мы не будем описывать работу с отладчиком Turbo Debugger. Если вы разрабатывали программы для MS-DOS, вы скорее всего уже пользовались этим отладчиком. Поэтому мы ограничимся только несколькими замечаниями, которые относятся к отладке приложений Windows.

Первое замечание касается отладки функций обратного вызова, таких, как функция окна WndProc.



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

Если функция окна расположена в отдельном файле, для установки точки останова вам надо перейти в режим просмотра этого файла. Для просмотра модуля, расположенного в отдельном файле, выберите из меню отладчика "View" строку "Module". Вы окажитесь в диалоговой панели, с помощью которой можно выбрать имя модуля. Приложения, приведенные в нашей книге, используют для функции окна файл с именем wndproc.cpp, поэтому в появившейся диалоговой панели выберите строку "WNDPROC".

После этого в окне отладчика вы увидите исходный текст функции окна. Выберите нужное вам место и установите точку останова, нажав клавишу <F2>. Далее запустите приложение на выполнение, нажав клавишу <F9>. Когда будет достигнута установленная вами точка останова, отладчик переключится в пошаговый режим.

Второе замечание относится к просмотру экрана выполняющегося приложения.

Для переключения в режим просмотра экрана приложения и обратно используйте комбинацию клавиш <Alt+F5>. При переключении в верхней части экрана авторы практически всегда наблюдали "мусор". Это не есть результат неправильной работы вашего приложения, поэтому, если можете, не обращайте на него никакого внимания. Возможно, в следующих версиях одноэкранного отладчика Borland сумеет сделать правильное восстановление экрана Windows. Если же подобная "особенность" отладчика вам кажется недопустимой, единственный выход - отладка с помощью двух адаптеров или, что лучше, с помощью двух компьютеров.


Однако для начала вам лучше работать с одноэкранным отладчиком.

Отладчик Turbo Debugger for Windows имеет дополнительные возможности по сравнению с версией для MS-DOS. В частности, с помощью этого отладчика можно протоколировать сообщения, которые получают и посылают окна вашего приложения, работать с глобальной и локальной памятью, просматривать полный список загруженных модулей, отлаживать библиотеки динамической загрузки DLL и просматривать содержимое любого селектора.

Для просмотра списка сообщений, получаемых функцией окна, выберите в отладчике из меню "View" строку "Windows Message". На экране появится диалоговая панель "Windows Message", разделенная на три части. В левой верхней части следует указать имя функции окна или идентификатор окна, для которого нужно протоколировать сообщения. В правой верхней части окна можно выбирать, какие сообщения следует отображать, а какие - игнорировать. Нижняя часть окна предназначена для отображения получаемых сообщений.

Выберите мышью левую верхнюю часть окна "Windows Message" и нажмите комбинацию клавиш <Alt+F10>. На экране появится меню из трех строк:

Add... Remove Delete all

Выберите строку "Add...". На экране появится диалоговая панель "Add window or handle to watch". В этой диалоговой панели установите переключатель "Identify by" в положение "Window proc" и в поле "Window identifier" введите имя функции окна, например wndproc. Затем нажмите кнопку "OK".

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

В правой верхней части диалоговой панели "Windows Message" находится надпись "Log all messages", которая означает, что будут протоколироваться все сообщения, поступающие в указанные вами окна. Если сообщений слишком много, вы можете выбрать для отображения только некоторые классы сообщений.


Для этого выберите мышью правую верхнюю часть диалоговой панели "Windows Message" и нажмите комбинацию клавиш <Alt+F10>. Появится уже знакомое вам меню:

Add... Remove Delete all

Выберите строку "Add...". С помощью переключателя "Message class" вы можете выбрать различные классы отображаемых (протоколируемых) сообщений:

Класс сообщения Описание
All Messages Все сообщения
Mouse Сообщения от мыши
Window Сообщения от системы управления окнами (такие, как WM_PAINT и WM_CREATE)
Input Сообщения от клавиатуры, системного меню, полос просмотра или кнопок изменения размера
System Системные сообщения
Initialization Сообщения, которые появляются при создании приложением окна или диалоговой панели
Clipboard Сообщения, которые создаются приложением при доступе к универсальному буферу обмена Clipboard или окну другого приложения
DDE Сообщения, которые возникают в результате динамического обмена данными между приложениями с использованием механизма DDE
Non-client Сообщения, которые создаются Windows для работы с внешней (non-client) областью окна (такие, как WM_NCHITTEST или WM_NCCREATE)
Other Остальные сообщения, не попавшие в перечисленные выше классы, такие, как сообщения интерфейса MDI
Single Message Любое выбранное вами сообщение. Символическое имя или десятичный идентификатор сообщения следует указать в поле "Single Message Name"
С помощью переключателя "Action" вы можете задать действие, выполняемое отладчиком при перехвате сообщения. По умолчанию включен режим "Log", что соответствует протоколированию сообщения и отображению его в нижней части диалоговой панели "Windows Messages". Режим "Break" позволяет вам организовать останов приложения по сообщению.

Остальные возможности отладчика будут рассмотрены позже.


Отображение окна на экране


Итак, окно создано. Однако на экране оно еще не появилось, в чем вы можете убедиться, запустив приложение под управлением отладчика. Поэтому, проверив, что создание окна выполнено успешно (функция CreateWindow вернула ненулевое значение), необходимо сделать окно видимым (нарисовать его на экране). Это можно сделать с помощью функции ShowWindow:

ShowWindow(hwnd, nCmdShow);

Прототип функции:

BOOL ShowWindow(HWND hwnd, int nCmdShow);

Функция отображает окно, идентификатор которого задан первым параметром (hwnd), в нормальном, максимально увеличенном или уменьшенном до пиктограммы виде, в зависимости от значения второго параметра (nCmdShow). Наше приложение использует в качестве второго параметра значение, передаваемое функции WinMain через параметр nCmdShow.

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

Внешний вид окна, создаваемого нашим приложением, показан на рис. 1.12.

Рис. 1.12. Главное окно приложения

Сразу после функции ShowWindow в приложении вызывается функция UpdateWindow.

UpdateWindow(hwnd);

Прототип функции:

void UpdateWindow(HWND hwnd);

Функция UpdateWindow вызывает функцию окна, заданного идентификатором, передаваемым в качестве параметра hwnd, и передает ей сообщение WM_PAINT. Получив это сообщение, функция окна должна перерисовать все окно или его часть. Наше приложение не обрабатывает это сообщение, передавая его функции DefWindowProc. Сообщение WM_PAINT и способ его обработки будут описаны позже, когда мы займемся рисованием в окне.



Параметры клавиатурных сообщений


Сообщения WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP передают информацию о нажатой клавише через параметры lParam и wParam.

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

Параметр wParam содержит код виртуальной клавиши, соответствующей нажатой физической клавише. Именно этот параметр используется приложениями для идентификации нажатой клавиши.

Приведем описание отдельных бит парамера lParam.

Бит Описание
0-15 Счетчик повторов. Если нажать клавишу и держать ее в нажатом состоянии, несколько сообщений WM_KEYDOWN и WM_SYSKEYDOWN будут слиты в одно. Количество объединенных таким образом сообщений
16-23 OEM скан-код клавиши. Изготовители аппаратуры (OEM - Original Equipment Manufacturer) могут заложить в своей клавиатуре различное соответствие скан-кодов и обозначений клавиш. Скан-код генерируется клавиатурным контроллером. Это тот самый код, который получают в регистре AH программы MS-DOS, вызывая прерывание INT16h
24 Флаг расширенной клавиатуры. Этот бит установлен в 1, если сообщение соответствует клавише, имеющейся только на расширенной 101- или 102-клавишной клавиатуре. Это может быть одна из следующих клавиш: <Home>, <End>, <PgUp>, <PgDn>, <Insert>, <Delete>, клавиши дополнительной клавиатуры.
25-26 Не используются
27-28 Зарезервированы для использования Windows
29 Код контекста. Этот бит равен 1, если сообщение соответствует комбинации клавиши <Alt> с любой другой, и 0 в противном случае
30 Предыдущее состояние клавиши. Если перед приходом сообщения клавиша, соответствующая сообщению, была в нажатом состоянии, этот бит равен 1. В противном случае бит равен 0
31 Флаг изменения состояния клавиши (transition state). Если клавиша была нажата, бит равен 0, если отпущена - 1

Если нажать клавишу и оставить ее в нажатом состоянии, функция окна может получить подряд несколько сообщений WM_KEYDOWN, прежде чем придет сообщение WM_KEYUP.
В этом случае бит предыдущего состояния клавиши (бит 30) можно использовать для обнаружения сообщений, возникших в результате включения автоповтора клавиатуры. Приложение может игнорировать такие сообщения, исключая эффект накопления, когда приложение не может обрабатывать сообщения с такой скоростью, с какой они поступают. Например, если вы будете долго держать клавишу пролистывания страниц в текстовом редакторе, то после того, как вы ее отпустите, текстовый редактор будет еще долго листать ваш документ.
Для сообщений WM_KEYDOWN и WM_KEYUP значение кода контекста (бит 29) и флага изменения состояния (бит 31) всегда равно 0.
Для сообщений WM_SYSKEYUP и WM_SYSKEYDOWN бит 31 равен 1. Но есть два исключения.
Во-первых, если активное окно свернуто в пиктограмму, все сообщения от клавиатуры преобразовываются в системные и, если клавиша <Alt> не нажата, код контекста равен 0.
Во-вторых, на некоторых клавиатурах для ввода символов национального языка могут использоваться комбинации с участием клавиш <Alt>, <Control>, <Shift> и т. п. В этом случае код контекста может быть равен 1, хотя сообщение не является системным.
Обработчик клавиатурного сообщения должен возвратить значение 0 для всех перехваченных сообщений.
Теперь мы расскажем вам о параметре wParam.
Как мы уже говорили, этот параметр содержит код виртуальной клавиши, соответствующей нажатой физической клавише. Код виртуальной клавиши не зависит от аппаратной реализации клавиатуры.
Многие коды виртуальных клавиш имеют символьное обозначение, определенное в файле windows.h. Приведем полный список кодов виртуальных клавиш. Те из них, которые определены в файле windows.h, имеют префикс VK_ (от слов Virtual Key).


Символическое имя Код виртуальной клавиши Клавиша, которой соответствует данный код Клавиша на клавиатуре IBM PC
Не определено 0x0  
VK_LBUTTON 0x1 Левая клавиша мыши  
VK_RBUTTON 0x2 Правая клавиша мыши  
VK_CANCEL 0x3 <Control + Break> <Control + Break>
VK_MBUTTON 0x4 Средняя клавиша мыши  
Не определено 0x5 - 0x7 Не определено  
VK_BACK 0x8 Клавиша забоя Клавиша забоя <Backspace>
VK_TAB 0x9 Клавиша табулятора <Tab>
Не определено 0xa - 0xb Не определено  
VK_CLEAR 0xc CLEAR Соответствует клавише <5> дополнительной клавиатуры при выключенном режиме <Num Lock>
VK_RETURN 0xd RETURN <Enter>
Не определено 0xe - 0xf Не определено  
VK_SHIFT 0x10 SHIFT <Shift>
VK_CONTROL 0x11 CONTROL <Control>
VK_MENU 0x12 MENU <Alt>
VK_PAUSE 0x13 PAUSE <Pause>
VK_CAPITAL 0x14 CAPITAL <Caps Lock>
Не определено 0x15 - 0x19 Зарезервировано для систем Kanji  
Не определено 0x1a Не определено  
VK_ESCAPE 1b ESCAPE <Esc>
Не определено 0x1c - 0x1f Не определено  
VK_SPACE 0x20 Клавиша пробела SPACEBAR Клавиша пробела
VK_PRIOR 0x21 PAGE UP <PgUp>
VK_NEXT 0x22 PAGE DOWN <PgDn>
VK_END 0x23 END <End>
VK_HOME 0x24 HOME <Home>
VK_LEFT 0x25 Перемещение курсора влево LEFT ARROW Клавиша перемещения курсора влево <Left>
VK_UP 0x26 Перемещение курсора вверх UP ARROW Клавиша перемещения курсора вверх <Up>
VK_RIGHT 0x27 Перемещение курсора вправо RIGHT ARROW Клавиша перемещения курсора вправо <Right>
VK_DOWN 0x28 Перемещение курсора вниз DOWN ARROW Клавиша перемещения курсора вниз <Down>
VK_SELECT 0x29 SELECT  
VK_PRINT 0x2a Зависит от изготовителя клавиатуры  
VK_EXECUTE 0x2b EXECUTE  
VK_SNAPSHOT 0x2c PRINTSCREEN <PrtSc>
VK_INSERT 0x2d INSERT <Insert>
VK_DELETE 0x2e DELETE <Delete>
VK_HELP 0x2f HELP  
Не определено 0x30 0 <0>
Не определено 0x31 1 <1>
Не определено 0x32 2 <2>
Не определено 0x33 3 <3>
Не определено 0x34 4 <4>
Не определено 0x35 5 <5>
Не определено 0x36 6 <6>
Не определено 0x37 7 <7>
Не определено 0x38 8 <8>
Не определено 0x39 9 <9>
Не определено 0x3a - 0x40 Не определено  
Не определено 0x41 A <A>
Не определено 0x42 B <B>
Не определено 0x43 C <C>
Не определено 0x44 D <D>
Не определено 0x45 E <E>
Не определено 0x46 F <F>
Не определено 0x47 G <G>
Не определено 0x48 H <H>
Не определено 0x49 I <I>
Не определено 0x4a J <J>
Не определено 0x4b K <K>
Не определено 0x4c L <L>
Не определено 0x4d M <M>
Не определено 0x4e N <N>
Не определено 0x4f O <O>
Не определено 0x50 P <P>
Не определено 0x51 Q <Q>
Не определено 0x52 R <R>
Не определено 0x53 S <S>
Не определено 0x54 T <T>
Не определено 0x55 U <U>
Не определено 0x56 V <V>
Не определено 0x57 W <W>
Не определено 0x58 X <X>
Не определено 0x59 Y <Y>
Не определено 0x5a Z <Z>
Не определено 0x5b - 0x5f Не определено  
VK_NUMPAD0 0x60 0 на цифровой клавиатуре <0> на цифровой клавиатуре
VK_NUMPAD1 0x61 1 на цифровой клавиатуре <1> на цифровой клавиатуре
VK_NUMPAD2 0x62 2 на цифровой клавиатуре <2> на цифровой клавиатуре
VK_NUMPAD3 0x63 3 на цифровой клавиатуре <3> на цифровой клавиатуре
VK_NUMPAD4 0x64 4 на цифровой клавиатуре <4> на цифровой клавиатуре
VK_NUMPAD5 0x65 5 на цифровой клавиатуре <5> на цифровой клавиатуре
VK_NUMPAD6 0x66 6 на цифровой клавиатуре <6> на цифровой клавиатуре
VK_NUMPAD7 0x67 7 на цифровой клавиатуре <7> на цифровой клавиатуре
VK_NUMPAD8 0x68 8 на цифровой клавиатуре <8> на цифровой клавиатуре
VK_NUMPAD9 0x69 9 на цифровой клавиатуре <9> на цифровой клавиатуре
VK_MULTIPLAY 0x6a Клавиша умножения <*> на цифровой клавиатуре
VK_ADD 0x6b Клавиша сложения <+> на цифровой клавиатуре
VK_SEPARATOR 0x6c Клавиша разделения  
VK_SUBTRACT 0x6d Клавиша вычитания <-> на цифровой клавиатуре
VK_DECIMAL 0x6e Клавиша десятичной точки <.> на цифровой клавиатуре
VK_DIVIDE 0x6f Клавиша деления </> на цифровой клавиатуре
VK_F1 0x70 F1 <F1>
VK_F2 0x71 F2 <F2>
VK_F3 0x72 F3 <F3>
VK_F4 0x73 F4 <F4>
VK_F5 0x74 F5 <F5>
VK_F6 0x75 F6 <F6>
VK_F7 0x76 F7 <F7>
VK_F8 0x77 F8 <F8>
VK_F9 0x78 F9 <F9>
VK_F10 0x79 F10 <F10>
VK_F11 0x7a F11 <F11>
VK_F12 0x7b F12 <F12>
VK_F13 0x7c F13  
VK_F14 0x7d F14  
VK_F15 0x7e F15  
VK_F16 0x7f F16  
Не определено 0x80 - 0x87 Зависит от изготовителя клавиатуры  
Не определено 0x88 - 0x8f Не определено  
VK_NUMLOCK 0x90 NUM LOCK <Num Lock>
VK_SCROLL 0x91 SCROLL LOCK <Scroll Lock>
Не определено 0x92 - 0xb9 Не определено  
Не определено 0xba Клавиша знака пунктуации ;
Не определено 0xbb Плюс + =
Не определено 0xbc Запятая , <
Не определено 0xbd Минус - _
Не определено 0xbe Точка . >
Не определено 0xbf Клавиша знака пунктуации / ?
Не определено 0xc0 Клавиша знака пунктуации ` ~
Не определено 0xc1 - 0xda Не определено  
Не определено 0xdb Клавиша знака пунктуации [ {
Не определено 0xdc Клавиша знака пунктуации \ |
Не определено 0xdd Клавиша знака пунктуации ] }
Не определено 0xde Клавиша знака пунктуации ' "
Не определено 0xdf Клавиша знака пунктуации  
Не определено 0xe0 - 0xe1 Зависит от изготовителя клавиатуры  
Не определено 0xe2 Знак неравенства  
Не определено 0xe3 - 0xe4 Зависит от изготовителя клавиатуры  
Не определено 0xe5 Не определено  
Не определено 0xe6 Зависит от изготовителя клавиатуры  
Не определено 0xe7 - 0xe8 Не определено  
Не определено 0xe9 - 0xf5 Зависит от изготовителя клавиатуры  
Не определено 0xf6 - 0xff Не определено  
<


Рассматривая приведенную выше таблицу, нетрудно заметить, что в ней есть коды виртуальных клавиш, которые невозможно получить в компьютере с IBM-совместимой клавиатурой. Кроме того, используя только эти коды, невозможно различить строчные и прописные буквы.
Для определения состояния клавиш <Shift>, <Caps Lock>, <Control>, <Num Lock> сразу после получения сообщения функция окна должна вызвать функцию с именем GetKeyState, которая входит в программный интерфейс Windows. Приведем прототип этой функции:
int WINAPI GetKeyState(int vkey);
Параметр функции vkey должен указывать код виртуальной клавиши, для которой необходимо вернуть состояние.
Старший бит возвращаемого значения, установленный в 1, говорит о том, что указанная клавиша была нажата. Если этот бит равен 0, клавиша не была нажата.
Младший бит возвращаемого значения указывает состояние переключения. Если он равен 1, клавиша (такая, как <Caps Lock> или <Num Lock>) находится во включенном состоянии, то есть она была нажата нечетное число раз после включения компьютера. Учтите, что при помощи программы установки параметров компьютера SETUP, расположенной в BIOS, вы можете задать произвольное состояние переключающих клавиш после запуска системы.
Функция GetKeyState возвращает состояние клавиши на момент извлечения сообщения из очереди приложения функцией GetMessage.
Для того чтобы узнать состояние клавиш в любой произвольный момент времени, можно воспользоваться функцией GetAsyncKeyState: int WINAPI GetAsyncKeyState (int vkey);
Параметр функции vkey должен указывать код виртуальной клавиши, для которой необходимо вернуть состояние.
Старший бит возвращаемого значения, установленный в 1, говорит о том, что указанная клавиша была нажата в момент вызова функции. Если этот бит равен 0, клавиша не была нажата.
Младший бит возвращаемого значения установлен в 1, если указанная клавиша была нажата с момента последнего вызова функции GetAsyncKeyState.
Если для функции в качестве параметра задать значения VK_LBUTTON или VK_RBUTTON, можно узнать состояние клавиш, расположенных на корпусе мыши.


Есть возможность определить и изменить состояние для всех клавиш одновременно.
Для определения состояния клавиш воспользуйтесь функцией GetKeyboardState:
void WINAPI GetKeyboardState (BYTE FAR* lpbKeyState);
Единственный параметр lpbKeyState этой функции - дальний указатель на массив из 256 байт. После вызова функции этот массив будет заполнен информацией о состоянии всех виртуальных клавиш в момент генерации клавиатурного сообщения. В этом смысле функция аналогична функции GetKeyState.
Для любого байта массива установленный в 1 старший бит означает, что соответствующая клавиша была нажата. Если этот бит равен 0, клавиша не была нажата. Младший бит, установленный в 1, означает, что клавиша была переключена. Если младший бит равен 0, клавиша не была переключена.
После вызова функции GetKeyboardState вы можете изменить содержимое массива и вызвать функцию SetKeyboardState, изменяющую состояние клавиатуры:
void WINAPI SetKeyboardState(BYTE FAR* lpbKeyState);
Функция GetKeyboardType позволит вам определить тип клавиатуры и количество имеющихся на ней функциональных клавиш:
int WINAPI GetKeyboardType(int fnKeybInfo);
В зависимости от значения параметра fnKeybInfo функция может возвращать различную информацию о клавиатуре.
Если задать значение параметра fnKeybInfo, равное 0, функция вернет код типа клавиатуры:

Код Тип клавиатуры Количество функциональных клавиш
1 Клавиатура IBM PC/XT или совместимая, 83-клавишная 10
2 Клавиатура Olivetti "ICO", 102-клавишная 12 (иногда 18)
3 Клавиатура IBM AT или аналогичная, 84-клавишная 10
4 Клавиатура IBM Enhanced (улучшенная), 101- или 102-клавишная 12
5 Клавиатура Nokia 1050 или аналогичная 10
6 Клавиатура Nokia 9140 или аналогичная 24
7 Японская клавиатура Зависит от аппаратуры

Если задать значение параметра, равное 1, функция вернет код подтипа клавиатуры.
И наконец, если задать значение параметра, равное 2, функция вернет количество функциональных клавиш, имеющихся на клавиатуре.


Интересна также функция GetKeyNameText, возвращающая для заданного кода виртуальной клавиши название соответствующей клавиши в виде текстовой строки. Названия виртуальных клавиш определены в драйвере клавиатуры.
Приведем прототип функции GetKeyNameText:
int WINAPI GetKeyNameText(LONG lParam, LPSTR lpszBuffer, int cbMaxKey);
Первый параметр функции lParam должен определять клавишу в формате компоненты lParam клавиатурного сообщения. Вы можете использовать в качестве этого параметра значение lParam, полученное функцией окна вместе с любым клавиатурным сообщением, таким, как WM_KEYDOWN или WM_SYSKEYDOWN.
Второй параметр - lpszBuffer является указателем на буфер, в который будет записано название клавиши.
Третий параметр - cbMaxKey должен быть равен длине буфера, уменьшенной на 1.
Приведем исходный текст приложения KBTYPE, определяющего тип и подтип клавиатуры, а также количество функциональных клавиш (листинг 5.1).
Листинг 5.1. Файл kbtype\kbtype.cpp
// ---------------------------------------- // Определение типа клавиатуры // ----------------------------------------
#define STRICT #include <windows.h>
#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Рабочий буфер char szBuf[80];
// Рабочие переменные int type, subtype, nfkeys, size;
// Типы клавиатур char *apszKbTypes[] = { "IBM PX/XT", "Olivetti ICO", "IBM AT", "IBM Enhanced", "Nokia 1050", "Nokia 9140", "Японская", };
// Определяем тип клавиатуры type = GetKeyboardType(0);
// Он должен лежать в интервале от // 1 до 7. Если это не так, завершаем // работу приложения с сообщением об ошибке if (type == 0 type > 7) { MessageBox(NULL, "Ошибка в типе клавиатуры", "KBTYPE Application", MB_ICONSTOP); return 0; }
// Определяем подтип клавиатуры subtype = GetKeyboardType(1);
// Определяем количество функциональных // клавиш nfkeys = GetKeyboardType(2);


// Подготавливаем буфер и выводим его size = wsprintf(szBuf, "Клавиатура %s,\nподтип %d,\n", (LPSTR)apszKbTypes[type-1], subtype);
wsprintf(szBuf + size, " %d функциональных клавиш", nfkeys);
MessageBox(NULL, szBuf, "KBTYPE Application", MB_OK | MB_ICONINFORMATION); return 0; }
Приложение использует файл определения модуля, приведенный в листинге 5.2.
Листинг 5.2. Файл kbtype\kbtype.def
; ============================= ; Файл определения модуля ; ============================= NAME KBTYPE DESCRIPTION 'Приложение KBTYPE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Работа приложения KBTYPE понятна без дополнительных комментариев. Единственное, на чем нам хотелось бы остановиться, так это на использовании для подготовки текстового буфера функции wsprintf.
Функция wsprintf входит в ядро Windows и используется аналогично функции sprintf. Эта функция определена в файле windows.h следующим образом:
int FAR CDECL wsprintf(LPSTR lpszOut, LPCSTR lpszFmt, ...);
Первый параметр функции является дальним указателем на буфер, в который будет записана сформированная текстовая строка, закрытая двоичным нулем.
Второй параметр - указатель на строку формата, определяющую формат строки, которая будет записана в буфер. Допустимо использовать следующие спецификаторы форматов вывода:

Спецификатор Формат
c Один символ
d, i Целое число со знаком
ld, li Двойное целое число со знаком
u Целое число без знака
lu Двойное целое число без знака
lx, lX Двойное целое число без знака в шестнадцатеричном формате строчными или прописными буквами
s Текстовая строка

Далее следует произвольное число переменных, описанных в строке формата. Так как функции передается переменное число параметров, она (в отличие от подавляющего большинства функций программного интерфейса Windows) использует для передачи параметров соглашение языка Си, а не Паскаль.


Для вывода текстовых строк необходимо использовать явное преобразование типа, как это сделано в нашем примере:
size = wsprintf(szBuf, "Клавиатура %s,\nподтип %d,\n", (LPSTR)apszKbTypes[type-1], subtype);
Функция возвращает количество байт, записанных в выходной буфер, без учета двоичного нуля, закрывающего текстовую строку.
На рис. 5.1 представлено сообщение, которое было выведено при запуске приложения KBTYPE на компьютере одного из авторов этой книги.

Рис. 5.1. Сообщение приложения KBTYPE
Прежде чем перейти к следующему разделу, приведем исходные тексты еще одного приложения, демонстрирующего использование функций GetKeyboardState и SetKeyboardState для изменения состояния клавиш <Num Lock>, <Caps Lock>, <Scroll Lock>. Это приложение называется KBLED. Исходный текст основного файла приложения приведен в листинге 5.3.
Листинг 5.3. Файл kbled\kbled.cpp
// ---------------------------------------- // Переключение состояния виртуальных // клавиш // ----------------------------------------
#define STRICT #include <windows.h>
#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Буфер для записи состояния клавиш BYTE aKBState[256];
// Определяем состояние клавиш GetKeyboardState(aKBState);
MessageBox(NULL, "Нажмите 'OK' для" " переключения клавиш <NumLock>, " "<ScrollLock>, <CapsLock>", "KBLED Application", MB_OK | MB_ICONINFORMATION);
// Инвертируем текущее состояние клавиш // <NumLock>, <ScrollLock>, <CapsLock> aKBState[VK_NUMLOCK] ^= 1; aKBState[VK_SCROLL] ^= 1; aKBState[VK_CAPITAL] ^= 1;
// Устанавливаем новое состояние клавиш SetKeyboardState(aKBState);
MessageBox(NULL, "Нажмите 'OK' для обратного" " переключения", "KBLED Application", MB_OK | MB_ICONINFORMATION);
// Возвращаем исходное состояние клавиш // <NumLock>, <ScrollLock>, <CapsLock> aKBState[VK_NUMLOCK] ^= 1; aKBState[VK_SCROLL] ^= 1; aKBState[VK_CAPITAL] ^= 1;


// Устанавливаем новое состояние клавиш SetKeyboardState(aKBState);
return 0; }
Это простое приложение выводит на экран сообщение, в котором говорится, что для переключения состояния виртуальных клавиш надо нажать кнопку "OK". Когда вы нажмете эту кнопку, состояние трех виртуальных клавиш изменится на противоположное. Это нетрудно проконтролировать при помощи светодиодов, расположенных на клавиатуре: все они должны изменить свое состояние на противоположное.
Когда вы ответите на второе сообщение, приложение возвратит исходное состояние клавиш (и светодиодов).
Файл определения модуля для приложения KBLED приведен в листинге 5.4.
Листинг 5.4. Файл kbled\kbled.def
; ============================= ; Файл определения модуля ; ============================= NAME KBLED DESCRIPTION 'Приложение KBLED, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple

Перекрывающиеся (overlapped) окна


Перекрывающиеся окна обычно используются в качестве главного окна приложения. Такие окна имеют заголовок (title bar), рамку и, разумеется, внутреннюю часть окна (client region). Дополнительно перекрывающиеся окна могут иметь (а могут и не иметь) системное меню, кнопки для максимального увеличения размера окна и для сворачивания окна в пиктограмму, вертикальную и горизонтальную полосу просмотра (scroll bar) и меню.

В первых версиях операционной системы Windows (версии 1.х) окна располагались рядом и назывались tiled window (tile - черепица). Сейчас такие окна не используются, вместо них появились перекрывающиеся окна, способные перекрывать окна других приложений. Перекрывающиеся окна называются также окнами верхнего уровня (top-level window).

Файл windows.h содержит следующее определение стиля перекрывающегося окна:

#define WS_OVERLAPPED 0x00000000L

В нашем приложении для определения стиля перекрывающегося окна мы использовали символическую константу WS_OVERLAPPEDWINDOW, определенную как логическое ИЛИ нескольких констант:

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | \ WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | \ WS_MAXIMIZEBOX)

Константа WS_OVERLAPPED определяет базовый стиль окна - перекрывающееся окно. Стиль WS_OVERLAPPEDWINDOW в добавление к базовому указывает, что окно должно иметь заголовок (константа WS_CAPTION), системное меню (WS_SYSMENU), толстую рамку для изменения размера окна (WS_THICKFRAME), кнопку минимизации размера окна (WS_MINIMIZEBOX) и кнопку для максимального увеличения размера окна (WS_MAXIMIZEBOX). Если окно имеет заголовок, вы можете его перемещать по экрану.

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

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

Таким образом, если вы создаете несколько перекрывающихся окон, одни окна могут принадлежать другим.

Если окно-хозяин сворачивается в пиктограмму, все окна, которыми оно владеет, становятся невидимыми. Если вы сначала свернули в пиктограмму окно, которым владеет другое окно, а затем и окно-хозяин, пиктограмма первого (подчиненного) окна исчезает.

Если вы уничтожили окно, автоматически уничтожаются и все принадлежащие ему окна.

Обычное перекрывающееся окно, не имеющее окна-владельца, может располагаться в любом месте экрана и принимать любые размеры. Подчиненные окна располагаются всегда над поверхностью окна-владельца, загораживая его.

Координаты создаваемых функцией CreateWindow перекрывающихся окон указываются по отношению ко всему экрану. Таким образом, если вы создаете перекрывающееся окно с координатами (0, 0), оно будет расположено в верхнем левом углу экрана.


Подключение таймера к окну


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

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

#define FIRST_TIMER 1 int nTimerID; nTimerID = SetTimer(hwnd, FIRST_TIMER, 1000, NULL);

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

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

KillTimer(hwnd, FIRST_TIMER);

Для изменения интервала посылки сообщений вам следует вначале уничтожить таймер, а потом создать новый, работающий с другим периодом времени:

KillTimer(hwnd, FIRST_TIMER); nTimerID = SetTimer(hwnd, FIRST_TIMER, 100, NULL);



Приложение 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.

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

Параметр функции GetDeviceCaps CGA EGA VGA SVGA 800x 600 8514/A SVGA 1024 x 768
HORZRES 640 640 640 800 1024 1024
VERTRES 200 350 480 600 760 768
HORZSIZE 240 240 208 208 280 208
VERTSIZE 180 175 156 152 210 152
ASPECTX 5 38 36 36 10 36
ASPECTY 12 48 36 36 14 36
ASPECTXY 13 61 51 51 14 51
LOGPIXELSX 96 96 96 96 120 96
LOGPIXELSY 48 72 96 96 120 96
Размеры экрана в миллиметрах (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 пунктов) были бы слишком мелкими.


Приложение KBMSG


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

В верхней части главного окна приложения расположены две строки заголовка отдельных полей параметров сообщения. В остальной части окна организован построчный вывод самих параметров сообщения со сверткой (рис. 5.9).

Рис. 5.9. Главное окно приложения KBMSG

В первом столбце выводится символическое имя полученного от клавиатуры сообщения, во втором - символ ANSI, соответствующий параметру wParam полученного сообщения, затем следуют значения wParam и lParam в шестнадцатеричном представлении, и в последнем столбце выводится имя виртуальной клавиши, полученное по коду клавиши от драйвера клавиатуры.

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

Функция WinMain приложения KBMSG определена в файле kbmsg.cpp (листинг 5.7).

Листинг 5.7. Файл kbmsg\kbmsg.cpp

// ---------------------------------------- // Просмотр сообщений от клавиатуры // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "KBMSGAppClass"; char const szWindowTitle[] = "KBMSG 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)) { // Вызываем функцию, создающую // символьные сообщения TranslateMessage(&msg);

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:

while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }

Перед обычной функцией DispatchMessage вызывается функция TranslateMessage, которая на основе сообщений WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP создает сообщения WM_CHAR, WM_SYSCHAR, WM_DEADCHAR и WM_SYSDEADCHAR.

При регистрации класса окна указывается стиль класса, который требует перерисовки окна при изменении его вертикального или горизонтального размера:

wc.style = CS_HREDRAW | CS_VREDRAW;

Других особенностей (по сравнению с нашими предыдущими приложениями) функция WinMain не имеет.

Функция окна, обрабатывающая все сообщения, поступающие в главное окно приложения KBMSG, приведена в листинге 5.8.

Листинг 5.8. Файл kbmsg\wndproc.cpp

// ===================================== // Функция WndProc // =====================================



#define STRICT #include <windows.h> #include <stdio.h> #include <string.h>

void PrintMsg(HWND, WPARAM, LPARAM, char *);

static int cxChar, cyChar; RECT rect;

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);

// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);

// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;

// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;

// Освобождаем контекст ReleaseDC(hwnd, hdc);

// Задаем верхнюю границу несвертываемой // области окна. Эта область будет использована // для двух строк заголовка rect.top = 3 * cyChar;

return 0; }

case WM_SIZE: { // Сохраняем координаты нижнего правого // угла окна rect.right = LOWORD(lParam); rect.bottom = HIWORD(lParam);

// Перерисовываем все окно InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);

return 0; }

case WM_PAINT: { // Две строки заголовка сообщений char szHead1[] = "Message Char wParam lParam KeyName"; char szHead2[] = "------- ---- ------ ------ -------";

hdc = BeginPaint(hwnd, &ps);

// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

// Выводим строки заголовка TextOut(hdc, cxChar, cyChar/2, szHead1, sizeof(szHead1) - 1); TextOut(hdc, cxChar, cyChar/2 + cyChar, szHead2, sizeof(szHead2) - 1);

EndPaint(hwnd, &ps); return 0; }

case WM_KEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_KEYDOWN"); break; }



case WM_KEYUP: { PrintMsg(hwnd, wParam, lParam, "WM_KEYUP"); break; }

case WM_SYSKEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_SYSKEYDOWN"); break; }

case WM_SYSKEYUP: { PrintMsg(hwnd, wParam, lParam, "WM_SYSKEYUP"); break; }

case WM_CHAR: { PrintMsg(hwnd, wParam, lParam, "WM_CHAR"); break; }

case WM_SYSCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_SYSCHAR"); break; }

case WM_DEADCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_DEADCHAR"); break; }

case WM_SYSDEADCHAR: { PrintMsg(hwnd, wParam, lParam, "WM_SYSDEADCHAR"); break; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ========================================== // Функция для вывода параметров сообщений // от клавиатуры в окно // ==========================================

void PrintMsg(HWND hwnd, WPARAM wParam, LPARAM lParam, char *szMsg) { HDC hdc; char szBuf[256]; char szKeyName[20]; int nBufSize; int rc;

// Сворачиваем часть окна, не занятую заголовком ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);

hdc = GetDC(hwnd);

// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

// Получаем имя клавиши, определенное в // драйвере клавиатуры rc = GetKeyNameText(lParam, szKeyName, 20); if(!rc) MessageBeep(0);

// Подготавливаем строку, описывающую сообщение nBufSize = wsprintf(szBuf, "%-14s %c %02x %08lX %-20s", (LPSTR)szMsg, (BYTE)wParam, (BYTE)wParam, lParam, (LPSTR)szKeyName);

// Выводим строку в нижнюю часть окна TextOut(hdc, cxChar, rect.bottom - cyChar, szBuf, nBufSize);

ReleaseDC(hwnd, hdc);

// Удаляем всю внутреннюю часть окна из // списка областей, требующих обновления ValidateRect(hwnd, NULL); }

При создании главного окна приложения (функцией CreateWindow) функция окна получает сообщение WM_CREATE. Наш обработчик этого сообщения создает контекст отображения и выбирает в него системный шрифт с фиксированной шириной букв, для чего вызывает функции GetStockObject и SelectObject:



SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

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

После выбора шрифта обработчик сообщения WM_CREATE определяет метрики шрифта. В переменные cxChar и cyChar записывается соответственно ширина и высота букв.

Далее контекст отображения освобождается.

В поле top переменной rect типа RECT записывается координата верхней границы сворачиваемой области главного окна приложения:

rect.top = 3 * cyChar;

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

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

При отображении окна функцией ShowWindow функция окна получает среди прочих сообщение WM_SIZE. Обработчик этого сообщения определяет координаты правого нижнего угла окна, сохраняя их в переменной rect:

rect.right = LOWORD(lParam); rect.bottom = HIWORD(lParam);

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

После определения координат обработчик сообщения WM_SIZE объявляет все окно как требующее перерисовки и генерирует сообщение WM_PAINT, вызывая функцию UpdateWindow:

InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);

Это приводит к тому, что при изменении размеров окна содержимое окна стирается и в него выводятся только две строки заголовка.



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

Далее в исходном тексте функции окна расположены несколько обработчиков для клавиатурных сообщений. Все они вызывают функцию PrintMsg, отображающую параметры сообщения, вслед за чем передают сообщение функции DefWindowProc:

case WM_KEYDOWN: { PrintMsg(hwnd, wParam, lParam, "WM_KEYDOWN"); break; }

Таким образом, сообщения от клавиатуры не изымаются приложением, оно только "подсматривает" за ними.

Отображение параметров сообщения выполняется функцией PrintMsg, определенной в нашем приложении.

Кроме идентификатора окна, нужного для свертки окна и получения контекста отображения, функции PrintMsg передаются параметры сообщения и текстовая строка символического имени сообщения.

Функция PrintMsg начинает свою работу со свертки окна для освобождения в его нижней части места для вывода параметров очередного сообщения:

ScrollWindow(hwnd, 0, -cyChar, &rect, &rect);

Для свертки окна используется функция ScrollWindow, входящая в состав программного интерфейса Windows:

void WINAPI ScrollWindow(HWND hwnd, int dx, int dy, const RECT FAR* lprcScroll, const RECT FAR* lprcClip);

Первый параметр (hwnd) этой функции определяет идентификатор сворачиваемого окна.

Второй (dx) и третий (dy) парамеры определяют величину сдвига при свертке соответственно по горизонтали и вертикали. Для свертки влево и вверх параметры должны иметь отрицательное значение, для свертки вправо и вниз - положительное. В нашем случае окно сворачивается только в верхнем направлении, поэтому второй параметр равен нулю, а третий задает сдвиг на высоту буквы.

Четвертый параметр (lprcScroll) определяет прямоугольный участок внутренней области окна, подлежащей свертке. В нашем случае это нижняя часть окна (верхняя часть используется для вывода двух строк заголовка).

Пятый параметр (lprcClip) задает ограничивающую область, внутри которой выполняется сдвиг.


В нашем случае эта область совпадает с заданной четвертым параметром.

В качестве четвертого и пятого параметра можно задать значение NULL, в этом случае будет свернута вся внутренняя область окна.

Далее функция PrintMsg выбирает системный шрифт с фиксированной шириной букв и получает имя клавиши, соответствующее параметру lParam. Для получения имени клавиши, определенном в драйвере клавиатуры, вызывается уже знакомая вам функция GetKeyNameText:

rc = GetKeyNameText(lParam, szKeyName, 20); if(!rc) MessageBeep(0);

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

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

Для вывода строки вызывается функция TextOut:

TextOut(hdc, cxChar, rect.bottom - cyChar, szBuf, nBufSize);

Строка выводится начиная с позиции (cxChar, rect.bottom - cyChar), то есть в нижней части главного окна приложения.

Перед завершением работы функция PrintMsg вызывает функцию ValidateRect:

ValidateRect(hwnd, NULL);

Эта функция описана в файле windows.h:

void WINAPI ValidateRect(HWND hwnd, const RECT FAR* lprc);

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

Для чего здесь вызывается эта функция?

Дело в том, что после вызова функции ScrollWindow область свертки помечается как требующая обновления, поэтому функция окна получит сообщение WM_PAINT и только что выведенная строка будет стерта.

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



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

Листинг 5.9. Файл kbmsg\kbmsg.def

; ============================= ; Файл определения модуля ; ============================= NAME KBMSG DESCRIPTION 'Приложение KBMSG, (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 содержит еще одну функцию, предназначенную для свертки окна, - функцию ScrollWindowEx:

int WINAPI ScrollWindowEx(HWND hwnd, int dx, int dy, const RECT FAR* lprcScroll, const RECT FAR* lprcClip, HRGN hrgnUpdate, RECT FAR* lprcUpdate, UINT fuScroll);

Эта функция аналогична функции ScrollWindow, но имеет три дополнительных параметра - hrgnUpdate, lprcUpdate, и fuScroll.

Параметр hrgnUpdate определяет область, которая будет обновлена в результате свертки. Этот параметр можно указывать как NULL.

Параметр lprcUpdate является указателем на структуру типа RECT, в которую после вызова функции ScrollWindowEx будут записаны координаты границ области, обновленной в результате свертки. Этот параметр также можно указывать как NULL.

Параметр fuScroll определяет флаги, которые используются для управления режимом свертки:

Символическое имя Описание
SW_SCROLLCHILDREN Выполнить свертку всех дочерних окон, пересекающих прямоугольную область, заданную параметром lprcScroll. Все эти дочерние окна получат сообщение WM_MOVE
SW_INVALIDATE После выполнения свертки область, указанная параметром hrgnUpdate, отмечается как требующая обновления
SW_ERASE Если указан флаг SW_INVALIDATE, обновленная область стирается посылкой сообщения WM_ERASEBKGND
Если не указаны флаги SW_INVALIDATE или SW_ERASE, функция ScrollWindowEx не объявляет требующей обновления область, из которой было выдвинуто изображение в результате выполнения свертки.


Приложение MOUSENC


Приложение MOUSENC демонстрирует обработку сообщений WM_NCHITTEST и WM_MOUSEMOVE.

Основной файл приложения приведен в листинге 6.4.

Листинг 6.4. Файл mousenc\mousenc.cpp

// ---------------------------------------- // Обработка сообщений от мыши // для внешней (non-client) области окна // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "MOUSENCAppClass"; char const szWindowTitle[] = "MOUSENC 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 | CS_DBLCLKS;

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); }


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

Функция главного окна определена в файле wndproc.cpp (листинг 6.5).

Листинг 6.5. Файл mousenc\wndproc.cpp

// ===================================== // Функция WndProc // =====================================

#define STRICT #include <windows.h>

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; WORD xPosScr, yPosScr, nSizeScr; WORD xPos, yPos, nSize; BYTE szBuf[80];

static TEXTMETRIC tm; static int cxChar, cyChar;

switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта 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; }

case WM_NCHITTEST: { // Если убрать знак комментария со следующей // строки, окно можно будет передвигать не только // при помощи заголовка окна, но и просто // установив курсор мыши в любую область окна

// return HTCAPTION;

// Сохраняем координаты курсора мыши xPosScr = LOWORD(lParam); yPosScr = HIWORD(lParam);

hdc = GetDC(hwnd);

// Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%-3d, %-3d)", xPosScr, yPosScr);

// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

// Выводим экранные координаты курсора мыши TextOut(hdc, cxChar, 0, szBuf, nSize);

ReleaseDC(hwnd, hdc); break; }

case WM_MOUSEMOVE: { // Сохраняем координаты курсора мыши xPos = LOWORD(lParam); yPos = HIWORD(lParam);



hdc = GetDC(hwnd);

// Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%-3d, %-3d)", xPos, yPos);

// Выбираем шрифт с фиксированной шириной букв SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));

// Выводим оконные координаты курсора мыши TextOut(hdc, cxChar, cyChar, szBuf, nSize);

ReleaseDC(hwnd, hdc); break; }

// Двойной щелчок левой клавишей мыши // завершает работу приложения case WM_LBUTTONDBLCLK: case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

При создании окна функция окна получает сообщение WM_CREATE. Обработчик этого сообщения выбирает системный шрифт с фиксированной шириной букв и определяет метрики выбранного шрифта. Метрики шрифта сохраняются для дальнейшего использования.

В самом начале обработчика сообщения WM_NCHITTEST имеется строка, закрытая символом комментария:

// return HTCAPTION;

Если убрать комментарий, обработчик примет следующий вид:

case WM_NCHITTEST: { return HTCAPTION; }

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

Фактически здесь используется объектная ориентированность Windows, позволяющая вам подменить метод, с помощью которого Windows определяет расположение курсора мыши относительно элементов окна.

Как это можно использовать на практике?

Вы, например, можете создать окно, не имеющее заголовка, но которое тем не менее можно перемещать при помощи мыши. Вспомните внешний вид, который можно придать стандартному приложению Windows с именем Clock (рис. 6.2).



Рис. 6.2. Приложение Clock

Несмотря на то что главное окно приложения Clock в данном случае не имеет заголовка, его все же можно перемещать по экрану. Аналогичного эффекта можете добиться и вы, если соответствующим образом обработаете сообщение WM_NCHITTEST.



Если же оставить обработчик в таком виде, как он представлен в нашем примере, после прихода сообщения WM_NCHITTEST в левом верхнем углу окна будут отображены текущие экранные координаты курсора (если курсор находится внутри окна).

Обработчик сообщения WM_MOUSEMOVE тоже отображает текущие координаты курсора мыши (строкой ниже), но только в оконных, а не экранных координатах (рис. 6.3).



Рис. 6.3. Главное окно приложения MOUSENC

Обратите внимание, что работа обработчика сообщения WM_NCHITTEST завершается передачей управления функции DefWindowProc. Если этого не сделать, Windows не сможет правильно определить расположение курсора и, следовательно, не сможет выполнить такие операции, как перемещение окна и изменение размера окна.

Обработчик сообщения WM_LBUTTONDBLCLK завершает работу приложения, вызывая функцию PostQuitMessage.

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

Листинг 6.6. Файл mousenc\mousenc.def

; ============================= ; Файл определения модуля ; ============================= NAME MOUSENC DESCRIPTION 'Приложение MOUSENC, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение MOUSEXY


Приложение MOUSEXY демонстрирует обработку сообщений мыши, имеющих отношение к внутренней области окна.

Задачей приложения является отображение координат курсора мыши в тот момент, когда вы в окне приложения нажимаете левую клавишу курсора. Если нажать правую клавишу мыши, содержимое внутренней области окна должно быть стерто. Обработка двойного щелчка левой клавишей мыши должна сводиться к выводу сообщения (рис. 6.1).

Рис. 6.1. Главное окно приложения MOUSEXY

Функция WinMain приложения MOUSEXY находится в файле mousexy.cpp (листинг 6.1).

Листинг 6.1. Файл mousexy\mousexy.cpp

// ---------------------------------------- // Обработка сообщений от мыши // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "MOUSEXYAppClass"; char const szWindowTitle[] = "MOUSEXY 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 | CS_DBLCLKS;


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 | CS_DBLCLKS;

Функция окна (листинг 6.2) обрабатывает сообщения мыши WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_LBUTTONDBLCLK.

Листинг 6.2. Файл mousexy\wndproc.cpp

// ===================================== // Функция WndProc // =====================================

#define STRICT #include <windows.h>

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc;

switch (msg) { // Нажали левую клавишу мыши case WM_LBUTTONDOWN: { WORD xPos, yPos, nSize; BYTE szBuf[80];

// Сохраняем координаты курсора мыши xPos = LOWORD(lParam); yPos = HIWORD(lParam);

hdc = GetDC(hwnd);

// Подготавливаем текстовую строку, содержащую // координаты курсора мыши nSize = wsprintf(szBuf, "(%d, %d)", xPos, yPos);

// Выводим координаты курсора мыши // в точке, соответствующей положению // курсора мыши TextOut(hdc, xPos, yPos, szBuf, nSize);

ReleaseDC(hwnd, hdc); return 0; }

// Нажали правую клавишу мыши case WM_RBUTTONDOWN: { // Перерисовываем все окно, стирая фон InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; }

// Двойной щелчок левой клавишей мыши case WM_LBUTTONDBLCLK: { // Выдаем сообщение MessageBox(hwnd, "Двойной щелчок!", "MouseXY", MB_OK | MB_ICONINFORMATION); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }



В ответ на сообщение WM_LBUTTONDOWN функция окна сохраняет координаты курсора в переменных xPos и yPos, затем она подготавливает текстовую строку, содержащую координаты курсора и выводит ее в позицию курсора мыши:

nSize = wsprintf(szBuf, "(%d, %d)", xPos, yPos); TextOut(hdc, xPos, yPos, szBuf, nSize);

По сообщению WM_RBUTTONDOWN функция окна перерисовывает окно:

InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);

На сообщение WM_LBUTTONDBLCLK функция окна реагирует выдачей диалоговой панели с сообщением.

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

Листинг 6.3. Файл mousexy\mousexy.def

; ============================= ; Файл определения модуля ; ============================= NAME MOUSEXY DESCRIPTION 'Приложение MOUSEXY, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение OEM2ANSI


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

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

Листинг 5.5. Файл oem2ansi\oem2ansi.cpp

// ---------------------------------------- // Перекодировка текстового файла // из OEM в ANSI // ----------------------------------------

#define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h>

// Прототипы функций HFILE GetSrcFile(void); HFILE GetDstFile(void); void Oem2Ansi(HFILE, HFILE);

// ------------------------------- // Функция WinMain // -------------------------------

#pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Переменные для хранения // идентификаторов входного и выходного // файлов HFILE hfSrc, hfDst;

// Открываем входной файл. // В случае ошибки или отказа от выбора // файла завершаем работу приложения hfSrc = GetSrcFile(); if(!hfSrc) return 0;

// Открываем выходной файл hfDst = GetDstFile(); if(!hfDst) return 0;

// Выполняем перекодировку файла Oem2Ansi(hfSrc, hfDst);

// Закрываем входной и выходной файлы _lclose(hfSrc); _lclose(hfDst);

return 0; }

// ------------------------------- // Функция GetSrcFile // Выбор файла для перекодировки // -------------------------------

HFILE GetSrcFile(void) { // Структура для выбора файла OPENFILENAME ofn;

// Буфер для записи пути к выбранному файлу char szFile[256];

// Буфер для записи имени выбранного файла char szFileTitle[256];

// Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

// Идентификатор открываемого файла HFILE hf;


// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';

// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));

// Инициализируем нужные нам поля

// Размер структуры ofn.lStructSize = sizeof(OPENFILENAME);

// Идентификатор окна ofn.hwndOwner = NULL;

// Адрес строки фильтра ofn.lpstrFilter = szFilter;

// Номер позиции выбора ofn.nFilterIndex = 1;

// Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile;

// Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile);

// Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle;

// Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle);

// В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL;

// Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

// Выбираем входной файл if (GetOpenFileName(&ofn)) {

// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);

// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }

// ------------------------------- // Функция GetDstFile // Выбор файла для записи // результата перекодировки // -------------------------------

HFILE GetDstFile(void) { OPENFILENAME ofn;

char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

HFILE hf;

szFile[0] = '\0';

memset(&ofn, 0, sizeof(OPENFILENAME));

ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY;

// Выбираем выходной файл if (GetSaveFileName(&ofn)) {



// При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; }

// ------------------------------- // Функция Oem2Ansi // Перекодировка файла // -------------------------------

void Oem2Ansi(HFILE hfSrcFile, HFILE hfDstFile) { // Счетчик прочитанных байт int cbRead;

// Буфер для считанных данных BYTE bBuf[2048];

// Читаем в цикле файл и перекодируем его, // записывая результат в другой файл do { // Читаем в буфер 2048 байт из входного файла cbRead = _lread(hfSrcFile, bBuf, 2048);

// Перекодируем содержимое буфера OemToAnsiBuff(bBuf, bBuf, cbRead);

// Сохраняем содержимое буфера в // выходном файле _lwrite(hfDstFile, bBuf, cbRead);

// Завершаем цикл по концу входного файла } while (cbRead != 0); }

Дополнительно к стандартному для всех приложений Windows файлу windows.h в главный файл приложения включен файл commdlg.h, который содержит определения для функций стандартных диалогов Windows версии 3.1. Загрузочные модули этих функций расположены в отдельной библиотеке динамической загрузки (DLL) с именем commdlg.dll.

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

В функции WinMain определены переменные для хранения идентификаторов файлов (file handle), вовлеченных в процесс перекодировки:

HFILE hfSrc, hfDst;

Тип HFILE определен в файле windows.h следующим образом:

typedef int HFILE;

Работа функции WinMain начинается с вызова функции GetSrcFile, определенной в нашем приложении. Эта функция используется для поиска и открытия файла. Функция GetSrcFile возвращает идентификатор файла, подлежащего перекодировке, или ноль, если вы отказались от выбора файла. Полученный идентификатор можно использовать для выполнения операций файлового ввода/вывода.

Затем функция WinMain вызывает функцию GetDstFile, предназначенную для выбора имени и расположения в каталогах диска выходного (перекодированного) файла.


Функция GetDstFile открывает имеющийся файл (если для записи результата перекодировки выбран уже существующий файл) или создает и открывает новый.

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

В заключение функция WinMain закрывает оба файла, вызывая функцию _lclose из программного интерфейса Windows.

Функция GetSrcFile вызывает функцию GetOpenFileName, которая выводит на экран знакомую вам по стандартным приложениям Windows диалоговую панель "Open", позволяющую выбрать файл для перекодировки (рис. 5.6).



Рис. 5.6. Диалоговая панель "Open"

Внешний вид этой диалоговой панели определяется структурой типа OPENFILENAME, определенной в файле commdlg.h (этот файл находится в каталоге include системы разработки Borland C++ или Microsoft Visual C++):

typedef struct tagOFN { DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCSTR lpstrFilter; LPSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPSTR lpstrFile; DWORD nMaxFile; LPSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCSTR lpstrInitialDir; LPCSTR lpstrTitle; DWORD Flags; UINT nFileOffset; UINT nFileExtension; LPCSTR lpstrDefExt; LPARAM lCustData; UINT (CALLBACK *lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; } OPENFILENAME;

Адрес структуры передается функции GetOpenFileName в качестве параметра lpfn:

BOOL WINAPI GetOpenFileName(OPENFILENAME FAR* lpofn);

Функция возвращает ненулевое значение, если вы сделали выбор файла, и ноль, если вы отказались от выбора, нажав кнопку "Cancel" или выбрав строку "Close" из системного меню диалоговой панели. Нулевое значение возвращается также при возникновении ошибки.

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

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



Прежде всего перед вызовом функции GetOpenFileName наше приложение записывает во все поля структуры нулевые значения:

memset(&ofn, 0, sizeof(OPENFILENAME));

Затем в поле lStructSize записывается размер самой структуры в байтах:

ofn.lStructSize = sizeof(OPENFILENAME);

Поле hwndOwner должно содержать идентификатор окна, создавшего диалоговую панель. Так как наше приложение не создает ни одного окна, в качестве идентификатора используется значение NULL, при этом диалоговая панель не имеет окна-владельца (похожим образом мы поступали при вызове функции MessageBox):

ofn.hwndOwner = NULL;

В поле lpstrFilter должен быть указан адрес текстовой строки, задающей фильтр для выбора имен файлов (шаблоны имен файлов):

ofn.lpstrFilter = szFilter;

Наше приложение использует в качестве фильтра такую строку:

char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";

Согласно описанию структуры OPENFILENAME фильтр должен состоять из одной или нескольких расположенных непосредственно друг за другом пар текстовых строк, закрытых двоичным нулем. Последняя строка должна иметь на конце два двоичных нуля.

Первая строка в паре строк описывает название фильтра, например "Text Files" (текстовые файлы), во второй строке пары через символ ";" перечисляются возможные шаблоны для имен файлов.

В нашем приложении определены две пары строк. Одна из них предназначена для выбора только текстовых файлов с расширениями имени *.txt и *.doc, вторая - для выбора любых файлов (с любым расширением имени).

Поле nFilterIndex определяет номер пары строк, используемой для фильтра. В нашем случае этот номер задается как 1, поэтому из двух фильтров выбирается первый, предназначенный для поиска текстовых файлов:

ofn.nFilterIndex = 1;

Поле lpstrFile должно содержать адрес текстовой строки, в которую будет записан полный путь к выбранному файлу:

ofn.lpstrFile = szFile;

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


В нашем приложении эта возможность не используется, поэтому по адресу szFile мы расположили пустую строку, состоящую из одного нуля:

szFile[0] = '\0';

Поле nMaxFile должно содержать размер буфера, расположенного по адресу, указанному в поле lpstrFile:

ofn.nMaxFile = sizeof(szFile);

Размер этого буфера должен быть достаточным для записи полного пути к файлу. Файловая система MS-DOS допускает использование для указания пути к файлу не более 128 символов.

В поле lpstrFileTitle необходимо записать адрес буфера, в который после выбора будет записано имя файла с расширением, но без пути к файлу:

ofn.lpstrFileTitle = szFileTitle;

Это поле должно быть использовано приложением для отображения имени выбранного файла.

Поле nMaxFileTitle должно содержать размер указанного выше буфера:

ofn.nMaxFileTitle = sizeof(szFileTitle);

Поле lpstrInitialDir позволяет указать начальный каталог, который будет выбран для поиска файла сразу после отображения диалоговой панели "Open". Наше приложение начинает поиск в текущем каталоге, поэтому в это поле мы записали значение NULL:

ofn.lpstrInitialDir = NULL;

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

ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

Флаг OFN_PATHMUSTEXIST указывает, что можно выбирать только такие пути, которые соответствуют существующим каталогам. Аналогично флаг OFN_FILEMUSTEXIST определяет, что при выборе можно указывать только существующие файлы. Флаг OFN_HIDEREADONLY убирает из диалоговой панели переключатель, позволяющий открыть файл в режиме "только чтение" (мы не пользуемся этим режимом, так как не собираемся изменять открываемый файл).

После подготовки структуры мы вызываем функцию GetOpenFileName и проверяем возвращаемое ей значение:

if (GetOpenFileName(&ofn)) { hf = _lopen(ofn.lpstrFile, OF_READ); return hf; } else return 0;

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


Этот путь приложение передает функции _lopen, открывающей файл на чтение. Затем функция GetSrcFile возвращает идентификатор открытого файла (точнее, результат, полученный при попытке открыть файл).

При ошибке или отказе от выбора файла функция GetSrcFile возвращает нулевое значение.

Для выбора файла, в который будет записан результат перекодировки, функция WinMain вызывает функцию GetDstFile. Эта функция вызывает функцию GetSaveFileName, которая выводит стандартную диалоговую панель "Save As..." (рис. 5.7).



Рис. 5.7. Диалоговая панель "Save As..."

Диалоговая панель "Save As..." используется многими стандартными приложениями Windows для выбора файла, в который будет записан результат работы приложения.

Функция GetSaveFileName описана в файле commdlg.h:

BOOL WINAPI GetSaveFileName(OPENFILENAME FAR* lpofn);

Она используется аналогично функции GetOpenFileName.

Вначале приложение подготавливает структуру OPENFILENAME:

szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY;

При этом используется тот же фильтр, что и при поиске входного (перекодируемого) файла, но другое значение поля Flags.

Затем приложение вызывает функцию GetSaveFileName и проверяет возвращаемое ей значение:

if ((&ofn)) { hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0;

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

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

Для перекодирования файла функция WinMain вызывает функцию Oem2Ansi.


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

do { cbRead = _lread(hfSrcFile, bBuf, 2048); OemToAnsiBuff(bBuf, bBuf, cbRead); _lwrite(hfDstFile, bBuf, cbRead); } while (cbRead != 0);

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

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

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

Для перекодировки буфера используется знакомая вам функция OemToAnsiBuff. И первый, и второй параметры этой функции указывают на один и тот же буфер, поэтому перекодировка будет выполняться "по месту".

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

На рис. 5.8 представлен результат перекодировки приложением OEM2ANSI текстового файла, подготовленного в MS-DOS и содержащего символы кириллицы.



Рис. 5.8. Исходный и преобразованный файлы

В верхней части рис. 5.8 изображено окно редактирования системы разработки Borland C++ for Windows, в которое загружен текст в кодировке OEM. Этот текст содержит символы кириллицы, которые в кодировке ANSI отображаются в виде различных "нечитаемых" символов. В нижней части рис. 5.8 расположено окно, в которое загружен перекодированный текст.

Если при использовании нашего приложения OEM2ANSI вы получили результаты, отличные от представленного на рис. 5.8, вам следует убедиться в том, что на вашем компьютере была выполнена локализация операционной системы Windows и что она была выполнена правильно.Для локализации Windows можно использовать такие программные средства, как CyrWin или ParaWin. Вы можете также приобрести локализованную версию Windows 3.1, которая поставляется в России фирмой Microsoft A.O.

На базе приложения OEM2ANSI вы без труда сможете создать приложение с названием, например, ANSI2OEM, выполняющее обратную перекодировку. Мы предлагаем вам создать такое приложение самостоятельно.

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

Листинг 5.6. Файл oem2ansi\oem2ansi.def

; ============================= ; Файл определения модуля ; ============================= NAME OEM2ANSI DESCRIPTION 'Приложение OEM2ANSI, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение с обработкой сообщений


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



Приложение 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 не предусмотрено никакого средства для определения количества клавиш, имеющихся на корпусе мыши.


Приложение TEXTOUT


Наше следующее приложение, которое называется TEXTOUT, отличается от предыдущего только использованной функцией окна. Функция окна теперь обрабатывает сообщение WM_PAINT и выводит в окно текст. Однако дополнительно мы полностью изменили структуру исходных текстов приложения и использовали некоторые возможности языка программирования C++.

Приложение TEXTOUT создает два класса с именами WinApp и Window (здесь имеются в виду классы C++, а не классы окон). Первый класс реализует приложение Windows как таковое, второй используется для создания фундаментальных объектов Windows - окон.

Для большего удобства и обозримости все исходные тексты приложения разбиты на несколько файлов.

Главным файлом, который содержит функцию WinMain, является файл textout.cpp (листинг 2.1).

Листинг 2.1. Файл textout/textout.cpp

// ---------------------------------------- // Вывод текста в окно приложения // ---------------------------------------- #define STRICT #include <windows.h> #include "window.hpp" #include "winapp.hpp" #include "textout.hpp" // Имя класса окна char szClassName[] = "TextoutAppClass"; // Заголовок окна char szWindowTitle[] = "Textout Application"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Указатель на объект класса Window - главное // окно приложения Window *PMainWindow; // Создаем объект класса WinApp - наше приложение WinApp App(hInstance, hPrevInstance, lpszCmdLine, nCmdShow); // Регистрируем класс для главного окна приложения App.RegisterWndClass(szClassName, (WNDPROC)WndProc); // Проверяем ошибки, которые могли возникнуть // при регистрации if(App.Error()) return App.Error(); // Создаем объект класса Window - главное // окно приложения PMainWindow = new Window(hInstance, szClassName, szWindowTitle); // Проверяем ошибки, которые могли возникнуть // при создании окна if(PMainWindow->Error()) PMainWindow->Error(); // Отображаем окно PMainWindow->Show(nCmdShow); // Посылаем в окно сообщение WM_PAINT PMainWindow->Update(); // Запускаем цикл обработки сообщений App.Go(); // Завершаем работу приложения return App.Error(); }


Этот файл выглядит значительно проще, чем аналогичный из предыдущего приложения.

Дополнительно к файлу windows.h в файл включаются include-файлы с именами window.hpp, winapp.hpp и textout.hpp. Первые два файла содержат соответственно определения классов Window и WinApp. Файл textout.hpp содержит все необходимые определения для файла textout.cpp.

Как и в предыдущем приложении, массивы с именами szClassName и szWindowTitle используются для хранения имени класса главного окна, создаваемого приложением, и заголовка этого же окна.

В функции WinMain определен указатель на объект класса Window с именем PMainWindow. Этот указатель будет хранить адрес объекта - главного окна приложения.

Далее в функции WinMain определяется объект класса WinApp, имеющий имя App. При создании объекта конструктор класса WinApp получает те же самые параметры, что и функция WinMain при старте приложения:

WinApp App(hInstance, hPrevInstance, lpszCmdLine, nCmdShow);

После этого выполняется регистрация класса окна, для чего вызывается функция-метод с именем RegisterWndClass, определенная в классе WinApp. В качестве параметров функции передаются указатель на имя класса szClassName и адрес функции окна WndProc.

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

После проверки ошибок, возможных на этапе регистрации класса окна, функция WinMain создает главное окно приложения, являющееся объектом класса Window:

PMainWindow = new Window(hInstance, szClassName, szWindowTitle);

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

После проверки ошибок функция WinMain вызывает методы Show и Update из класса Window. Метод Show отображает окно, вызывая уже известную вам функцию ShowWindow.


Метод Update посылает в функцию окна сообщение WM_PAINT, вызывая функцию UpdateWindow, которая вам также знакома.

Далее вызывается функция-метод Go, определенная в классе WinApp, которая запускает цикл обработки сообщений.

После завершения цикла обработки сообщения функция WinMain возвращает управление Windows, завершая работу приложения.

Include-файл textout.hpp (листинг 2.2) содержит прототип функции окна WndProc, необходимый при создании класса окна.

Листинг 2.2. Файл textout\textout.hpp

// Прототип функции WndProc LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

Класс WinApp определяется в файле winapp.hpp (листинг 2.3).

Листинг 2.3. Файл textout\winapp.cpp

// ===================================== // Определение класса WinApp // ===================================== class WinApp { MSG msg; // структура для работы с сообщениями int errno; // флаг ошибки HINSTANCE hInstance; // идентификатор приложения

public: // Конструктор WinApp(HINSTANCE, HINSTANCE, LPSTR, int); // Регистрация класса окна BOOL RegisterWndClass(LPSTR, WNDPROC); // Запуск цикла обработки сообщений WORD Go(void); // Проверка флага ошибок int Error(void) { return errno; } };

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

Кроме конструктора в классе WinApp определены методы RegisterWndClass (регистрация класса окна), Go (запуск цикла обработки сообщений) и Error (проверка флага ошибок). Процедуры регистрации класса окна и цикл обработки сообщения ничем не отличаются от использованных в предыдущем приложении.

Исходные тексты функций-методов класса WinApp приведены в листинге 2.4.

Листинг 2.4. Файл textout\winapp.cpp

// ===================================== // Функции-методы для класса WinApp // ===================================== #define STRICT #include <windows.h> #include "winapp.hpp" // ------------------------------------- // Конструктор класса Window // ------------------------------------- #pragma argsused WinApp::WinApp(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Сбрасываем флаг ошибки errno = 0; // Сохраняем идентификатор приложения hInstance = hInst; } // ------------------------------------- // Регистрация класса окна // ------------------------------------- BOOL WinApp::RegisterWndClass(LPSTR szClassName, WNDPROC WndProc) { ATOM aWndClass; // атом для кода возврата WNDCLASS 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); } // ------------------------------------- // Запуск цикла обработки сообщений // ------------------------------------- WORD WinApp::Go(void) { // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }



Конструктор класса WinApp сбрасывает флаг ошибки и сохраняет в переменной hInstance идентификатор текущей копии приложения, переданный ему функцией WinMain.

Функция-метод RegisterWndClass регистрирует класс окна, используя для этого указатель на имя класса и указатель на функцию окна. Регистрация выполняется, как и в предыдущем приложении, при помощи функции RegisterClass.

Функция-метод Go запускает цикл обработки сообщений, полностью идентичный использованному в предыдущем приложении.

Так как приложение может создавать много окно, удобно определить в качестве класса окно. Файл window.hpp содержит определение класса Window, который предназначен для создания окон (листинг 2.5).

Листинг 2.5. Файл textout\window.hpp

// ===================================== // Определение класса Window // ===================================== class Window { HWND hwnd; // Идентификатор окна int errno; // Флаг ошибки public: // Конструктор Window(HINSTANCE, LPSTR, LPSTR); // Проверка флага ошибки int Error(void) { return errno; } // Отображение окна void Show(int nCmdShow) { ShowWindow(hwnd, nCmdShow); } // Передача функции WndProc сообщения WM_PAINT void Update(void) { UpdateWindow(hwnd); } };

В классе Window определена переменная типа HWND для хранения идентификатора окна и флаг ошибок errno. Там же определены конструктор, функции-методы Show (отображение окна), Update (передача в функцию окна сообщения WM_PAINT) и Error (проверка флага ошибок).

Исходный текст конструктора класса Window находится в файле window.cpp (листинг 2.6).

Листинг 2.6. Файл textout\window.cpp

// ===================================== // Функции-методы для класса Window // ===================================== #define STRICT #include <windows.h> #include "window.hpp" // ------------------------------------- // Конструктор класса Window // ------------------------------------- Window::Window(HINSTANCE hInstance, LPSTR szClassName, LPSTR szWindowTitle) { // Сбрасываем флаг ошибки errno = 0; // Создаем окно hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если при создании окна были ошибки, // устанавливаем флаг ошибки if(!hwnd) { errno = 1; return; } }



Конструктор класса Window сбрасывает флаг ошибок и создает окно, вызывая для этого известную вам функцию CreateWindow. Если при создании окна были ошибки, устанавливается флаг ошибок.

До настоящего момента в приложении TEXOUT вам не встречалось ничего нового по сравнению с предыдущим приложением, за исключением того, что мы подготовили его для объектно-ориентированного программирования. Однако нашей задачей было научиться выводить в окно текст.

Как мы и говорили, эта операция выполняется функцией окна (листинг 2.7).

Листинг 2.7. Файл textout\winproc.cpp

// ===================================== // Функция WndProc // Функция выполняет обработку сообщений главного // окна приложения // ===================================== #define STRICT #include <windows.h> LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования switch (msg) { case WM_PAINT: { // Получаем индекс контекста устройства hdc = BeginPaint(hwnd, &ps); // Выводим текстовую строку TextOut(hdc, 10, 20, "Сообщение WM_PAINT", 18); // Отдаем индекс контекста устройства EndPaint(hwnd, &ps); return 0; } case WM_LBUTTONDOWN: { // Получаем индекс контекста устройства hdc = GetDC(hwnd); // Выводим текстовую строку TextOut(hdc, 10, 40, "Сообщение WM_LBUTTONDOWN", 24); // Отдаем индекс контекста устройства ReleaseDC(hwnd, hdc); return 0; } case WM_RBUTTONDOWN: { hdc = GetDC(hwnd); TextOut(hdc, 10, 60, "Сообщение WM_RBUTTONDOWN", 24); ReleaseDC(hwnd, hdc); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

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

Последний файл, входящий в проект приложения, - это файл определения модуля с именем textout.def (листинг 2.8), который почти полностью повторяет аналогичный файл из предыдущего приложения.

Листинг 2.8. Файл textout\textout.def

; ============================= ; Файл определения модуля ; ============================= NAME TEXTOUT DESCRIPTION 'Приложение TEXTOUT, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение TIMBEEP


Приложение TIMBEEP демонстрирует простейший случай использования таймера.

Главный файл приложения приведен в листинге 7.1.

Листинг 7.1. Файл timbeep\timbeep.cpp

// ---------------------------------------- // Обработка сообщений от таймера // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "TIMBEEPAppClass"; char const szWindowTitle[] = "TIMBEEP 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 = 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); }

Функция WinMain создает главное окно приложения. Функция главного окна (листинг 7.2) будет получать приблизительно раз в секунду сообщение с кодом WM_TIMER.


/Листинг 7.2. Файл timbeep\wndproc.cpp

/ ===================================== // Функция WndProc // =====================================

#define STRICT #include <windows.h>

#define BEEP_TIMER 1

LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

switch (msg) { case WM_CREATE: { // Создаем таймер, посылающий сообщения // функции окна примерно раз в секунду SetTimer(hwnd, BEEP_TIMER, 1000, NULL); return 0; }

case WM_TIMER: { // В ответ на сообщение таймера выдаем // звуковой сигнал MessageBeep(-1); return 0; }

case WM_DESTROY: { // Перед уничтожением окна уничтожаем // созданный ранее таймер KillTimer(hwnd, BEEP_TIMER);

PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

По сообщению WM_CREATE (оно приходит при создании окна) функция окна создает таймер:

SetTimer(hwnd, BEEP_TIMER, 1000, NULL);

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

Обработка сообщения таймера сводится к простой выдаче звукового сигнала.

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

KillTimer(hwnd, BEEP_TIMER);

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

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

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

Листинг 7.3. Файл timbeep\timbeep.def

; ============================= ; Файл определения модуля ; ============================= NAME TIMBEEP DESCRIPTION 'Приложение TIMBEEP, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Приложение 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


Приложение WSTYLE


Для того чтобы вы смогли проводить эксперименты со стилями окон и классов, мы подготовили приложение WSTYLE (листинг 3.1). В этом приложении создается одно главное окно, одно перекрывающееся (этим окном владеет главное), одно временное (также принадлежащее главному окну) и одно дочернее окно.

Листинг 3.1. Файл wstyle\wstyle.cpp

// ---------------------------------------- // Демонстрация стилей окна // ----------------------------------------

#define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h>

// Прототипы функций

BOOL Register(HINSTANCE); LRESULT CALLBACK _export MainWndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK _export ChildWndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK _export PopUpWndProc(HWND, UINT, WPARAM, LPARAM);

// Имя класса для главного окна приложения char const szMainClassName[] = "WStyleAppClass";

// Имя класса для дочерних окон char const szChildClassName[] = "WStyleAppChildClass";

// Имя класса для временных окон char const szPopUpClassName[] = "WStyleAppPopUpClass";

// Заголовок главного окна приложения char const szMainWindowTitle[] = "WStyle Application";

// Заголовок дочернего окна char const szChildWindowTitle[] = "Окно Child";

// Заголовок временного окна char const szPopUpWindowTitle[] = "Окно PopUp";

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND MainHwnd; // идентификатор главного окна приложения HWND ChildHwnd; // идентификатор дочернего окна HWND PopUpHwnd; // идентификатор временного окна HWND OwnedHwnd; // идентификатор окна, которым владеет // главное окно приложения

// Регистрируем классы окон if(!Register(hInstance)) return FALSE;

// Создаем главное окно приложения MainHwnd = CreateWindow( szMainClassName, // имя класса окна szMainWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры


// Если создать окно не удалось, завершаем приложение if(!MainHwnd) return FALSE;

// Делаем окно видимым ShowWindow(MainHwnd, nCmdShow);

// Посылаем в окно сообщение WM_PAINT UpdateWindow(MainHwnd);

// Создаем перекрывающееся окно, которое // принадлежит главному окну приложения OwnedHwnd = CreateWindow( szMainClassName, // имя класса окна "Перекрывающееся окно", // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна 20, // задаем размеры и расположение 200, // окна 300, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

// Если создать окно не удалось, завершаем приложение if(!OwnedHwnd) return FALSE;

// Делаем окно видимым ShowWindow(OwnedHwnd, nCmdShow);

// Посылаем в окно сообщение WM_PAINT UpdateWindow(OwnedHwnd);

// Создаем дочернее окно ChildHwnd = CreateWindow( szChildClassName, // имя класса окна "Дочернее окно", // заголовок окна WS_CHILDWINDOW | WS_VISIBLE | WS_CAPTION, // стиль окна 300, // задаем размеры и расположение 20, // окна 200, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

// Если создать окно не удалось, завершаем приложение if(!ChildHwnd) return FALSE;

// Создаем временное окно PopUpHwnd = CreateWindow( szPopUpClassName, // имя класса окна "Временное окно", // заголовок окна WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE, // стиль окна 100, // задаем размеры и расположение 100, // окна 200, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

// Если создать окно не удалось, завершаем приложение if(!PopUpHwnd) return FALSE;

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция Register // Выполняет регистрацию классов окна, // используемых приложением // =====================================



BOOL Register(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc));

// -------------------------------------------------- // Регистрируем класс окна для главного // и перекрывающегося окна // -------------------------------------------------- wc.style = 0; wc.lpfnWndProc = (WNDPROC) MainWndProc; 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)szMainClassName;

aWndClass = RegisterClass(&wc); if(aWndClass == 0) return FALSE;

// -------------------------------------------------- // Регистрируем класс окна для // дочернего окна // --------------------------------------------------

wc.lpfnWndProc = (WNDPROC) ChildWndProc; wc.hCursor = LoadCursor(NULL, IDC_SIZE); wc.hbrBackground = GetStockBrush(GRAY_BRUSH); wc.lpszClassName = (LPSTR)szChildClassName;

aWndClass = RegisterClass(&wc); if(aWndClass == 0) return FALSE;

// -------------------------------------------------- // Регистрируем класс окна для // временного окна // --------------------------------------------------

wc.lpfnWndProc = (WNDPROC) PopUpWndProc; wc.hCursor = LoadCursor(NULL, IDC_CROSS); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szPopUpClassName;

aWndClass = RegisterClass(&wc); if(aWndClass == 0) return FALSE;

return TRUE; }

// ===================================== // Функция MainWndProc // Используется главным окном приложения // и созданным дополнительно // перекрывающимся окном // =====================================

LRESULT CALLBACK _export MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования

switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 20, "WM_PAINT", 8); EndPaint(hwnd, &ps); return 0; }



case WM_LBUTTONDOWN: { hdc = GetDC(hwnd); TextOut(hdc, 10, 40, "WM_LBUTTONDOWN", 14); ReleaseDC(hwnd, hdc); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ===================================== // Функция ChildWndProc // Используется дочерним окном // =====================================

LRESULT CALLBACK _export ChildWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования

switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 20, "WM_PAINT", 8); EndPaint(hwnd, &ps); return 0; }

case WM_LBUTTONDOWN: { hdc = GetDC(hwnd); TextOut(hdc, 10, 40, "WM_LBUTTONDOWN", 14); ReleaseDC(hwnd, hdc); return 0; }

case WM_RBUTTONDOWN: { MessageBeep(-1); // звуковой сигнал return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ===================================== // Функция PopUpWndProc // Используется временным окном // =====================================

LRESULT CALLBACK _export PopUpWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования

switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps); TextOut(hdc, 10, 20, "WM_PAINT", 8); EndPaint(hwnd, &ps); return 0; }

case WM_LBUTTONDOWN: { hdc = GetDC(hwnd); TextOut(hdc, 10, 40, "WM_LBUTTONDOWN", 14); ReleaseDC(hwnd, hdc); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

Приложение регистрирует три класса окна со следующими именами:

char const szMainClassName[] = "WStyleAppClass"; char const szChildClassName[] = "WStyleAppChildClass"; char const szPopUpClassName[] = "WStyleAppPopUpClass";

Первый класс с именем, записанным в массиве szMainClassName, используется для создания главного окна приложения и одного дополнительного перекрывающегося окна.



Класс с именем, записанным в массиве szChildClassName, используется для создания дочернего окна, а класс с именем, записанным в массиве szPopUpClassName, - для создания временного окна.

Для каждого класса окон в приложении используется отдельная функция окна. Главное окно и дополнительное временное окно работают с функцией MainWndProc, для дочернего окна используется функция окна с именем ChildWndProc, а для временного - PopUpWndProc.

Для каждого окна определен свой заголовок:

char const szMainWindowTitle[] = "WStyle Application"; char const szChildWindowTitle[] = "Окно Child"; char const szPopUpWindowTitle[] = "Окно PopUp";

Так как приложение создает четыре различных окна, в функции WinMain определены четыре переменные, предназначенные для хранения идентификаторов окон. Эти переменные имеют имена MainHwnd, ChildHwnd, PopUpHwnd и OwnedHwnd.

Переменная MainHwnd используется для хранения идентификатора главного окна приложения. Переменные ChildHwnd и PopUpHwnd предназначены соответственно для хранения идентификаторов дочернего и временного окна. В переменной OwnedHwnd хранится идентификатор дополнительного перекрывающегося окна, принадлежащего главному окну приложения.

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

Регистрация класса для главного окна приложения не имеет никаких особенностей по сравнению с аналогичной операцией, выполняемой нашими предыдущими приложениями:

wc.style = 0; wc.lpfnWndProc = (WNDPROC) MainWndProc; 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)szMainClassName; aWndClass = RegisterClass(&wc); if(aWndClass == 0) return FALSE;

При регистрации класса для дочернего окна для упрощения программы используется проинициализированная ранее структура wc.


В этой структуре необходимо заменить указатель на функцию окна (дочернее окно работает с функцией ChildWndProc) и имя класса (поле lpszClassName). Дополнительно для дочернего окна мы изменили форму курсора мыши и цвет фона:

wc.lpfnWndProc = (WNDPROC) ChildWndProc; wc.hCursor = LoadCursor(NULL, IDC_SIZE); wc.hbrBackground = GetStockBrush(GRAY_BRUSH); wc.lpszClassName = (LPSTR)szChildClassName;

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

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

wc.lpfnWndProc = (WNDPROC) PopUpWndProc; wc.hCursor = LoadCursor(NULL, IDC_CROSS); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szPopUpClassName;

В случае успешной регистрации всех классов функция Register возвращает значение TRUE.

После регистрации классов окна в функции WinMain создаются четыре окна.

Первым создается главное окно приложения:

MainHwnd = CreateWindow( szMainClassName, // имя класса окна szMainWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

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

Для того чтобы сделать окно видимым и нарисовать его внутреннюю область, вызываются функции ShowWindow и UpdateWindow.

После главного окна создается перекрывающееся окно.


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

Для перекрывающегося окна мы определяем свой заголовок, стиль WS_OVERLAPPEDWINDOW, явно указываем координаты левого верхнего угла окна (20, 200), размеры окна (300, 100), а также (вместо идентификатора родительского окна) идентификатор окна-владельца:

OwnedHwnd = CreateWindow( szMainClassName, // имя класса окна "Перекрывающееся окно", // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна 20, // задаем размеры и расположение 200, // окна 300, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

Для того чтобы сделать перекрывающееся окно видимым и нарисовать его внутреннюю область, вызываются функции ShowWindow и UpdateWindow. В качестве параметра им передается идентификатор перекрывающегося окна OwnedHwnd.

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

Далее приложение создает дочернее окно:

ChildHwnd = CreateWindow( szChildClassName, // имя класса окна "Дочернее окно", // заголовок окна WS_CHILDWINDOW | WS_VISIBLE | WS_CAPTION, // стиль окна 300, // задаем размеры и расположение 20, // окна 200, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

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


Если указана константа WS_VISIBLE, окно создается сразу видимым, поэтому у нас нет необходимости для отображения дочернего окна вызывать функцию ShowWindow.

Для дочернего окна указан идентификатор родительского окна, в качестве которого выступает главное окно приложения.

Временное окно создается аналогично дочернему:

PopUpHwnd = CreateWindow( szPopUpClassName, // имя класса окна "Временное окно", // заголовок окна WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE, // стиль окна 100, // задаем размеры и расположение 100, // окна 200, 100, MainHwnd, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

Для стиля временного окна используется комбинация констант WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE. При этом создается временное окно, которое имеет строку заголовка и становится видимым сразу после его создания функцией CreateWindow.

После создания всех окон запускается цикл обработки сообщений.

Окна приложения WSTYLE показаны на рис. 3.1.



Рис. 3.1. Окна приложения WSTYLE

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

Функция MainWndProc обрабатывает сообщения WM_PAINT, WM_LBUTTONDOWN и WM_DESTROY.

По сообщению WM_PAINT в окно выводится строка "WM_PAINT". Если в функцию окна приходит сообщение WM_LBUTTONDOWN, в окно выводится строка "WM_LBUTTONDOWN". Заметьте, что строка "WM_LBUTTONDOWN" выводится в то окно, в котором вы нажали на левую клавишу мыши. Если в функцию окна приходит сообщение WM_DESTROY, вызывается функция PostQuitMessage и приложение завершает свою работу.

Функция окна ChildWndProc используется для обработки сообщений, поступающих в дочернее окно. Эта функция также обрабатывает сообщения WM_PAINT и WM_LBUTTONDOWN, выводя соответствующие строки в дочернее окно. Дополнительно функция дочернего окна обрабатывает сообщение WM_RBUTTONDOWN. Если приходит это сообщение, вызывается функция MessageBeep, которая выдает звуковой сигнал.



Функция временного окна PopUpWndProc аналогична функции ChildWndProc, но не обрабатывает сообщение WM_RBUTTONDOWN.

Таким образом, два окна приложения (главное и перекрывающееся) используют одну общую функцию окна MainWndProc. Дочернее окно и временное окно используют свои собственные функции окна.

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

Файл определения модуля, использованный приложением WSTYLE, приведен в листинге 3.2.

Листинг 3.2. Файл wstyle\wstyle.def

; ============================= ; Файл определения модуля ; ============================= NAME WSTYLE DESCRIPTION 'Приложение WSTYLE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Программа "Hello, world!" для Windows


Давайте попробуем создать для Windows вариант известной всем программы, приведенной в книге Кернигана и Риччи, посвященной программированию на языке C:

main() { printf("Hello, world!"); }

Задачей этой программы, как следует из исходного текста, является вывод на экран строки "Hello, world!".

Как мы уже говорили, вместо функции main приложение Windows использует функцию WinMain, причем необходимо указать все четыре параметра этой функции.

К сожалению, вы не сможете воспользоваться функцией printf, так как ни эта, ни другие аналогичные функции консольного ввода/вывода в обычных приложениях Windows использовать нельзя. Для вывода текстовой строки "Hello, world!" мы воспользуемся функцией из программного интерфейса Windows с именем MessageBox.

Создайте на диске каталог с именем hello и скопируйте в него файлы hello.cpp и hello.prj из одноименного каталога, расположенного на дискете, которую вы купили вместе с книгой. Если вы приобрели книгу без дискеты, воспользуйтесь исходным текстом программы, приведенным в листинге 1.1.

Листинг 1.1. Файл hello\hello.cpp

// ---------------------------------------- // Простейшее приложение Windows // "Hello, world!" // ---------------------------------------- #define STRICT #include <windows.h> #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MessageBox(NULL, "Hello, world!", "Main Window", MB_OK); return 0; }

Первой строкой в программе является определение символа STRICT:

#define STRICT

Это определение влияет на обработку файла windows.h, обеспечивая более строгую проверку типов данных. Такая проверка облегчит вам в дальнейшем преобразование исходных текстов программ для 32-разрядных приложений Win32s или Windows NT. И хотя в нашем простом примере проверять почти нечего, мы включили определение STRICT для сохранения единого стиля во всех примерах программ.

Следующая строка сообщает компилятору, что не нужно выводить предупреждающее сообщение о том, что расположенная следом функция не пользуется своими параметрами:


#pragma argsused

В нашем первом примере мы игнорировали все четыре параметра, однако из-за использования соглашения о передаче параметров PASCAL все параметры функции WinMain должны быть описаны.

Для вывода строки "Hello, world!" мы использовали функцию MessageBox:

MessageBox(NULL, "Hello, world!", "Main Window", MB_OK);

Прототип функции MessageBox определен в файле windows.h:

int WINAPI MessageBox(HWND, LPCSTR, LPCSTR, UINT);

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

int WINAPI MessageBox(HWND hwndParent, LPCSTR lpszText, LPCSTR lpszTitle, UINT fuStyle);

Не вдаваясь в подробности, скажем, что эта функция создает на экране диалоговую панель с текстом, заданным вторым параметром lpszText (в нашем случае - с текстом "Hello, world!"), и заголовком, заданным третьим параметром lpszTitle ("Main Window").

Параметр hwndParent указывает так называемый идентификатор родительского окна, создающего диалоговую панель (его мы рассмотрим позже). Этот параметр можно указывать как NULL, в этом случае у диалоговой панели не будет родительского окна.

Первый параметр в нашем примере необходимо указать как NULL.

Последний параметр fuStyle - константа MB_OK, значение которой определено в файле windows.h. Использование в качестве последнего параметра значения MB_OK приводит к появлению в диалоговой панели одной кнопки с надписью "OK". Когда вы нажмете на эту кнопку, функция MessageBox возвратит управление в функцию WinMain.

Обратите внимание на то, что имена параметров функции MessageBox имеют префиксы. Эти префиксы используются для того, чтобы включить в имя параметра информацию о его типе. При создании приложений Windows приходится использовать очень много типов данных. Префиксы позволяют избежать ошибок, связанных с неправильным использованием параметров.


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

Для завершения работы приложение использует функцию return:

return 0;

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

Теперь займемся созданием приложения, для чего воспользуемся интегрированной средой разработки Borland C++ for Windows версии 3.1 или Borland Turbo C++ for Windows.

Для создания приложения прежде всего вам нужно образовать новый prj-файл. Запустите среду разработки и из меню "Project" выберите строку "Open Project...". На экране появится диалоговая панель "Open Project File" (рис. 1.1).



Рис. 1.1. Диалоговая панель "Open Project File"

С помощью меню "Directories" выберите и сделайте текущим созданный вами каталог с именем hello. Если в этом каталоге уже имеется файл hello.prj, выберите его и нажмите кнопку "OK". Если файла нет (вы не купили дискету с примерами программ), наберите в поле "File Name" имя hello.prj и нажмите кнопку "OK". В этом случае будет создан новый файл проекта.

При создании нового проекта в нижней части основного окна Borland C++ появится окно "Project:hello" (рис. 1.2).



Рис. 1.2. Окно "Project: hello"

В этом окне отображается список файлов, входящих в проект hello.prj. При создании нового проекта этот список пуст. Нам надо добавить в проект файл с именем hello.cpp. Для добавления файла нажмите клавишу <Insert>. На экране появится диалоговая панель "Add To Project List" (рис. 1.3), с помощью которой можно добавить к проекту файл с программой, объектным модулем, библиотекой объектных модулей и т. п.



Рис. 1.3. Диалоговая панель "Add To Project List"

Выберите при помощи меню или наберите в поле "File Name" имя hello.cpp, затем нажмите кнопку "Add". В списке файлов проекта появится имя добавленного вами файла.


Так как проект нашего первого приложения состоит из одного файла, после добавления файла hello.cpp нажмите кнопку "Done".

Для того чтобы открыть окно редактирования файла сделайте в окне "Project:hello" двойной щелчок левой клавишей мыши по имени файла (установите курсор мыши на имя файла и нажмите с небольшим интервалом два раза левую клавишу мыши), в нашем случае по имени hello.cpp. В главном окне появится окно редактирования (рис. 1.4).



Рис. 1.4. Окно редактирования

Если вы добавили к проекту пустой файл hello.cpp и затем открыли его двойным щелчком по имени файла, окно редактирования не будет содержать никакого текста. Наберите в нем текст программы, приведенный в листинге 1.1.

Учтите, что транслятор Borland C++ версии 3.1 для отображения текста программы использует шрифт BorlandTE, в котором нет русских букв. Если вы будете работать с русскими буквами, замените этот шрифт на другой, например Courier Cyrillic. Для этого выберите в меню "Options" строку "Environment". Затем в появившемся подменю выберите строку "Preferences". На экране появится диалоговая панель "Preferences" (рис. 1.5).



Рис. 1.5. Диалоговая панель "Preferences"

В этой диалоговой панели с помощью меню "Font" вы можете выбрать шрифт для окна редактирования. Мы рекомендуем вам также в группе переключателей "Auto Save" включить все переключатели (как это показано на рис 1.5). После внесения всех изменений нажмите кнопку "OK".

Кроме этого в меню "Options" выберите строку "Application..." и в появившейся диалоговой панели нажмите мышью пиктограмму с надписью "Windows App" и затем кнопку "OK". При этом транслятор будет настроен на создание обычных приложений Windows.

Для сохранения внесенных изменений выберите в меню "Options" строку "Save..." и в появившейся диалоговой панели нажмите кнопку "OK", предварительно убедившись, что все три имеющихся там переключателя находятся во включенном состоянии (отмечены галочкой).



Подготовив файл hello.cpp, выберите из меню "Compile" строку "Build all". На экране появится диалоговая панель "Compile Status", в которой будет отображаться ход трансляции файлов проекта и сборки файла загрузочного модуля (рис. 1.6)



Рис. 1.6. Диалоговая панель "Compile Status"

Вам надо следить за сообщениями об ошибках (Errors) и предупреждениями (Warnings). Если вы не допустили ошибок при наборе содержимого файла hello.cpp, после редактирования вы должны получить только одно предупреждающее сообщение:

Linker Warning: No module definition file specified: using defaults

Это сообщение говорит о том, что в проект не включен так называемый файл определения модуля (module definition file) и поэтому используется файл, принятый по умолчанию. Для простоты мы сознательно не стали включать в проект этот файл, но проекты всех наших следующих приложений будут содержать файл определения модуля.

После завершения процесса редактирования в поле "Status" диалоговой панели "Compile Status" появится слово Success (успех). Для продолжения работы вы должны нажать кнопку "OK".

Теперь попробуем запустить созданное нами приложение. Для этого из меню "Run" выберите строку "Run". После проверки файлов проекта на экране появится диалоговая панель, озаглавленная "Main Window" (рис. 1.7).



Рис. 1.7. Диалоговая панель "Main Window"

Она содержит текстовую строку "Hello, world!", прочитав которую, вы можете нажать кнопку "OK". Это приведет к завершению работы нашего первого приложения.

Не следует думать, что все приложения Windows так же просты, как это. Мы еще не затронули основного в Windows - окон и сообщений! Однако теперь вы умеете создавать приложения Windows, что и было нашей основной задачей на данном этапе.


Простейшее приложение Windows


В этом разделе мы создадим простейшее приложение Windows. Оно будет мало напоминать "настоящие" приложения, которые поставляются вместе с Windows, но на данном этапе наша основная задача - научиться создавать файл загрузочного модуля приложения Windows с использованием системы разработки Borland C++ версии 3.1.



Регистрация класса окна


Задача функции InitApp - регистрация класса окна. На базе этого класса будет создано главное окно приложения.

Если допускается одновременная работа нескольких копий одного приложения, регистрация класса окна должна выполняться только один раз первой копией приложения.

В области локальных переменных функции определены две переменные - aWndClass и wc:

ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

Переменная aWndClass используется для временного хранения кода возврата функции RegisterClass. Эта функция относится к функциям программного интерфейса Windows, она и выполняет регистрацию класса. В качестве единственного параметра функции необходимо указать адрес соответствующим образом подготовленной структуры типа WNDCLASS:

aWndClass = RegisterClass(&wc);

Приведем прототип функции RegisterClass:

ATOM WINAPI RegisterClass(const WNDCLASS FAR* lpwc);

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

Для вас, возможно, непривычно использование переменной специального типа ATOM для передачи результата выполнения функции. Однако такое использование не создает никаких дополнительных трудностей. Тип ATOM отображается на тип UINT, который, в свою очередь, отображается на тип unsigned int (см. файл windows.h):

typedef UINT ATOM; typedef unsigned int UINT;

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

В нашем приложении функция InitApp использует переменную типа ATOM для формирования кода возврата:

return (aWndClass != 0);

Если регистрация класса произошла успешно, функция RegisterClass возвращает атом с ненулевым значением, при этом функция InitApp возвращает значение TRUE.
Последнее означает, что инициализация приложения выполнена без ошибок.

Теперь займемся структурой WNDCLASS, которая используется для регистрации класса окна. Эта структура определена в файле windows.h:

typedef struct tagWNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; } WNDCLASS;

Перед регистрацией вам необходимо заполнить все поля в этой структуре.

Поле style определяет стиль класса и задается в виде констант (описанных, как всегда, в файле windows.h), имя которых начинается с префикса CS_, например CS_HREDRAW, CS_VREDRAW. Стиль задает реакцию окна на изменение его размера, на выполнение в окне операции двойного щелчка мышью (double click), а также позволяет определить другие характеристики окна, создаваемого на базе данного класса. Например, если для стиля задать значение CS_HREDRAW | CS_VREDRAW, при изменении вертикального или горизонтального размера окна приложение должно его перерисовать, то есть нарисовать заново все или часть того, что было изображено в окне до изменения размера.

В нашем приложении стиль класса не используется, поэтому для него задается нулевое значение:

wc.style = 0;

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

wc.lpfnWndProc = (WNDPROC) WndProc;

Поле lpfnWndProc имеет тип WNDPROC (дальний указатель на функцию), который мы рассмотрим чуть позже, при описании функции окна. Для того чтобы избежать получения от компилятора предупреждающего сообщения о несоответствии типов, вы должны использовать явное преобразование типа.

Поле cbClsExtra используется для резервирования дополнительной памяти, общей и доступной для всех окон, создаваемых на базе данного класса.



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

Наше приложение не создает в описании класса никаких дополнительных областей, поэтому для заполнения поля cbClsExtra используется нулевое значение:

wc.cbClsExtra = 0;

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

wc.cbWndExtra = 0;

Поле hInstance перед регистрацией класса окна должно содержать идентификатор приложения, создающего класс окна. В качестве этого идентификатора следует использовать значение, полученное функцией WinMain в параметре hInstance:

wc.hInstance = hInstance;

Следующее поле имеет имя hIcon и тип HICON. Это идентификатор пиктограммы, в которую превращается окно при уменьшении его размеров до предела (при минимизации окна).

В нашем приложении мы указываем пиктограмму, используемую Windows по умолчанию. Для Microsoft Windows версии 3.1 вид этой пиктограммы приведен на рис. 1.11.



Рис. 1.11. Пиктограмма приложения

Для загрузки пиктограммы в приложении вызывается функция программного интерфейса Windows с именем LoadIcon:

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

Прототип функции LoadIcon:



HICON WINAPI LoadIcon(HINSTANCE hinst, LPCSTR pszicon);

Первый параметр функции (hinst) содержит идентификатор приложения, второй (pszicon) - имя ресурса-пиктограммы.

Позже мы научим вас определять для окон собственные пиктограммы, нарисованные с помощью приложения Resource Workshop, входящего в комплект поставки Borland C++ for Windows.

В поле hCursor (имеющем тип HCURSOR) вы можете задать вид курсора мыши при его прохождении над окном. Вы знаете, что курсор мыши меняет свою форму при перемещении над различными окнами приложений Windows. При регистрации класса окна вы можете указать форму курсора, для чего и используется поле hCursor.

В нашем приложении мы задаем курсор в виде стандартной стрелки, для чего вызываем функцию LoadCursor и указываем в качестве второго параметра константу IDC_ARROW:

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

Прототип функции LoadCursor:

HCURSOR WINAPI LoadCursor(HINSTANCE hinst, LPCSTR pszCursor);

Вы можете определить для окна свой курсор, нарисовав его аналогично пиктограмме при помощи такого приложения, как Resource Workshop или Microsoft SDKPaint. Однако пока для простоты мы будем использовать стандартный курсор.

Далее нам необходимо заполнить поле hbrBackground, имеющее тип HBRUSH. Это поле позволяет определить кисть (brush), которая будет использована для закрашивания фона окна. В качестве кисти можно использовать "чистые" цвета или небольшую пиктограмму размером 8 х 8 точек.

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

wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

Системный цвет можно изменять при помощи приложения Control Panel. Позже мы научим вас задавать для фона окна другие цвета и раскрашивать окно при помощи пиктограмм.

Поле lpszMenuName (указатель на строку типа LPCSTR) определяет меню, располагающееся в верхней части окна. Если меню не используется, при заполнении этого поля необходимо использовать значение NULL:

wc.lpszMenuName = (LPSTR)NULL;

Тип LPCSTR определяется как константный дальний указатель на строку символов:

typedef const char FAR* LPCSTR;

Очень важно поле lpszClassName. В это поле необходимо записать указатель на текстовую строку, содержащую имя регистрируемого класса окон:

wc.lpszClassName = (LPSTR)szClassName;

На этом подготовку структуры wc к регистрации класса окна можно считать законченной. Можно вызывать функцию RegisterClass.

После регистрации функция InitApp возвращает управление обратно в функцию WinMain.


Ресурсы


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

Для создания ресурсов используются специальные программы, которые называются редакторами ресурсов. Такие программы поставляются в комплекте с компилятором. Они позволяют редактировать ресурсы без изменения кода, и в этом смысле можно говорить об относительной независимости ресурсов от программного кода приложения.

Приложение Windows при необходимости может загрузить ресурс в оперативную память и использовать его, например, для вывода на экран.

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

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

Ресурсы могут загружаться в оперативную память сразу после запуска приложения или при необходимости. В последнем случае после запуска приложения ресурсы хранятся на диске. Если ресурсы не используются, то они не занимают места в оперативной памяти.



Символьные клавиатурные сообщения


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

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

while(GetMessage(&msg, 0, 0, 0)) { TranslateMessageTranslateMessage(&msg); DispatchMessage(&msg); }

Функция TranslateMessage преобразует клавиатурные сообщения WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN и WM_SYSKEYUP в символьные сообщения WM_CHAR, WM_DEADCHAR, WM_SYSCHAR, WM_SYSDEADCHAR. Образованные символьные сообщения помещаются в очередь сообщений приложения, причем оригинальные клавиатурные сообщения из этой очереди не удаляются.

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

Параметр wParam содержит код символа, соответствующего нажатой клавише в так называемом стандарте ANSI, принятом в Windows для представления символов. Этот код определяется функцией TranslateMessage с учетом состояния клавиш <Control>, <Shift>, <Alt>, <Caps Lock> и используемого национального языка.

Из всех четырех символьных сообщений приложения чаще всего используют сообщение WM_CHAR, которое передается функции окна в результате трансляции сообщения WM_KEYDOWN. Сообщение WM_SYSCHAR образуется из сообщения WM_SYSKEYDOWN и обычно игнорируется приложением (передается функции DefWindowProc).

Сообщения WM_DEADCHAR и WM_SYSDEADCHAR образуются при использовании клавиатур, имеющих дополнительную клавишу для снабжения символов диакритическими знаками (например, символ "Ў" снабжен диакритическим знаком).
Такие дополнительные клавиши называются "мертвыми" клавишами, так как они не образуют символов, а лишь изменяют действие следующей нажимаемой клавиши. Эти клавиши определяются на основе информации об используемом национальном алфавите.

Если после "мертвой" клавиши была нажата правильная, обычная клавиша (не все символы могут иметь диакритические знаки), в очередь приложения помещаются два сообщения - WM_DEADCHAR и WM_CHAR. Последнее в параметре wParam передает ANSI-код введенного символа, имеющего диакритический знак.

Если после "мертвой" клавиши была нажата клавиша, соответствующая символу, который не может иметь диакритического знака, то в очередь приложения после сообщения WM_DEADCHAR будут записаны два сообщения WM_CHAR. Первое сообщение будет соответствовать коду "мертвой" клавиши, интерпретированному как код символа, второе - коду клавиши, нажатой после "мертвой".

Поэтому приложению достаточно обрабатывать только сообщение WM_CHAR, игнорируя сообщение WM_DEADCHAR (за исключением тех случаев, когда после нажатия "мертвой" клавиши на экране необходимо отобразить диакритический знак). Параметр wParam сообщения WM_CHAR будет содержать правильный ANSI-код символа, учитывающий использование "мертвых" клавиш.


Система координат и режим отображения


При выводе текста из программы MS-DOS вы пользовались простой системой координат, начало которой находилось в левом верхнем углу экрана. Ось x была направлена слева направо, ось y - сверху вниз. Так как в текстовом режиме на экране обычно помещаются 25 строк по 80 символов, возможные значения для x находились в пределах от 0 до 79, а для y - от 0 до 24.

При необходимости вывода текста из программы MS-DOS в графическом режиме вы должны были учитывать, что видеоконтроллер может работать в различных режимах с разным разрешением, например 640 x 480 в одном из режимов VGA или 1024 x 1200 в одном из режимов SVGA. Для каждого типа видеоконтроллера и для каждого видеорежима ваша программа при выводе текста должна была использовать отдельный набор шрифтов.

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

В процессе вывода изображения функции графического интерфейса GDI преобразуют логические координаты в физические. Для определения способа такого преобразования используется атрибут с названием режим отображения (mapping mode), который хранится в контексте устройства вывода.

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

Для указания режима отображения в файле windows.h определены символьные константы с префиксом MM_ (от Mapping Mode - режим отображения). При создании контекста устройства по умолчанию устанавливается режим отображения, обозначаемый как MM_TEXT.
При выводе текста мы будем использовать именно этот режим отображения.

В режиме отображения MM_TEXT используются логическая система координат, полностью эквивалентная физической. Логическое начало координат (0, 0) соответствует физическому началу координат устройства (0, 0). Каждая единица по оси x или y соответствует одному пикселу экрана.

В нашем примере мы вывели строку текста, использовав логические координаты (10, 20):

TextOut(hdc, 10, 20, "Сообщение WM_PAINT", 18);

При использовании режима отображения MM_TEXT в качестве начала координат берется верхняя левая точка устройства вывода. В нашем случае устройством вывода является главное окно приложения, поэтому начало координат (0, 0) находится в верхнем левом углу главного окна приложения. Текст будет выведен начиная с точки (10, 20) в логической системе координат, связанной с главным окном приложения.

Однако как будет расположен текст относительно точки (10, 20)?

Если очертить строку текста воображаемым прямоугольником, то по умолчанию в точке (10, 20) будет находиться верхний левый угол этого прямоугольника (рис. 2.1).



Рис. 2.1. Координаты текстовой строки

В контексте отображения можно задать так называемый режим выравнивания текста (text alignment mode). По умолчанию используется выравнивание на левую границу, причем координаты вывода текста указывают верхнюю границу воображаемого прямоугольника, охватывающего текст.

При помощи функции SetTextAlign можно изменить режим выравнивания. Приведем прототип функции:

UINT WINAPI SetTextAlign(HDC hdc, UINT fuAlign);

Функция SetTextAlign возвращает старое значение режима выравнивания. Ключевое слово WINAPI определено в файле windows.h следующим образом:

#define WINAPI _far _pascal

Как и все функции программного интерфейса Windows версии 3.1 для компьютеров с процессорами фирмы Intel, функция SetTextAlign использует при передаче параметров соглашение языка Паскаль и является дальней функцией.

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


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

Второй параметр (fuAlign) указывает новый режим выравнивания и задается при помощи трех групп флагов. Символические имена флагов определены в файле windows.h и начинаются с префикса TA_.

Первая группа флагов отвечает за выравнивание текстовой строки по горизонтали:

Флаг Описание
TA_LEFT Выравнивание по левой границе. Координаты соответствуют левой границе воображаемого прямоугольника, охватывающего текст (используется по умолчанию)
TA_CENTER Выравнивание по центру. Координаты соответствуют центру воображаемого прямоугольника, охватывающего текст
TA_RIGHT Выравнивание по правой границе
Вторая группа флагов отвечает за выравнивание текста по вертикали:

Флаг Описание
TA_TOP Выравнивание по верхней границе. Координаты соответствуют верхней границе воображаемого прямоугольника, охватывающего текст (используется по умолчанию)
TA_BASELINE Выравнивание по базовой линии выбранного шрифта
TA_BOTTOM Выравнивание по нижней границе
Третья группа флагов относится к текущей позиции вывода текста:

Флаг Описание
TA_NOUPDATECP Не изменять значение текущей позиции вывода текста (используется по умолчанию)
TA_UPDATECP После использования функций TextOut и ExtTextOut вычислить новое значение текущей позиции вывода текста
Понятие текущей позиции вам уже знакомо, если вы составляли программы MS-DOS, выводящие текст. Для приложений Windows в контексте отображения вы можете разрешить или запретить использование текущей позиции. Если использование текущей позиции вывода текста разрешено, ее значение будет обновляться при вызове функций вывода текста TextOut и ExtTextOut (еще одна функция для вывода текста, ее мы рассмотрим позже).

Из каждой группы флагов можно использовать только один, например:

SetTextAlign(hdc, TA_CENTER | TA_BASELINE | TA_UPDATECP);

Если задан режим выравнивания TA_UPDATECP, функция TextOut начинает вывод текста с текущей позиции, игнорируя параметры, определяющие расположение текста в окне.

В любой момент времени вы можете определить текущий режим выравнивания, вызвав функцию GetTextAlign:

UINT GetTextAlign(hdc);

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

Небольшое замечание относительно типа данных UINT. Для операционной системы Windows этот тип данных определен следующим образом:

typedef unsigned int UINT;

Однако не следует думать, что для операционной системы Windows NT будет использовано такое же определение. Поэтому для беззнаковых данных размером в одно машинное слово следует пользоваться типом UINT, но не unsigned int. При этом у вас будет меньше проблем с переносом ваших приложений в другую среду, отличную от Windows версии 3.1.


Системные метрики


Метрики системных компонент Windows можно определить при помощи функции GetSystemMetrics, имеющей следующий прототип:

int WINAPI GetSystemMetrics(int nIndex);

Единственный параметр функции (nIndex) выбирает параметр, значение которого необходимо определить. Значение параметра возвращается функцией GetSystemMetrics.

Для определения компоненты Windows в файле windows.h имеются символические константы с префиксом SM_:

Имя константы Описание
SM_CXBORDER Ширина рамки для окна, размеры которого нельзя изменять
SM_CXCURSOR Ширина курсора
SM_CXDLGFRAME Ширина рамки окна, имеющего стиль WS_DLGFRAME
SM_CXDOUBLECLK Ширина прямоугольника, внутри которого должны быть сделаны два щелчка мышью, для того чтобы они могли распознаваться как один двойной щелчок (double click). Эта константа определена только для Windows версии 3.1
SM_CXFRAME Ширина рамки для окна, размеры которого можно изменять
SM_CXFULLSCREEN Ширина внутренней поверхности окна, увеличенного до предела (maximised)
SM_CXHSCROLL Ширина битового образа стрелки горизонтальной полосы просмотра
SM_CXHTHUMB Ширина ползунка горизонтальной полосы просмотра
SM_CXICON Ширина пиктограммы
SM_CXICONSPACING Ширина прямоугольника, используемого для расположения пиктограммы с заголовком. Эта константа определена только для Windows версии 3.1
SM_CXMIN Минимальная ширина окна
SM_CXMINTRACK Минимальная ширина окна, которая может быть установлена при помощи мыши (Minimum tracking width of a window)
SM_CXSCREEN Ширина экрана
SM_CXSIZE Ширина полосы битового образа (bitmap) заголовка окна (title bar)
SM_CXVSCROLL Ширина битового образа стрелки вертикальной полосы просмотра
SM_CYBORDER Высота рамки для окна, размеры которого нельзя изменять
SM_CYCAPTION Высота заголовка окна
SM_CYCURSOR Высота курсора
SM_CYDLGFRAME Высота рамки окна, имеющего стиль WS_DLGFRAME
SM_CYDOUBLECLK Высота прямоугольника, внутри которого должны быть сделаны два щелчка мышью, для того чтобы они могли распознаваться как один двойной щелчок (double click). Эта константа определена только для Windows версии 3.1
SM_CYFRAME Высота рамки для окна, размеры которого можно изменять
SM_CYFULLSCREEN Высота внутренней поверхности окна, увеличенного до предела (maximised)
SM_CYHSCROLL Высота битового образа стрелки горизонтальной полосы просмотра
SM_CYICON Высота пиктограммы
SM_CYICONSPACING Высота прямоугольника, используемого для расположения пиктограммы с заголовком. Эта константа определена только для Windows версии 3.1
SM_CYKANJIWINDOW Высота окна Kanji
SM_CYMENU Высота одной строки в полосе меню
SM_CYMIN Минимальная высота окна
SM_CYMINTRACK Минимальная высота окна, которая может быть установлена при помощи мыши (Minimum tracking width of a window)
SM_CYSCREEN Высота экрана
SM_CYSIZE Высота полосы битового образа заголовка окна
SM_CYVSCROLL Высота битового образа стрелки вертикальной полосы просмотра
SM_CYVTHUMB Высота ползунка горизонтальной полосы просмотра
SM_DBCSENABLED Флаг использования символов, состоящих из двух байт (используется в тех языках, где для представления всех символов не хватает 8-разрядной сетки). Эта константа определена только для Windows версии 3.1
SM_DEBUG Флаг отладочной версии Windows. Он не равен нулю, если работает отладочная версия Windows (поставляется вместе с Microsoft SDK или Microsoft Visual C++)
SM_MENUDROPALIGNMENT Флаг типа выравнивания временного меню (pop-up menu). Если флаг равен нулю, левая сторона меню выравнена по левой стороне соответствующего элемента строки меню. В противном случае левая сторона меню выравнена по правой стороне соответствующего элемента строки меню. Эта константа определена только для Windows версии 3.1
SM_MOUSEPRESENT Флаг не равен нулю, если компьютер оборудован мышью
SM_PENWINDOWS Идентификатор библиотеки динамической загрузки DLL Pen Windows или 0, если Pen Windows не используется. Эта константа определена только для Windows версии 3.1
SM_RESERVED1 Зарезервировано
SM_RESERVED2 Зарезервировано
SM_RESERVED3 Зарезервировано
SM_RESERVED4 Зарезервировано
SM_SWAPBUTTON Если флаг не равен нулю, действия левой и правой клавиши мыши поменялись местами, то есть вместо левой клавиши используется правая и наоборот, вместо правой - левая



Сообщение WM_TIMER


Параметр wParam сообщения WM_TIMER содержит идентификатор таймера, который был указан или получен от функции SetTimer при создании таймера.

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

После обработки этого сообщения приложение должно возвратить нулевое значение.

Заметим, что сообщение WM_TIMER является низкоприоритетным. Это означает, что функция DispatchMessage посылает это сообщение приложению только в том случае, если в очереди приложения нет других сообщений. В этом отличие таймера Windows от аналогичных средств MS-DOS, реализованных с помощью перехвата прерывания INT8h.

Выполнение программы MS-DOS прерывается синхронно с приходом аппаратного прерывания таймера и программа MS-DOS, перехватившая это прерывание, немедленно оповещается о нем. Выполнение приложения Windows тоже, разумеется, прерывается по аппаратному прерыванию таймера, но оповещение об этом событии приходит не всегда, и как правило, позже, вместе с сообщением WM_TIMER.



Сообщения для внутренней области окна


Эти сообщения генерируются в том случае, если при обработке сообщения WM_HITTEST функция DefWindowProc вернула значение HTCLIENT.

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

Младшее слово параметра lParam содержит горизонтальные координаты курсора мыши, старшее - вертикальные.

Параметр wParam может состоять из отдельных битовых флагов, перечисленных ниже.

Значение Описание
MK_CONTROL На клавиатуре была нажата клавиша <Control>
MK_LBUTTON Была нажата левая клавиша мыши
MK_MBUTTON Была нажата средняя клавиша мыши
MK_RBUTTON Была нажата правая клавиша мыши
MK_SHIFT На клавиатуре была нажата клавиша <Shift>

Анализируя параметр wParam, приложение может определить, были ли в момент события нажаты какие-либо клавиши мыши или клавиши <Control> и <Shift>, расположенные на клавиатуре.

Следует учесть, что вы можете нажать клавишу мыши, когда курсор находится над одним окном, затем переместить курсор в другое окно и там отпустить клавишу мыши. В этом случае одно из сообщений о том, что была нажата клавиша мыши (WM_LBUTTONDOWN, WM_RBUTTONDOWN или WM_MBUTTONDOWN), попадет в функцию первого окна, а сообщение о том, что клавиша мыши была отпущена (WM_LBUTTONUP, WM_RBUTTONUP или WM_MBUTTONUP), - во второе.

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

Следует сделать особое замечание относительно сообщений о двойном щелчке мыши.
Это сообщения WM_LBUTTONDBLCLK, WM_MBUTTONDBLCLK и WM_RBUTTONDBLCLK.

Двойным щелчком ( double click) называется пара одиночных щелчков, между которыми прошло достаточно мало времени. Изменить значение интервала, в течение которого должны поступить два одиночных щелчка, чтобы система распознала их как один двойной щелчок, проще всего при помощи стандартного приложения Windows с именем Control Panel.

Еще одно условие распознавания двойного щелчка менее очевидно и заключается в том, что за интервал между двумя одиночными щелчками курсор мыши не должен переместиться на слишком большое расстояние. С помощью функции GetSystemMetrics в Windows версии 3.1 вы можете определить размеры прямоугольника, внутри которого должны быть сделаны два щелчка мышью, для того чтобы они могли распознаваться как один двойной щелчок. Для этого ей надо передать в качестве параметра значения SM_CXDOUBLECLK (ширина прямоугольника) и SM_CYDOUBLECLK (высота прямоугольника).

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

Если выполнить двойной щелчок левой клавишей мыши в окне, для класса которого не определен стиль CS_DBLCLKS, функция окна последовательно получит следующие сообщения:

WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDOWN WM_LBUTTONUP

Если же сделать то же самое в окне, способном принимать сообщения о двойном щелчке, функция окна в ответ на двойной щелчок получит следующую последовательность сообщений:

WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK WM_LBUTTONUP

Как нетрудно заметить, перед сообщением WM_LBUTTONDBLCLK функция окна получит сообщение WM_LBUTTONDOWN. Дело в том, что после первого щелчка Windows еще не знает, будет ли следом обычный или двойной щелчок, - все зависит от интервала времени и перемещения курсора.

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



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

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

Последнее сообщение из группы сообщений для внутренней области окна имеет имя WM_MOUSEACTIVATE. Оно посылается функции неактивного окна, когда вы помещаете в это окно курсор мыши и делаете щелчок левой или правой клавишей. Если передать это сообщение функции DefWindowProc, в ответ на него Windows сделает указанное окно активным.

Сообщение WM_MOUSEACTIVATE передает параметры wParam и lParam.

Параметр wParam содержит идентификатор окна, которое будет активным. Если активным становится окно, имеющее дочерние окна, передается идентификатор самого старшего, родительского окна.

Младшее слово параметра lParam содержит результат обработки сообщения WM_NCHITTEST функцией DefWindowProc. Мы описали возможные значения, когда рассказывали о сообщении WM_NCHITTEST.

Старшее слово параметра lParam содержит код сообщения мыши, соответствующий способу, которым данное окно было выбрано. Это может быть код сообщений типа WM_LBUTTONDOWN, WM_RBUTTONDOWN и т. п.

Для сообщения WM_MOUSEACTIVATE определен код возврата:

Код возврата Описание
MA_ACTIVATE Сделать окно активным
MA_ACTIVATEANDEAT Не делать окно активным
MA_NOACTIVATE Сделать окно активным и удалить события, связанные с мышью
MA_NOACTIVATEANDEAT Не делать окно активным и удалить события, связанные с мышью

Сообщения, поступающие от мыши


Мышь может порождать много сообщений, всего их 22! Однако большинство из них вы можете благополучно проигнорировать, передав эти сообщения "всеядной" функции DefWindowProc. Сообщения, поступающие от мыши, содержат информацию о текущем расположении курсора, о его расположении в момент, когда вы нажимаете на клавиши мыши, и другую аналогичную информацию.

Куда попадают сообщения от мыши?

Существует два режима, определяющих два способа распределения сообщений от мыши.

В первом режиме, который установлен по умолчанию, сообщения от мыши направляются функции окна, расположенного под курсором мыши. Если в главном окне приложения создано дочернее окно и курсор мыши располагается над дочерним окном, сообщения мыши попадут в функцию дочернего окна, но не в функцию главного окна приложения. Это же касается и временных (pop-up) окон.

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

HWND WINAPI SetCapture(HWND hwnd);

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

Функция SetCapture возвращает идентификатор окна, которое захватывало мышь до вызова функции или NULL, если такого окна не было.

Функция ReleaseCapture возвращает нормальный режим обработки сообщений мыши:

void WINAPI ReleaseCapture(void);

Эта функция не имеет параметров и не возвращает никакого значения.

Функция GetCapture позволяет определить идентификатор окна, захватившего мышь:

HWND WINAPI GetCapture(void);

Если ни одно окно не захватывало мышь, эта функция возвратит значение NULL.

В любом случае на получение сообщений от мыши никак не влияет факт приобретения или потери окном фокуса ввода.

Приведем полный список сообщений, поступающих от мыши.

Сообщение Описание
WM_LBUTTONDBLCLK Двойной щелчок левой клавишей мыши во внутренней (client) области окна
WM_LBUTTONDOWN Нажата левая клавиша мыши во внутренней области окна
WM_LBUTTONUP Отпущена левая клавиша мыши во внутренней области окна
WM_MBUTTONDBLCLK Двойной щелчок средней клавишей мыши во внутренней области окна
WM_MBUTTONDOWN Нажата средняя клавиша мыши во внутренней области окна
WM_MBUTTOMUP Отпущена средняя клавиша мыши во внутренней области окна
WM_MOUSEMOVE Перемещение курсора мыши во внутренней области окна
WM_RBUTTONDBLCLK Двойной щелчок правой клавишей мыши во внутренней области окна
WM_RBUTTONDOWN Нажата правая клавиша мыши во внутренней области окна
WM_RBUTTONUP Отпущена правая клавиша мыши во внутренней области окна
WM_NCHITTEST Перемещение мыши в любом месте экрана
WM_MOUSEACTIVE Нажата клавиша мыши над неактивным окном
WM_NCLBUTTONDBLCLK Двойной щелчок левой клавишей мыши во внешней (non-client) области окна
WM_NCLBUTTONDOWN Нажата левая клавиша мыши во внешней области окна
WM_NCLBUTTONUP Отпущена левая клавиша мыши во внешней области окна
WM_NCMBUTTONDBLCLK Двойной щелчок средней клавишей мыши во внешней области окна
WM_NCMBUTTONDOWN Нажата средняя клавиша мыши во внешней области окна
WM_NCMBUTTOMUP Отпущена средняя клавиша мыши во внешней области окна
WM_NCMOUSEMOVE Перемещение курсора мыши во внешней области окна
WM_NCRBUTTONDBLCLK Двойной щелчок правой клавишей мыши во внешней области окна
WM_NCRBUTTONDOWN Нажата правая клавиша мыши во внешней области окна
WM_NCRBUTTONUP Отпущена правая клавиша мыши во внешней области окна
<
Из приведенных выше 22 сообщений 21 сообщение образуется из сообщения WM_NCHITTEST. Это сообщение генерируется драйвером мыши при любых перемещениях мыши. Разумеется, драйвер не отслеживает перемещение мыши для каждого пиксела экрана. Период возникновения сообщений WM_NCHITTEST зависит от скорости перемещения мыши, параметров драйвера, аппаратуры мыши и т. п.

Сообщение WM_NCHITTEST не использует параметр wParam. В младшем слове параметра lParam передается горизонтальная позиция курсора мыши, а в старшем - вертикальная. Координаты вычисляются относительно верхнего левого угла экрана.

Приложения редко обрабатывают сообщение WM_NCHITTEST, обычно оно передается функции DefWindowProc. Получив это сообщение, функция DefWindowProc определяет положение курсора мыши относительно расположенных на экране объектов и возвращает одно из приведенных ниже значений, описанных в файле windows.h).

Значение Расположение курсора мыши
HTBORDER На рамке окна, которое создано без толстой рамки, предназначенной для изменения размера окна
HTBOTTOM На нижней горизонтальной линии рамки окна
HTBOTTOMLEFT В левом нижнем углу рамки
HTBOTTOMRIGHT В правом нижнем углу рамки
HTCAPTION На заголовке окна (title-bar)
HTCLIENT Во внутренней области окна (client area)
HTERROR Над поверхностью экрана или на линии, разделяющей различные окна. Дополнительно функция DefWindowProc выдает звуковой сигнал
HTGROWBOX В области изменения размера окна (size box)
HTHSCROLL На горизонтальной полосе просмотра
HTLEFT На левой вертикальной линии рамки окна
HTMAXBUTTON На кнопке максимизиции
HTMENU В области меню
HTMINBUTTON На кнопке минимизации
HTNOWHERE Над поверхностью экрана или на линии, разделяющей различные окна
HTREDUCE В области минимизации
HTRIGHT На правой вертикальной линии рамки окна
HTSIZE В области изменения размера окна (size box). То же самое, что и HTGROWBOX
HTSYSMENU В области системного меню
HTTOP На верхней горизонтальной линии рамки окна
HTTOPLEFT В верхнем левом углу рамки окна
HTTOPRIGHT В правом верхнем углу рамки окна
HTTRANSPARENT В окне, которое перекрыто другим окном
HTVSCROLL На вертикальной полосе просмотра
HTZOOM В области максимизиции
<


После обработки сообщения WM_HITTEST Windows анализирует расположение курсора и генерирует одно из сообщений, описанных выше.

Если курсор находится во внутренней области окна (client area), функция DefWindowProc возвращает значение HTCLIENT. В этом случае функция окна, над которой находится курсор мыши (или функция окна, захватившая мышь), будет получать сообщения о событиях во внутренней области окна. Это все описанные выше сообщения, кроме сообщений с префиксом WM_NC и сообщения WM_MOUSEACTIVATE (сочетание букв NC в символическом имени сообщения означает Non Client).

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

Если при обработке сообщения WM_HITTEST выясняется, что курсор мыши расположен во внешней области окна, функция окна получает сообщения мыши с префиксом WM_NC.

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

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


Создание главного окна приложения


Далее приложение вызывает функцию CreateWindow для того, чтобы создать главное окно приложения:

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

В случае успеха функция CreateWindow возвращает идентификатор окна (типа HWND). Если окно создать не удалось, функция возвращает нулевое значение.

Приведем прототип функции CreateWindow:

HWND CreateWindow(LPCSTR lpszClassName, LPCSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hwndParent, HMENU hmenu, HINSTANCE hinst, void FAR* lpvParam);

Многочисленные параметры функции CreateWindow дополняют описание окна, сделанное при создании класса окна.

Первый параметр функции (lpszClassName) - указатель на строку, содержащую имя класса, на базе которого создается окно. В процессе инициализации приложения мы зарегистрировали класс с именем "WindowAppClass" (переменная szClassName).

Второй параметр функции (lpszWindowName) - указатель на строку, содержащую заголовок окна (Title Bar). В нашем случае окно будет иметь заголовок "Window Application" (переменная szWindowTitle).

Третий параметр (dwStyle) - стиль создаваемого окна. Этот параметр задается как логическая комбинация отдельных битов. Константа WS_OVERLAPPEDWINDOW соответствует окну, которое может перекрывать другие окна, имеет заголовок, системное меню, кнопки для минимизации и максимизации окна, а также рамку вокруг окна, с помощью которой можно изменять размер окна. Операционная система Windows позволяет задавать различные стили для создаваемых окон. Мы их рассмотрим в дальнейшем.

Четвертый и пятый параметры функции CreateWindow для окна данного стиля определяют горизонтальную (x) и вертикальную (y) координату относительно верхнего левого угла экрана видеомонитора.


Шестой и седьмой параметры определяют ширину (nWidth) и высоту (nHeight) создаваемого окна.

Наше приложение в качестве координат окна и его размеров использует константу CW_USEDEFAULT. При этом операционная система Windows сама определяет положение и размеры создаваемого окна.

Восьмой параметр (hwndParent) определяет индекс родительского окна. Для нашего приложения в качестве значения используется нуль, так как в приложении создается только одно окно.

Девятый параметр (hmenu) - идентификатор меню или идентификатор порожденного (child) окна. В нашем случае никакого меню или порожденного окна нет, поэтому в качестве значения используется нуль.

Десятый параметр (hinst) - идентификатор приложения, которое создает окно. Необходимо использовать значение, передаваемое функции WinMain через параметр hInstance.

Одиннадцатый, последний параметр функции (lpvParam) представляет собой дальний указатель на область данных, определяемых приложением. Этот параметр передается в функцию окна вместе с сообщением WM_CREATE при создании окна. Наше приложение не пользуется этим параметром.


Создание и уничтожение таймера


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

UINT WINAPI SetTimer(HWND hwnd, UINT idTimer, UINT uTimeout, TIMERPROC tmprc);

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

Второй параметр (idTimer) определяет идентификатор таймера (он не должен быть равен нулю). Идентификатор используется только в том случае, если первый параметр функции SetTimer содержит идентификатор окна. Так как для одного окна можно создать несколько таймеров, для того чтобы различать сообщения, приходящие от разных таймеров, приложение при создании должно снабдить каждый таймер собственным идентификатором.

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

Третий параметр (uTimeout) определяет период следования сообщений от таймера в миллисекундах. Учтите, что физический таймер тикает приблизительно 18,21 раза в секунду (точное значение составляет 1000/54,925). Поэтому, даже если вы укажете, что таймер должен тикать каждую миллисекунду, сообщения будут приходить с интервалом не менее 55 миллисекунд.

Последний параметр (tmprc) определяет адрес функции, которая будет получать сообщения WM_TIMER (мы будем называть эту функцию функцией таймера). Этот параметр необходимо обязательно указать, если первый параметр функции SetTimer равен NULL.

Тип TIMERPROC описан в файле windows.h следующим образом:

typedef void (CALLBACK* TIMERPROC)(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

Сравните это с описанием типа WNDPROC, который используется для знакомой вам функции окна:

typedef LRESULT (CALLBACK* WNDPROC)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

Как видно из описания, функция таймера не возвращает никакого значения, имеет другие (по сравнению с функцией окна) параметры, но описана с тем же ключевым словом CALLBACK:


#define CALLBACK _far _pascal

Возвращаемое функцией SetTimer значение является идентификатором созданного таймера (если в качестве первого параметра функции было указано значение NULL). В любом случае функция SetTimer возвращает нулевое значение, если она не смогла создать таймер. В Windows версии 3.0 максимальное количество созданных во всей системе таймеров было 16. Для Windows версии 3.1 это ограничение снято.

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

BOOL WINAPI KillTimer (HWND hwnd, UINT idTimer);

Первый параметр функции (hwnd) определяет идентификатор окна, указанный при создании таймера функцией SetTimer.

Второй параметр (idTimer) - идентификатор уничтожаемого таймера. Это должен быть либо тот идентификатор, который вы указали при создании таймера (если таймер создавался для окна), либо значение, полученное при создании таймера от функции SetTimer (для таймера, имеющего собственную функцию обработки сообщений).

Функция KillTimer возвращает значение TRUE при успешном уничтожении таймера или FALSE, если она не смогла найти таймер с указанным идентификатором.


Список стилей окна


Приведем полный список возможных стилей окна, определенных в виде символических констант в файле windows.h. Некоторые из приведенных здесь стилей будут подробно рассмотрены в соответствующих главах этого тома или в следующих томах "Библиотеки системного программиста".

Имя константы Описание стиля
WS_BORDER Окно с рамкой
WS_CAPTION Окно с заголовком. Этот стиль несовместим со стилем WS_DLGFRAME. При использовании стиля WS_CAPTION подразумевается использование стиля WS_BORDER
WS_CHILD Дочернее окно. Несовместим со стилем WS_POPUP
WS_CHILDWINDOW То же самое, что и WS_CHILD
WS_CLIPCHILDREN Этот стиль используется при создании родительского окна. При его использовании родительское окно не перерисовывает свои внутренние области, занятые дочерними окнами
WS_CLIPSIBLINGS При указании этого стиля дочерние окна не перерисовывают свои области, перекрытые "братьями", то есть другими дочерними окнами, имеющими тех же родителей
WS_DISABLED Вновь созданное окно сразу становится заблокированным (не получает сообщения от мыши и клавиатуры)
WS_DLGFRAME Окно с двойной рамкой без заголовка. Несовместим со стилем WS_CAPTION
WS_GROUP Определяет первый орган управления в группе органов управления. Используется только в диалоговых панелях
WS_HSCROLL В окне создается горизонтальная полоса просмотра
WS_ICONIC То же самое, что и WS_MINIMIZE
WS_MAXIMIZE Создается окно максимально возможного размера
WS_MAXIMIZEBOX Окно содержит кнопку для увеличения его размера до максимально возможного. Этот стиль необходимо использовать вместе со стилями WS_OVERLAPPED или WS_CAPTION, в противном случае указанная кнопка не появится
WS_MINIMIZE Создается окно, уменьшенное до предела (свернутое в пиктограмму). Этот стиль необходимо использовать вместе со стилем WS_OVERLAPPED
WS_MINIMIZEBOX Окно содержит кнопку для сворачивания окна в пиктограмму (минимизации размеров окна). Этот стиль необходимо использовать вместе со стилем WS_OVERLAPPED или WS_CAPTION, в противном случае указанная кнопка не появится
WS_OVERLAPPED Создается перекрывающееся окно, имеющее заголовок и рамку
WS_OVERLAPPEDWINDOW Создается перекрывающееся окно, имеющее заголовок, рамку для изменения размера окна, системное меню, кнопки для изменения размеров окна. Этот стиль является комбинацией стилей WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX
WS_POPUP Создается временное (pop-up) окно
WS_POPUPWINDOW Комбинация стилей WS_POPUP, WS_BORDER и WS_SYSMENU. Для того чтобы сделать системное меню доступным, необходимо дополнительно использовать стиль WS_CAPTION
WS_SYSMENU Окно должно иметь системное меню
WS_TABSTOP Этот стиль указывает орган управления, на который можно переключиться при помощи клавиши <Tab>. Данный стиль может быть использован только дочерними окнами в диалоговых панелях
WS_THICKFRAME Окно должно иметь толстую рамку для изменения размера окна
WS_VISIBLE Создается окно, которое сразу становится видимым. По умолчанию окна создаются невидимыми, и для их отображения требуется вызывать функцию ShowWindow
WS_VSCROLL В окне создается вертикальная полоса просмотра
WS_TILED Устаревший стиль, аналогичен WS_OVERLAPPED
WS_SIZEBOX Устаревший стиль, аналогичен WS_THICKFRAME
WS_TILEDWINDOW Устаревший стиль, аналогичен WS_OVERLAPPEDWINDOW
MDIS_ALLCHILDSTYLES Этот стиль используется при создании дочерних MDI-окон и определяет окна, которые могут иметь любые комбинации стилей. По умолчанию дочерние MDI-окна имеют стили WS_MINIMIZE, WS_MAXIMIZE, WS_VSCROLL, WS_HSCROLL
<
Приведенные выше стили не всегда совместимы друг с другом. Например, перекрывающееся окно не может быть одновременно еще и временным. Пользуясь приведенной ниже таблицей, вы сможете определить совместимость стилей. В этой таблице символом "+" отмечены стили, которые можно использовать для создания перекрывающихся, временных и дочерних окон.

Имя константы Перекрывающееся окно Временное окно Дочернее окно
WS_BORDER + + +
WS_CAPTION + + +
WS_CHILD   +
WS_CHILDWINDOW     +
WS_CLIPCHILDREN + + +
WS_CLIPSIBLINGS     +
WS_DISABLED + + +
WS_DLGFRAME + + +
WS_GROUP     +
WS_HSCROLL + + +
WS_ICONIC +    
WS_MAXIMIZE +    
WS_MAXIMIZEBOX + + +
WS_MINIMIZE +    
WS_MINIMIZEBOX + + +
WS_OVERLAPPED +    
WS_OVERLAPPEDWINDOW +    
WS_POPUP   +  
WS_POPUPWINDOW   +  
WS_SYSMENU + + +
WS_TABSTOP     +
WS_THICKFRAME + + +
WS_VISIBLE + +  
WS_VSCROLL + + +
WS_TILED +    
WS_SIZEBOX + + +
WS_TILEDWINDOW +    
MDIS_ALLCHILDSTYLES      
В дополнение к перечисленным выше стилям, используемым при создании окон функцией CreateWindow, существуют так называемые расширенные стили окна (extended window styles). Окна с расширенными стилями должны создаваться функцией CreateWindowEx. Эта функция имеет следующий прототип:

HWND CreateWindowEx(DWORD dwExStyle, LPCSTR lpszClassName, LPCSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hwndParent, HMENU hmenu, HINSTANCE hinst, void FAR* lpvCreateParams);

Функции CreateWindowEx в качестве первого параметра (dwExStyle) необходимо указать расширенный стиль окна. Остальные параметры в точности соответствуют параметрам функции CreateWindow.

Приведем список расширенных стилей окна.

Имя константы Описание стиля
WS_EX_ACCEPTFILES Окно способно принимать файлы, перенесенные с использованием технологии drag-drop
WS_EX_DLGMODALFRAME Окно имеет двойную рамку и дополнительно может иметь стиль WS_CAPTION
WS_EX_NOPARENTNOTIFY Дочернее окно с этим стилем не будет посылать родительскому окну сообщение WM_PARENTNOTIFY. Обычно, когда дочернее окно создается или уничтожается или когда вы щелкаете мышью над дочерним окном, это сообщение посылается родительскому окну
WS_EX_TOPMOST Окно будет видно всегда, даже когда оно заблокировано
WS_EX_TRANSPARENT Этот стиль позволяет создать прозрачное окно. Оно получает сообщение WM_PAINT только после того, как все окна-братья получили сообщение WM_PAINT и обновили свои окна
<


Приведем таблицу совместимости расширенных стилей с перекрывающимися, временными и дочерними окнами.

Имя константы Перекрывающееся окно Временное окно Дочернее окно
WS_EX_ACCEPTFILES + + +
WS_EX_DLGMODALFRAME + + +
WS_EX_NOPARENTNOTIFY     +
WS_EX_TOPMOST + +  
WS_EX_TRANSPARENT + + +

Стандарты кодов символов


В операционной системе MS-DOS использовался расширенный набор символов, определенный фирмой IBM (IBM extended character set). В этот набор входят буквы английского алфавита, знаки пунктуации и псевдографики (рис. 5.2).

Рис. 5.2. Расширенный набор символов IBM

Для обеспечения возможности работы с символами кириллицы фирма Microsoft разработала набор символов, соответствующий 866-й кодовой странице (рис. 5.3). Этот набор символов был использован сначала в локализованной MS-DOS версии 4.01, а затем в локализованных версиях 5.0 и 6.0 этой операционной системы.

Рис. 5.3. Расширенный набор символов с кириллицей

В терминологии Windows приведенные выше наборы символов называются наборами символов OEM. OEM (Original Equipment Manufacturer) означает "производители оригинальной (в смысле подлинной) аппаратуры". Набор символов OEM соответствует естественному для данной аппаратуры набору. Он может меняться в зависимости от производителя, от страны, для которой выполнялась локализация операционной системы MS-DOS или изготавливалась аппаратура.

Операционная система Windows для представления символов использует набор символов ANSI (рис. 5.4).

Рис. 5.4. Набор символов ANSI

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

На рис. 5.5 изображен набор символов ANSI с добавлением кириллицы. Такой набор устанавливается в локализованной версии Windows или после локализации Windows с помощью специального программного продукта CyrWin, добавляющего в Windows возможность работы с русскими символами.

Рис. 5.5. Набор символов ANSI с символами кириллицы

Если программа MS-DOS запускается в окне Windows, для нее выбирается набор символов OEM. Поэтому в Windows используются как набор символов ANSI, так и набор символов OEM.
Соответствующим выбором шрифта вы можете добиться отображения текста в окне Windows с использованием как набора ANSI, так и набора OEM. По умолчанию в контекст отображения выбирается системный шрифт, для которого используется набор ANSI.

Сравнивая приведенные выше таблицы, нетрудно заметить, что для одинаковых символов наборы OEM и ANSI используют разные коды, которые к тому же могут зависеть от используемого национального языка. Это приводит к необходимости перекодировки символов, например при переносе текстов, подготовленных в среде MS-DOS в среду Windows или при открытии файлов из среды Windows. В последнем случае перед использованием имя файла, подготовленное с использованием набора ANSI, требуется перекодировать в набор OEM.

Появляются дополнительные трудности с преобразованием строчных букв в прописные и обратно, так как правила преобразования и сортировки могут различаться для разных национальных языков.

К счастью, специальный набор функций программного интерфейса Windows берет на себя работу по преобразованию и перекодировке символов.

Для перекодировки строки символов, закрытой двоичным нулем, из набора ANSI в набор OEM предназначена функция AnsiToOem:

void WINAPI AnsiToOem(const char _huge* hpszWindowsStr, char _huge* hpszOemStr);

Первый параметр (hpszWindowsStr) представляет собой указатель типа _huge на преобразуемую строку, второй (hpszOemStr) - на буфер для записи результата преобразования.

Похожая по назначению функция AnsiToOemBuff выполняет преобразование буфера заданного размера:

void WINAPI AnsiToOemBuff(LPCSTR lpszWindowsStr, LPSTR lpszOemStr, UINT cbWindowsStr);

Первый параметр этой функции (lpszWindowsStr) является дальним указателем на буфер, содержащий преобразуемые данные, второй (lpszOemStr) - на буфер для записи результата. Третий параметр (cbWindowsStr) определяет размер входного буфера, причем нулевой размер соответствует буферу длиной 64 Кбайт (65536 байт).

Обратное преобразование выполняется функциями OemToAnsi и OemToAnsiBuff:



void WINAPI OemToAnsi( const char _huge* hpszOemStr, char _huge* lpszWindowsStr); void WINAPI OemToAnsiBuff(LPCSTR lpszOemStr, LPSTR lpszWindowsStr, UINT cbOemStr);

Назначение параметров этих функций аналогично назначению параметров функций AnsiToOem и AnsiToOemBuff.

Для преобразований символов в строчные или прописные приложение Windows должно пользоваться функциями AnsiLower, AnsiLowerBuff, AnsiUpper, AnsiUpperBuff.

Функция AnsiLower преобразует закрытую двоичным нулем текстовую строку в строчные буквы:

LPSTR WINAPI AnsiLower(LPSTR);

Единственный параметр функции - дальний указатель на преобразуемую строку.

Функция AnsiUpper преобразует закрытую двоичным нулем текстовую строку в прописные буквы:

LPSTR WINAPI AnsiLower(LPSTR lpsz);

Параметр функции lpsz - дальний указатель на преобразуемую строку.

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

Функция AnsiLowerBuff позволяет преобразовать в строчные буквы заданное количество символов:

UINT WINAPI AnsiLowerBuff(LPSTR lpszString, UINT cbString);

Первый параметр функции (lpszString) является указателем на буфер, содержащий преобразуемые символы, второй (cbString) определяет количество преобразуемых символов (размер буфера). Нулевой размер соответствует буферу длиной 64 Кбайт (65536 байт).

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

Функция AnsiUpperBuff позволяет преобразовать в прописные буквы заданное количество символов:

UINT WINAPI AnsiUpperBuff(LPSTR lpszString, UINT cbString);

Первый параметр функции lpszString(lpszString) является указателем на буфер, содержащий преобразуемые символы, второй (cbString) определяет количество преобразуемых символов (размер буфера). Нулевой размер соответствует буферу длиной 64 Кбайт (65536 байт).

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

Еще одна проблема связана с необходимостью позиционирования вдоль текстовой строки.


Если используется однобайтовое представление символов, позиционирование сводится к увеличению или уменьшению значения указателя на один байт. Однако в некоторых национальных языках (например, в японском) набор символов OEM для представления каждого символа использует два байта. Для правильного позиционирования (с учетом различных наборов символов) необходимо использовать специальные функции AnsiNext и AnsiPrev, которые входят в состав программного интерфейса Windows.

Функция возвращает новое значение для указателя, передвинутое вперед по строке на одни символ:

LPSTR WINAPI AnsiNext(LPCSTR lpchCurrentChar);

Параметр функции указывает на текущий символ. Возвращаемое значение является указателем на следующий символ в строке или на закрывающий строку двоичный ноль.

Функция AnsiPrev выполняет передвижение указателя в направлении к началу строки:

LPSTR WINAPI AnsiPrev(LPCSTR lpchStart, LPCSTR lpchCurrentChar);

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

В составе программного интерфейса Windows имеются функции для преобразования символа ANSI в код виртуальной клавиши (VkKeyScan) или в соответствующий OEM скан-код и состояние (OemKeyScan).

Функция VkKeyScan используется для преобразования кода символа ANSI в код и состояние виртуальной клавиши:

UINT WINAPI VkKeyScan(UINT uChar);

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

Младший байт возвращаемого значения содержит код виртуальной клавиши, старший - состояние клавиш сдвига (<Shift>, <Alt>, <Control>):

Значение Описание
1 При выводе символа была нажата клавиша сдвига
2 Символ является управляющим
3 - 5 Данная комбинация клавиш сдвига не используется для представления символов
6 Символ образован при помощи комбинации клавиш <Control+Alt>
7 Символ образован при помощи комбинации клавиш <Shift+Control+Alt>
Эта функция обычно используется приложениями, которые передают символы другим приложениям с помощью сообщений WM_KEYDOWN и WM_KEYUP (то есть симулируют ввод с клавиатуры).

Функция OemKeyScan преобразует символ OEM в скан-код и состояние для набора OEM:

DWORD WINAPI OemKeyScan(UINT uOemChar);

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

Младшее слово возвращаемого значения содержит OEM скан-код для указанного символа.

Старшее слово указывает состояние клавиш сдвига для заданного символа. Если в этом слове установлен бит 1, нажата клавиша <Shift>, если бит 2 - клавиша <Control>.

Если преобразуемое значение не принадлежит к набору OEM, возвращается значение -1 (и в старшем, и в младшем слове).


Стиль класса окна


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

wc.style = 0;

Стиль класса окна задается в виде отдельных битов, для которых в файле windows.h определены символические константы с префиксом CS_:

Стиль Описание
CS_BYTEALIGNCLIENT Внутренняя область окна (client area) должна быть выравнена по границе байта видеопамяти. Иногда используется для ускорения процесса вывода изображения
CS_BYTEALIGNWINDOW Все окно (не только внутренняя область окна) должно быть выравнено по границе байта видеопамяти
CS_CLASSDC Необходимо создать единый контекст отображения, который будет использоваться всеми окнами, создаваемыми на базе данного класса
CS_DBLCLKS Функция окна будет получать сообщения при двойном щелчке клавишей мыши (double click)
CS_GLOBALCLASS Данный класс является глобальным и доступным другим приложениям. Другие приложения могут создавать окна на базе этого класса
CS_HREDRAW Внутренняя область окна должна быть перерисована при изменении ширины окна
CS_NOCLOSE В системном меню окна необходимо запретить выбор функции закрытия окна (строка Close будет отображаться серым цветом, и ее нельзя выбрать)
CS_OWNDC Для каждого окна, определяемого на базе данного класса, будет создаваться отдельный контекст отображения
CS_PARENTDC Окно будет пользоваться родительским контекстом отображения, а не своим собственным. Родительский контекст - это контекст окна, создавшего другое окно (см. дальше)
CS_SAVEBITS Для данного окна Windows должна сохранять изображение в виде битового образа (bitmap). Если такое окно будет перекрыто другим окном, то после уничтожения перекрывшего окна изображение первого окна будет восстановлено Windows на основании сохраненного ранее образа
CS_VREDRAW Внутренняя область окна должна быть перерисована при изменении высоты окна

Чаще всего используются стили CS_HREDRAW и CS_VREDRAW:

wc.style = CS_HREDRAW | CS_VREDRAW;

Если для класса заданы стили CS_HREDRAW и CS_VREDRAW, при изменении размеров окна функция окна может получить сообщение WM_PAINT. В этом случае функция окна должна перерисовать часть окна или все окно. Разумеется, если вы просто уменьшили размер окна, перерисовывать ничего не надо, и функция окна в этом случае не получит сообщения WM_PAINT.

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

Остальные приведенные выше классы окна используются реже. Мы будем рассказывать о них по мере необходимости.



Стиль окна


Определенный в классе окна стиль класса окна используется при создании на базе этого класса всех окон. Для дальнейшего уточнения внешнего вида и поведения окна используется другая характеристика - стиль окна. Стиль окна указывается при создании окна функцией CreateWindow. В наших примерах основное окно приложения не имело стиля класса окна, но для него был определен стиль окна WS_OVERLAPPEDWINDOW:

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры

Для определения стиля окна используются символические константы с префиксом WS_, определенные в файле windows.h. С помощью этих констант можно определить примерно два десятка стилей окна, однако чаще всего используются несколько основных стилей.

Мы рассмотрим три основных стиля окон - перекрывающиеся окна (overlapped window), временные окна (pop-up window) и дочерние окна (child window).



Стили окна


3.1

3.2.

3.3.

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

Напомним, что для создания окна вам необходимо зарегистрировать класс окна. Есть классы окон, зарегистрированные при инициализации Windows. Ваше приложение может создавать окна либо на базе собственных классов (созданных и зарегистрированных приложением), либо на базе готовых классов, созданных и зарегистрированных самой операционной системой Windows. В этой главе мы расскажем вам только об окнах, созданных приложениями на базе своих собственных классов окон.

На базе одного класса окна приложение может создать несколько окон. Все эти окна могут быть сделаны в одном или нескольких стилях. Стиль окна определяет внешний вид окна и его поведение. Для класса окна также определяется понятие стиля - стиль класса определяет внешний вид и поведение всех окон, созданных на базе данного класса.



Таймер


7.1.

7.2.

7.3.

7.4.

7.5.

7.6.

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

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

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

Есть и другой способ, также основанный на передаче сообщений. При использовании этого способа сообщения WM_TIMER посылаются не функции окна, а специальной функции, описанной с ключевым словом _export. Эта функция напоминает функцию окна и, так же как и функция окна, вызывается не из приложения, а из Windows. Функции, которые вызываются из Windows, имеют специальный пролог и эпилог и называются функциями обратного вызова (callback function). Функция окна и функция, специально предназначенная для обработки сообщений таймера, являются примерами функций обратного вызова.

К сожалению, точность виртуального таймера оставляет желать лучшего. Сообщения таймера проходят через очередь приложения, к тому же другое приложение может блокировать на некоторое время работу вашего приложения. Поэтому сообщения от таймера приходят в общем случае нерегулярно. Кроме того, несмотря на возможность указания интервалов времени в миллисекундах, реальная дискретность таймера определяется периодом прерываний, посылаемых таймером. Этот период (то есть длительность одного такта таймера) можно узнать с помощью функции GetTimerResolution:

DWORD WINAPI GetTimerResolution(void);

Нерегулярность прихода сообщений таймера не вызывает особых проблем, если речь не идет о работе в режиме реального времени. Системы реального времени, основанные на Windows, должны использовать для устройств ввода/вывода, критичных к скорости реакции системы, специальные драйверы. Строго говоря, операционная система Windows не предназначена для работы в режиме реального времени. Windows ориентирована на работу с человеком, когда небольшие задержки событий во времени не имеют никакого значения.



Текстовый курсор


При вводе текста в среде MS-DOS используется текстовый курсор, указывающий позицию ввода очередного символа. Текстовый курсор в MS-DOS формируется автоматически функциями BIOS или создается программой. В последнем случае программа должна сама следить за перемещением текстового курсора.

Если ваше приложение создает свой собственный текстовый редактор (не пользуясь стандартным, который доступен всем приложениям Windows), вы должны сами создать текстовый курсор и заботиться о его внешнем виде, отображении и перемещении.

Заметим, что в операционной системе Windows используются два курсора. Один курсор называется cursor и означает курсор мыши. Второй курсор называется caret (знак вставки) и означает текстовый курсор.

Текстовый курсор является системным ресурсом Windows. Можно создать только один текстовый курсор. И это правильно, так как в противном случае вы, увидев на экране два или больше приглашений для ввода текста, не будете знать, где же будут отображаться вводимые вами при помощи клавиатуры символы.

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

Когда окно теряет фокус ввода, оно должно уничтожить текстовый курсор. При потере фокуса ввода функции окна передается сообщение WM_KILLFOCUS, обработчик которого и должен удалить текстовый курсор, созданный при обработке сообщения WM_SETFOCUS.

Для создания текстового курсора обработчик сообщения WM_SETFOCUS должен вызвать функцию CreateCaret, входящую в программный интерфейс Windows:

void WINAPI CreateCaret(HWND hwnd, HBITMAP hbmp, int nWidth, int nHeight);

Первый параметр функции (hwnd) - идентификатор окна, создающего текстовый курсор.

Второй параметр (hbmp) может принимать значения NULL, 1 или он может быть идентификатором битового изображения курсора (bitmap). Если этот параметр равен NULL, текстовый курсор представляет собой вертикальную черту черного цвета.
Если этот параметр равен 1, текстовый курсор изображается серым цветом. Вы также можете нарисовать курсор любой произвольной формы в виде битового образа (bitmap) и использовать этот свой курсор, загрузив его из ресурсов приложения и указав идентификатор. В этом случае третий и четвертый параметры функции игнорируются. О ресурсах и битовых образах вы узнаете позже, так как это тема для отдельного разговора.

Третий параметр (nWidth) определяет ширину курсора в логических единицах. Если задать для ширины значение NULL, курсор будет иметь ширину, равную ширине рамки, создаваемой вокруг окна. Это значение возвращается функцией GetSystemMetrics, когда ей в качестве параметра указывается константа SM_CXBORDER.

Последний, четвертый параметр (nHeight) функции CreateCaret определяет высоту текстового курсора в логических единицах. Для этого параметра также можно указать значение NULL, при этом высота текстового курсора будет равна высоте рамки окна. Это значение возвращается функцией GetSystemMetrics, когда ей в качестве параметра указывается константа SM_CYBORDER.

Если для ширины и высоты текстового курсора заданы значения NULL, курсор будет видно при использовании видеорежимов высокого разрешения. Если же вы укажете ширину текстового курсора, равную одному пикселю, в режимах с высоким разрешением курсор окажется слишком тонким для того, чтобы его можно было заметить.

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

Когда функция окна получает сообщение WM_KILLFOCUS, она должна уничтожить текстовый курсор, вызвав функцию DestroyCaret:

void WINAPI DestroyCaret(void);

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

Сразу после создания функцией CreateCaret текстовый курсор находится в невидимом, выключенном состоянии.


Для того чтобы сделать текстовый курсор видимым, следует вызвать функцию ShowCaret:

void WINAPI ShowCaret (HWND hwnd);

В качестве параметра этой функции передается идентификатор окна hwnd, создавшего текстовый курсор.

Перед тем как перерисовывать окно, приложение должно выключить (скрыть) текстовый курсор. Так как курсор постоянно мигает, если его не выключить во время перерисовки окна, изображение курсора может "размножаться" на экране. Функция BeginPaint самостоятельно скрывает курсор, но, если вы перерисовываете окно во время обработки других сообщений, курсор необходимо выключить (но не уничтожить!), вызвав функцию HideCaret:

void WINAPI HideCaret(HWND hwnd);

В качестве параметра этой функции передается идентификатор окна hwnd, создавшего текстовый курсор.

Функции ShowCaret и HideCaret обладают свойством накопления. Если вы несколько раз подряд вызвали функцию HideCaret, для того чтобы текстовый курсор вновь стал видимым, вам придется столько же раз вызвать функцию ShowCaret. Аналогично, если вы несколько раз вызвали функцию ShowCaret, для того чтобы скрыть текстовый курсор, вам придется столько же раз вызвать функцию HideCaret.

Но прежде чем включать курсор, вам необходимо установить его в нужное место окна (вы сами должны заботиться о правильном расположении текстового курсора). Для этого следует вызвать функцию SetCaretPos:

void WINAPI SetCaretPos(int x, int y);

Первый параметр этой функции определяет горизонтальную X-координату курсора, второй - вертикальную Y-координату курсора.

Для получения текущих координат текстового курсора следует воспользоваться функцией GetCaretPos:

void WINAPI GetCaretPos(POINT FAR* lppt);

Единственный параметр этой функции lppt указывает на структуру типа POINT, в которую будет записана информация о расположении курсора. Тип POINT описан в файле windows.h:

typedef struct tagPOINT { int x; int y; } POINT;

После возврата из функции GetCaretPos поля x и y структуры будут содержать соответственно X- и Y-координаты текстового курсора.



С помощью функций GetCaretBlinkTime и SetCaretBlinkTime приложение может соответственно узнать и изменить период мигания текстового курсора.

Функция GetCaretBlinkTime возвращает период мигания текстового курсора в миллисекундах:

UINT WINAPI GetCaretBlinkTime(void);

С помощью функции SetCaretBlinkTime вы можете установить новое значение периода мигания курсора, указав его в качестве параметра функции (также в миллисекундах):

void WINAPI SetCaretBlinkTime(UINT uMSeconds);

Управлять текстовым курсором непросто, особенно если учесть, что при редактировании текста могут быть использованы разные шрифты с переменной шириной букв (и даже с наклонными буквами). Но у вас едва ли появится необходимость создания собственного текстового редактора, аналогичного Microsoft Word for Windows (если, конечно, ваша основная работа не связана именно с созданием таких текстовых редакторов!).

Для редактирования и ввода отдельных символьных строк или многострочного текста без использования шрифтового оформления проще всего воспользоваться зарегистрированным операционной системой Windows классом окна edit.

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


Типы данных


Для разработки приложений Windows используется большое количество типов данных и констант, определенных в таких файлах, как windows.h, commdlg.h и других. Эти типы данных как бы замещают собой стандартные типы данных языка Си.

Такая замена позволяет отделить программный интерфейс Windows от самой операционной системы Windows, с одной стороны, и от конкретных реализаций компиляторов языка Си, с другой. Например, система разработки программ Borland C++ версии 3.1 трактует тип unsignedint как беззнаковое целое размером 16 бит. В файле windows.h определен тип UNIT, который для указанной выше системы разработки отображается на тип unsigned int :

typedef unsigned int UINT;

Однако при разработке 32-разрядных приложений (таких, как Windows NT) для 32-разрядных процессоров тип UINT должен иметь размер 32 бит. Если вы будете думать о типе UINT как о типе, который используется для представления беззнакового целого естественной для данного процессора длины, вы можете избежать ошибок, связанных с неправильным определением размера целого числа без знака.

Другой пример - дальний указатель на строку символов char _far *. Для разработки приложений Windows версии 3.1 в среде разработки Borland C++ вместо этого типа данных используется тип LPSTR, определенный следующим образом:

typedef char FAR* LPSTR;

Ключевое слово FAR определено так:

#define FAR _far

В 16-разрядной среде тип LPSTR отображается на дальний указатель, состоящий из селектора и смещения. В 32-разрядной среде при использовании сплошной (FLAT) модели памяти содержимое сегментных регистров устанавливается один раз при запуске приложения и в дальнейшем не изменяется (самим приложением). Для адресации в этом случае используется только 32-разрядная компонента смещения.

Если ваша программа использует тип LPSTR, при ее переносе в среду Windows NT вам не придется изменять исходные тексты, достаточно выполнить новую трансляцию. Для этой операционной системы ключевое слово FAR определено как пустое место:

#define FAR

Поэтому все дальние указатели, определенные с использованием этого ключевого слова, автоматически превратятся в ближние (что и требовалось).

Если бы вы определяли дальний указатель на строку символов как char _far *, вам бы пришлось удалять или переопределять ключевое слово _far.



Типы данных в файле windows.h


Файл windows.h должен включаться во все исходные файлы приложений Windows. Он содержит определение типов данных, символических имен констант и прототипы функций программного интерфейса Windows.

Для создания мобильных приложений, которые вы сможете перенести в среду Windows NT или аналогичную среду, поддерживающую программный интерфейс Windows, следует пользоваться не стандартными типами данных, реализованными в конкретной версии системы, а теми типами данных, которые определены в файле windows.h.

Этот файл содержит описание базовых типов и производных, созданных из базовых. Имена типов (как правило, это указатели) могут начинаться с префикса. Префикс LP означает дальний указатель (Long Pointer), префикс NP - ближний указатель (Near Pointer), и префикс P - указатель без определения типа. Для константных типов данных (определенных с ключевым словом const) после префикса добавляется буква "C", например, LPCSTR.

Приведем список базовых типов данных, определенных в файле windows.h.

Тип данных Определение типа в файле windows.h Описание
BOOL int Булевый (двоичный)
BYTE unsigned char Байт
WORD unsigned short Беззнаковое целое размером 16 бит
DWORD unsigned long Беззнаковое целое размером 32 бит
UINT unsigned int Беззнаковое целое естественного для данной системы размера

Заметим, что в Windows версии 3.1 изменилось определение типа данных WORD по сравнению с версией 3.0. В файле windows.h, предназначенном для разработки приложений Windows версии 3.0, тип данных WORD был определен следующим образом:

typedef unsigned int WORD; // Для Windows версии 3.0!

В обоих случаях (и для версии 3.0, и для версии 3.1) тип данных отображается на беззнаковое целое длиной 16 бит. Но для Windows NT типы данных unsigned int и unsigned short уже не эквивалентны. Использование вместо них типа данных WORD упростит задачу переноса исходных текстов приложений в 32-разрядную среду.

На основе приведенного выше набора базовых типов в файле windows.h определены производные типы, которые являются указателями:


Тип данных Определение типа в файле windows.h Описание
PSTR char NEAR * Ближний указатель на символ типа char
NPSTR char NEAR * Ближний указатель на символ типа char
LPSTR char FAR * Дальний указатель на символ типа char
LPCSTR const char FAR * Константный дальний указатель на символ типа char
PBYTE BYTE NEAR * Ближний указатель на байт
LPBYTE BYTE FAR * Дальний указатель на байт
PINT int NEAR * Ближний указатель на int
LPINT int FAR * Дальний указатель на int
PWORD WORD NEAR * Ближний указатель на беззнаковое целое размером 16 бит
LPWORD WORD FAR * Дальний указатель на беззнаковое целое размером 16 бит
PLONG long NEAR * Ближний указатель на знаковое целое размером 32 бит
LPLONG long FAR * Дальний указатель на знаковое целое размером 32 бит
PDWORD DWORD NEAR * Ближний указатель на беззнаковое целое размером 32 бит
LPDWORD DWORD FAR * Дальний указатель на беззнаковое целое размером 32 бит
LPVOID void FAR * Дальний указатель, для которого не определен тип данных
Файл windows.h содержит определения для многочисленных структур данных, таких, как POINT, RECT, TEXTMETRICS и т. п. Для всех структур данных определены указатели, например:

typedef struct tagPOINT { int x; int y; } POINT; typedef POINT* PPOINT; typedef POINT NEAR* NPPOINT; typedef POINT FAR* LPPOINT;

Тип данных PPOINT в зависимости от используемой модели памяти может быть как дальним, так и ближним указателем. Поэтому, для того чтобы избежать неоднозначность в тех случаях, когда вам нужен, например, дальний указатель, лучше воспользоваться типом LPPOINT.

Мы не будем перечислять все структуры данных, описанные в файле windows.h, так как их очень много. Вы можете посмотреть определения нужных вам структур непосредственно в файле windows.h, который всегда находится в каталоге с именем include.

Еще один важный тип данных, определенный в файле windows.h, - это различные идентификаторы (handle).

Для использования того или иного ресурса Windows вы должны получить идентификатор нужного вам ресурса.


Для получения идентификатора вызывается одна из функций программного интерфейса Windows. Например, для получения идентификатора контекста отображения можно воспользоваться функцией GetDC:

HDC hdc; hdc = GetDC(hwnd);

Само по себе полученное значение идентификатора не имеет для вас никакого смысла. Идентификатор должен использоваться только для ссылки на ресурс, например:

DrawText(hdc, (LPSTR)szBuf, nBufSize, &rc, DT_CENTER | DT_VCENTER | DT_NOCLIP | DT_SINGLELINE);

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

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

ReleaseDC(hwnd, hdc);

Приведем список некоторых типов идентификаторов ресурсов:

Тип идентификатора Описание
GLOBALHANDLE Идентификатор блока глобальной памяти
HACCEL Акселератор
HBITMAP Изображение в виде битового образа (bitmap)
HBRUSH Кисть
HCURSOR Курсор
HDC Контекст устройства
HDRVR Драйвер устройства
HFONT Шрифт
HGDIOBJ Объект графического интерфейса GDI
HGLOBAL Идентификатор блока глобальной памяти
HICON Пиктограмма
HLOCAL Идентификатор блока локальной памяти
HMENU Меню
HMETAFILE Метафайл
HPALETTE Палитра
HPEN Перо
HRGN Область
HRSRC Ресурс
HSTR Строка символов
HTASK Задача
HWND Окно
LOCALHANDLE Идентификатор блока локальной памяти
С некоторыми перечисленными выше идентификаторами вы уже знакомы, с некоторыми вам еще только предстоит познакомиться в следующих томах "Библиотеки системного программиста".


Управление курсором мыши с помощью клавиатуры


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

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

Главный файл приложения приведен в листинге 6.7.

Листинг 6.7. Файл mousekey\mousekey.cpp

// ---------------------------------------- // Управление курсором мыши при // помощи клавиатуры // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "MOUSEKEYAppClass"; char const szWindowTitle[] = "MOUSEKEY 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); }

Функция WinMain создает одно главное окно и не имеет никаких особенностей.

Функция главного окна приложения приведена в листинге 6.8.

Листинг 6.8. Файл mousekey\wndproc.cpp

#define STRICT #include <windows.h>

// Прототипы функций int max(int value1, int value2); int min(int value1, int value2);

// -------------------------------------------- // Функция max // -------------------------------------------- int max(int value1, int value2) { return((value1 > value2) ? value1 : value2); }

// -------------------------------------------- // Функция min // -------------------------------------------- int min(int value1, int value2) { return((value1 < value2) ? value1 : value2); }

// ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static POINT pt; RECT rc;

switch (msg) { // Нажали клавишу case WM_KEYDOWN: { // Получаем текущие экранные // координаты курсора мыши GetCursorPos(&pt);

// Преобразуем экранные координаты // в оконные координаты ScreenToClient(hwnd, &pt);

// Для клавиш позиционирования текстового // курсора изменяем соответствующим образом // координаты курсора мыши switch(wParam) { case VK_DOWN: // вниз { pt.y += 20; break; } case VK_UP: // вверх { pt.y -= 20; break; } case VK_LEFT: // влево { pt.x -= 20; break; } case VK_RIGHT: // вправо { pt.x += 20; break; } // Для всех остальных клавиш // ничего не делаем default: { return 0; } }

// Получаем координаты внутренней // области окна GetClientRect(hwnd, &rc);



// Вычисляем новые координаты курсора мыши // таким образом, чтобы курсор не выходил // за пределы окна pt.x = max(min(pt.x, rc.right), rc.left); pt.y = max(min(pt.y, rc.bottom), rc.top);

// Преобразуем оконные координаты в экранные ClientToScreen(hwnd, &pt);

// Устанавливаем курсор мыши // в новую позицию SetCursorPos(pt.x, pt.y);

return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

Обработчик сообщения WM_KEYDOWN вызывает функцию GetCursorPos, которая записывает текущие экранные координаты курсора мыши в структуру pt.

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

void WINAPI ScreenToClient(HWND hwnd, POINT FAR* lppt);

В качестве первого параметра функции (hwnd) указывается идентификатор окна, для которого выполняется преобразование. Второй параметр (lppt) является указателем на структуру типа POINT. В эту структуру записываются преобразованные координаты. В нашем случае это оконные координаты курсора мыши:

ScreenToClient(hwnd, &pt);

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

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

Для получения координат главного окна приложения вызывается функция GetClientRect:

GetClientRect(hwnd, &rc);

Она записывает сведения о расположении окна в структуру rc.



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

Для выполнения преобразования оконных координат курсора в экранные используется функция ClientToScreen:

void WINAPI ClientToScreen(HWND hwnd, POINT FAR* lppt);

Назначение параметров этой функции аналогично назначению параметров функции ScreenToClient.

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

SetCursorPos(pt.x, pt.y);

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

Листинг 6.9. Файл mousekey\mousekey.def

; ============================= ; Файл определения модуля ; ============================= NAME MOUSEKEY DESCRIPTION 'Приложение MOUSEKEY, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Управление оперативной памятью


Если вы помните, подсистема управления оперативной памятью в MS-DOS базируется на использовании блоков управления памятью MCB (см. первый том "Библиотеки системного программиста"). Такое "управление" памятью полностью основано на джентльменском соглашении между программами о сохранении целостности операционной системы, так как любая программа может выполнить запись данных по любому адресу. Программа может легко разрушить системные области MS-DOS или векторную таблицу прерываний.

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

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

Использование защищенного режима работы процессора обеспечивает приложениям Windows непосредственный доступ к расширенной памяти компьютера. С помощью системы управления памятью приложение может заказать для себя буфер очень большого размера. Физически этот буфер может находиться либо в расширенной памяти, либо в виртуальной. Можно также заказать небольшой буфер в стандартной памяти (ниже границы 1 Мбайт).

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

Другая особенность системы управления памятью в операционной системе Windows связана с управлением сегментами памяти, выделенными приложению.

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

Приложение Windows устроено сложнее и загружается по-другому. Как и программы MS-DOS, приложения Windows состоят из сегментов кода и сегментов данных. В зависимости от модели памяти приложение может иметь один или несколько сегментов кода и один или несколько сегментов данных.

Сегменты приложения Windows получают дополнительный атрибут - тип сегмента. Существуют сегменты с фиксированным расположением в оперативной памяти (fixed), перемещаемые (moveable) и удаляемые (discardable). В операционной системе MS-DOS нет аналога перемещаемым и сбрасываемым сегментам, так как при загрузке все сегменты располагаются по фиксированным (на время работы программы) адресам. Перемещаемые сегменты могут менять свое расположение в адресном пространстве. Управляет этим, незаметным для приложений, процессом операционная система Windows.

Для чего понадобились перемещаемые сегменты?

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



Удаляемые (discardable) сегменты обычно используются для хранения выполняемых сегментов или сегментов констант. Если операционной системе Windows требуется получить в свое распоряжение область памяти, она может уничтожить удаляемый сегмент и забрать распределенную для него память. Если впоследствии потребуется восстановить содержимое удаляемого сегмента, Windows выполняет чтение данных сегмента из соответствующего файла.

Помимо описанных выше атрибутов сегменты могут иметь еще два. Можно создать сегменты, загружаемые при запуске приложения (preload) и загружаемые при обращении к ним (loadoncall). Сегменты типа loadoncall не загромождают оперативную память, так как после запуска приложения они остаются на диске и загружаются в память только при необходимости. Причем при составлении программы вам достаточно описать сегмент как loadoncall, после чего Windows будет сама его загружать при обращении к сегменту со стороны приложения.

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


Управление программами


Подсистема управления программами в Windows обеспечивает запуск и одновременную работу нескольких программ. Программы, созданные специально для Windows, называются приложениями Windows (Windows application). В среде операционной системы Windows версии 3.1 одновременно может быть запущено несколько приложений Windows и несколько программ, созданных для MS-DOS.

Если Windows работает в расширенном режиме, для каждой программы MS-DOS при запуске создается отдельная виртуальная машина. При выполнении программы MS-DOS процессор работает в режиме виртуального процессора 8086, поэтому все работающие параллельно на разных виртуальных машинах программы MS-DOS изолированы друг от друга.

Если Windows работает в стандартном режиме, запуск программы MS-DOS приводит к приостановке выполнения приложений Windows и выгрузке содержимого оперативной памяти на диск. Процессор переходит в реальный режим работы. Соответствующей настройкой системы запуска программ MS-DOS можно добиться этого и в расширенном режиме работы Windows.

Для всех приложений Windows в расширенном режиме работы создается одна виртуальная машина, причем процессор работает в защищенном режиме. Все приложения Windows используют для адресации памяти одну локальную таблицу дескрипторов LDT, что может привести к взаимному влиянию приложений друг на друга. В этом смысле программы MS-DOS, работающие в среде Windows, лучше защищены друг от друга, чем приложения Windows, так как адресные пространства виртуальных машин MS-DOS изолированы друг от друга (за исключением начала адресного пространства, занятого резидентными программами и драйверами, загруженными до запуска Windows).

Подсистема управления программами не обеспечивает квантования времени между приложениями Windows, но делает это для запущенных одновременно программ MS-DOS. Приложения Windows сделаны таким образом, что они сами "добровольно" отдают друг другу процессорное время, обеспечивая так называемую невытесняющую мультизадачность (nonpreemptive multitasking).

Операционная система Windows NT может выполнять обычные программы MS-DOS, приложения Windows версии 3.1 и новые, 32-разрядные приложения, созданные специально для Windows NT. Для каждого 32-разрядного приложения Windows NT создает отдельную виртуальную машину. Благодаря этому приложения Windows NT изолированы друг от друга. Использование отдельных виртуальных машин позволяет реализовать для 32-разрядных приложений Windows NT вытесняющую мультизадачность (preemptive multitasking) с выделением каждому приложению квантов времени.



Управление шрифтами


Операционная система MS-DOS не содержит никакой серьезной поддержки для работы со шрифтами. В то же время Windows версии 3.1 использует передовую технологию масштабируемых шрифтов TrueType. Из руководства пользователя Windows вы знаете, что шрифты TrueType сохраняют свой внешний вид при изменении высоты букв. Поэтому они и называются масштабируемыми.

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

Система управления шрифтами, встроенная в Windows, позволяет реализовать на практике режим редактирования документов, который носит название WYSIWYG - What You See Is What You Get (что вы видите, то и получите). Например, если вы используете для редактирования документов такой текстовый процессор, как Microsoft Word for Windows, вы можете выбрать для оформления любой из масштабируемых шрифтов. Система управления шрифтами обеспечит соответствие изображения текста на экране распечатке, полученной на принтере (особенно хорошие результаты получатся при использовании видеомонитора с высоким разрешением, а также струйного или лазерного принтера).



Временные (pop-up) окна


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

Временные окна имеют стиль WS_POPUP, определенный в файле windows.h следующим образом:

#define WS_POPUP 0x80000000L

Временные окна, в отличие от перекрывающихся, могут не иметь заголовок (title bar). Если для временного окна определен заголовок, оно может иметь и системное меню. Часто для создания временных окон, имеющих рамку, используется стиль WS_POPUPWINDOW, определенный в файле windows.h следующим образом:

#define WS_POPUPWINDOW (WS_POPUP | WS_BORDER | WS_SYSMENU)

Если надо добавить к временному окну системное меню и заголовок, стиль WS_POPUPWINDOW следует использовать в комбинации со стилем WS_CAPTION, добавляющим заголовок.

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

Начало системы координат, используемой при создании временных окон, находится в левом верхнем углу экрана. Поэтому при создании временных окон используются экранные координаты (так же, как и при создании перекрывающихся окон).

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

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



Вставка и привязка объектов OLE


Операционная система Windows содержит сложный механизм вставки и привязки объектов OLE (Object Linking and Embedding), обеспечивающий интеграцию приложений на уровне объектов, таких, как документы, графические изображения или электронные таблицы.

При использовании OLE документ, подготовленный, например, текстовым процессором Microsoft Word for Windows, может содержать в себе как объект изображение, созданное графическим редактором Paint Brush. Для редактирования такого объекта из среды текстового процессора Microsoft Word for Windows вызывается приложение Paint Brush, причем результат редактирования записывается обратно в тело документа.



Библиотеки системного программиста" были посвящены


Первые десять томов " Библиотеки системного программиста" были посвящены в основном аппаратуре компьютера, операционной системе MS-DOS и сетевым средствам, таким, как операционная система Novell NetWare. Теперь настало время приступить к изучению операционной системы Microsoft Windows - самой популярной среды для персональных компьютеров середины 90-х годов.
Операционная система Microsoft Windows по своим возможностям не только намного превосходит MS-DOS, но и даже просто не сравнима с MS-DOS. Удобный, хорошо продуманный, а главное, стандартизованный для всех программ Windows пользовательский интерфейс интуитивно ясен и удобен для изучения. Поэтому после появления Microsoft Windows версии 3.0 всего за несколько лет произошла настоящая революция прикладного программного обеспечения персональных компьютеров - практически все основные программные продукты были переделаны для работы в среде Windows. При этом они приобрели свойства и возможности, недостижимые ранее из-за ограничений, накладываемых операционной системой MS-DOS.
Но к сожалению, программирование для Windows - гораздо более трудоемкое и кропотливое занятие, чем программирование для MS-DOS. Это связано, в первую очередь, с обилием новых возможностей. Для создания пользовательского интерфейса и выполнения других задач вам предлагается набор из более чем тысячи функций! Если же вы собираетесь работать с мультимедиа или другими дополнительными подсистемами Windows, набор функций еще больше расширяется.
На помощь программисту приходит объектно-ориентированный подход и наборы классов, поставляемых в комплекте со средствами разработки программ или отдельно. Есть автоматизированные генераторы программ, почти или полностью исключающие программирование на каком бы то ни было процедурном языке. При этом сложность задачи сокращается в десятки и сотни раз, правда, иногда ценой потери эффективности и производительности программы.
Впрочем, вы всегда можете комбинировать разные подходы при разработке своего проекта и использовать в одном проекте средства разного уровня - от библиотек классов и генераторов программ до модулей, составленных на языке ассемблера.
При этом может быть достигнут компромисс между сроками разработки и отладки программы, с одной стороны, и рабочими характеристиками программы, с другой стороны.
Наш подход в изложении материала заключается в том, что вначале мы рассмотрим принципы работы операционной системы Windows и научимся использовать программный интерфейс (API) Windows. При этом все программы, которые мы приводим в качестве примеров, будут составлены на языке программирования C++.
После изучения программного интерфейса Windows мы займемся библиотекой классов Object Windows Library, которая поставляется в комплекте с трансляторами Borland C++. Эта библиотека упрощает процесс создания сложных программ, однако для ее эффективного использования вы уже должны владеть всеми основными понятиями Windows.
Не останется без внимания новый 32-разрядный программный интерфейс Win32s, который используется в Windows версии 3.1. В следующую версию Windows, предположительно называемую Windows 4.0, будет встроен интерфейс Win32c - надмножество интерфейса Win32s. Интерфейсы Win32s и Win32c являются подмножествами интерфейса Win32 другой операционной системы - Windows NT.
И конечно, мы обязательно рассмотрим новое направление в использовании персональных компьютеров - мультимедиа. Вы, в частности, научитесь составлять программы, умеющие работать со звуком и проигрывать звуковые компакт-диски.
Разумеется, мы не можем изложить весь этот материал в одном или двух томах, поэтому будьте готовы к длительной работе. Мы надеемся, что процесс изучения, а также процесс составления программ для Windows будет приятен и вы не только испытаете головную боль (чего никак не избежать!), но и получите удовлетворение.
Что вам нужно уметь и иметь, для того чтобы приступить к работе?
Вы должны свободно владеть языком программирования C++. В настоящее время есть много книг, посвященных C++. Поэтому, если вы раньше использовали C, затратив две-три недели, вы без труда научитесь использовать основные возможности C++. Мы, в свою очередь, будем стараться не злоупотреблять различными "тонкими моментами".


Хотя это может показаться странным, от вас не потребуется глубоких знаний прерываний BIOS или DOS. Дело в том, что в программах, рассчитанных на работу в среде Windows, эти прерывания используются крайне редко, практически они нужны только в некоторых специальных случаях. Но вы обязательно должны представлять себе архитектуру персонального компьютера и знать принципы работы всех его устройств.
Так как операционная система Windows работает в защищенном режиме, мы рекомендуем вам обратиться к шестому тому "Библиотеки системного программиста", который называется "Защищенный режим работы процессоров Intel 80286/80386/80486". Вы должны знать механизм адресации оперативной памяти и механизм обработки прерываний, а также исключений в защищенном режиме работы процессора.
Windows является графической операционной системой, поэтому мы будем уделять много внимания выводу изображений на экран. И хотя вы не будете непосредственно программировать видеоадаптер, для того чтобы эффективно использовать возможности видеоадаптера, полезно знать основные принципы его работы. Вы можете обратиться к третьему тому "Библиотеки системного программиста", который называется "Программирование видеоадаптеров CGA, EGA и VGA". В этом томе мы рассмотрели основные принципы работы видеоадаптеров и приемы программирования.
Из-за ограниченного объема книги мы не в состоянии рассказать вам о работе пользователя в операционной системе Windows. Поэтому мы предполагаем, что вы уже умеете работать в этой операционной системе как квалифицированный пользователь.
Для отладки примеров программ авторы использовали попеременно два компьютера - один на базе процессора 80386DX-40, второй - на базе процессора 80486DX-33 с оперативной памятью соответственно 8 и 16 Мбайт и с объемом дисковой памяти 240 и 1300 Мбайт. Работа программ проверялась с видеоадаптерами OAK и Cirrus Logic с объемом памяти 1 Мбайт, причем последний представляет из себя акселератор Windows, способный работать в режиме True Color (16,7 млн.


цветов). Для работы с мультимедиа мы использовали набор Multimedia Upgrade Kit "Sound Galaxy NX Pro(M)", в который входит 8-битовая стереофоническая звуковая плата, дисковод CD-ROM, микрофон и колонки.
Минимальные требования к компьютеру определяются, в основном, используемым транслятором. Мы пользовались транслятором Borland C++ for Windows версии 3.1. Для этого транслятора вполне достаточно, если ваш компьютер будет иметь 4 Мбайт оперативной памяти, причем в качестве процессора лучше использовать Intel 80386 или Intel 80486. Большинство программ будет работать и на процессоре 80286, но с таким процессором вы не сможете использовать расширенный режим работы Windows и виртуальную память.
Для трансляции программ, приведенных в этой книге, вы также можете воспользоваться средой Borland Turbo C++ for Windows версии 3.1 или Microsoft Visual C++ версии 1.0. Возможно также использование среды Borland C++ for Windows версии 4.0 или Symantec C++ версии 6.0.
Мы настоятельно рекомендуем использовать видеоадаптер VGA или SVGA. В этом случае у вас не будет проблем с отладчиком из поставки Borland C++ версии 3.1. Разработчики этого отладчика, вероятно, уже давно забыли о существовании таких видеоадаптеров, как CGA или EGA, поэтому с ними отладчик не работает. Кроме того (и это достаточно сильный аргумент), при использовании устаревших видеоадаптеров вы не сможете проверить работу своей программы во всех режимах, то есть вы не сможете ее полностью отладить.
Профессиональные программисты для разработки программ Windows иногда используют системы на базе двух компьютеров, соединенных нуль-модемным кабелем или объединенных в локальную сеть. При этом на одном компьютере работает отладчик, а на другом - отлаживаемая программа. Такой подход дает наибольшую гибкость и производительность при отладке. Начинающим программистам мы рекомендуем использовать один компьютер и одноэкранный отладчик, который входит в комплект Borland C++.
Немного о структуре книги.
В первой главе мы расскажем об основах операционной системы Microsoft Windows.


Будут описаны основные компоненты и подсистемы Windows. Мы приведем в качестве примеров исходные тексты простейших приложений, предназначенных для работы в среде этой операционной системы.
Вторая глава посвящена выводу текста в окна, создаваемые приложениями Windows. Вывод текста в среде Windows должен выполняться при помощи специально предназначенных для этого функций программного интерфейса Windows. Методы, которые вы использовали при создании программ MS-DOS, непригодны для приложений Windows.
Третья глава - о создании окон. Вы узнаете о стилях класса окна и о стилях окна. Мы познакомим вас с основными стилями, которые встречаются практически во всех приложениях Windows. Это перекрывающиеся (overlapped), временные (pop-up) и дочерние (child) окна. Мы приведем пример приложения, создающего окна с перечисленными выше стилями.
В четвертой главе описаны функции, позволяющие получить информацию о метриках объектов Windows, таких, как окна, пиктограммы и т. п., а также о возможностях устройств ввода/вывода.
В пятой главе мы расскажем о том, как приложения Windows работают с клавиатурой. Вы узнаете о типах сообщений, генерируемых клавиатурой, о таблицах кодировки MS-DOS и Windows, о том, как приложения Windows работают с текстовым курсором.
Шестая глава посвящена мыши. Мы опишем сообщения, поступающие от мыши, расскажем о том, как управлять из приложения курсором мыши и как дублировать работу мыши при помощи клавиатуры.
В седьмой главе описаны приемы работы с таймером.
В приложениях мы расскажем о включаемом файле windows.h, который используется при создании любых приложений Windows, расскажем о типах данных и именах констант, определенных в файле windows.h, а также о системе обозначений типов, принятой для имен параметров функций и переменных. Мы также расскажем об особенностях различных моделей памяти, об использовании символов кириллицы. Будет описан интерфейс EasyWin, упрощающий перенос программ из среды MS-DOS в среду Windows. Отдельный раздел приложения посвящен отладке приложений Windows.
Мы рекомендуем вам в процессе работы над книгой изучать приведенные примеры приложений, исследуя их непосредственно на компьютере. Вместе с книгой продается дискета, содержащая исходные тексты всех приложений.
Авторы выражают благодарность за ценные советы по содержанию книги Синеву Максиму и Ноженко Сергею. Мы также благодарим сотрудников издательского отдела "Диалог-МИФИ": Голубева Олега Александровича, Дмитриеву Наталью, Виноградову Елену, Кузьминову Оксану. Наша особая благодарность корректору Кустову Виктору.

Вывод текста в окно


2.1.

2.2.

2.3.

2.4.

2.5.

2.6.

2.7.

В предыдущей главе мы привели пример приложения, создающего единственное окно. Однако это окно так и осталось пустым, потому что мы еще не умеем выводить в него ни текст, ни графические изображения. К сожалению, вы не сможете воспользоваться для вывода текста ни одной из хорошо известных вам стандартных функций библиотеки компилятора, такой, как printf, cprintf или putc. Не помогут вам и прерывания BIOS, так как приложениям Windows запрещено их использовать, во всяком случае для вывода на экран. Все эти функции ориентированы на консольный вывод в одно-единственное окно, предоставленное в полное распоряжение программе MS-DOS.

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

Способ, которым приложение выводит что-либо в свои окна, коренным образом отличается от используемого в программах MS-DOS.

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

Приложения Windows также могут выводить в созданные ими окна текст или графические изображения в любой момент времени и из любого места. Однако обычно разработчики поступают по-другому.

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

наша первая книга, посвященная программированию


Итак, наша первая книга, посвященная программированию для операционной системы Microsoft Windows, подошла к концу. Как мы и обещали, в первой книге мы рассмотрели только самые основы. Вы научились создавать простейшие приложения Windows, знаете, что такое окна и умеете выводить в них текст. Но все это - только начало. Мы пока не затронули такие важнейшие компоненты Windows, как органы управления, диалоговые панели, ресурсы. Мы пока не научились рисовать в окне графические изображения. Наши приложения не имеют меню и справочной системы. Мы ничего не рассказали вам о выводе битовых графических изображений (bitmap).
Можно еще долго перечислять, о чем мы вам еще не рассказали. Но, согласно Козьме Пруткову, "нельзя объять необъятного". В нашей первой книге мы и не пытались это сделать. Постепенно мы рассмотрим все основные аспекты программирования для Windows.

Завершение работы приложения


Приложение обычно завершает свою работу, когда вы нажимаете комбинацию клавиш <Alt+F4> или выбираете строку "Close" в системном меню главного окна приложения. При этом в его функцию окна попадает сообщение WM_DESTROY. В ответ на это сообщение функция окна помещает в очередь сообщение WM_QUIT, вызывая для этого функцию PostQuitMessage. Как вы уже знаете, выборка этого сообщения приводит к завершению цикла обработки сообщений и, следовательно, к завершению работы приложения.

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