лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdfМетафайлы |
211 |
|
|
|
|
Если метафайл находится на диске, то сначала нужно получить его дескриптор. Это можно сделать с помощью функции GetEnhMetaFileHeader:
HENHMETAFILE GetEnhMetaFile(LPCTSTR lpszMetaFile);
Функция возвращает искомый дескриптор по заданному имени файла lpszMetaFile. Файл должен находиться в текущем каталоге, в противном случае строка lpszMetaFile должна содержать полный путь к файлу.
Остается последний вопрос: как получить размеры ограничивающего прямоу гольника для передачи третьему параметру функции PlayEnhMetaFile? Здесь нас вы ручит функция GetEnhMetaFileHeader, читающая заголовок метафайла в структуру типа ENHMETAHEADER, так как поле rclBounds этой структуры как раз и содержит не обходимую информацию.
В листинге 3.10 приведен код приложения, которое воспроизводит созданный ранее метафайл Pict1.emf. Перед запуском приложения нужно скопировать этот файл в каталог проекта PlayMetaFile.
Листинг 3.10. Проект PlayMetaFile
//////////////////////////////////////////////////////////////////////
//PlayMetaFile.cpp
#include <windows.h> #include "KWnd.h"
#define FILE_NAME "Pict1.emf"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
KWnd mainWnd("PlayMetaFile", hInstance, nCmdShow, WndProc);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC; PAINTSTRUCT ps; RECT rect; HENHMETAFILE hemf; ENHMETAHEADER emh; int x1, y1, x2, y2;
switch (uMsg)
{
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
hemf = GetEnhMetaFile(FILE_NAME);
продолжение
212 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
Листинг 3.10 (продолжение) |
|
if |
(!hemf) |
MessageBox(hWnd, "Файл " FILE_NAME " не найден.", "Error", MB_OK); else {
GetEnhMetaFileHeader(hemf, sizeof(ENHMETAHEADER), &emh); x1 = emh.rclBounds.left; y1 = emh.rclBounds.top;
x2 = emh.rclBounds.right; y2 = emh.rclBounds.bottom; SetRect(&rect, x1, y1, x2, y2);
PlayEnhMetaFile(hDC, hemf, &rect); DeleteEnhMetaFile(hemf);
}
EndPaint(hWnd, &ps); break;
case WM_DESTROY: PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//////////////////////////////////////////////////////////////////////
Рекомендуем вам поэкспериментировать с масштабированием воспроизводи мого метафайла, заменяя инструкцию
SetRect(&rect, x1, y1, x2, y2);
одним из следующих вариантов:
SetRect(&rect, x1, y1, 2 * x2, y2); SetRect(&rect, x1, y1, x2, 2 * y2); SetRect(&rect, x1, y1, 2 * x2, 2 * y2); SetRect(&rect, x1, y1, 0.5* x2, 0.5 * y2);
и наблюдая, как будет изменяться выводимое изображение.
Клавиатура |
213 |
4Средства ввода
Приложение, работающее под управлением системы Windows, получает данные от пользователя через устройства ввода, главными из которых являются клавиату ра и мышь. Когда сообщения от клавиатуры или мыши воспринимаются стандарт ными элементами управления, такими как кнопки, поля редактирования и меню, то обработка этих событий реализована в соответствующих оконных классах. Од нако иногда необходимо непосредственно обрабатывать сообщения от клавиатуры или мыши. Например, это нужно в приложениях, воспринимающих пользователь ский ввод или в главном окне, или в одном из дочерних окон. В Win32 API предус мотрен широкий набор функций для управления клавиатурой и мышью.
Клавиатура
Windows обеспечивает поддержку клавиатуры, независимую от аппаратуры и от на ционального языка, используемого в системе. Независимость от аппаратуры дос тигается благодаря использованию программного драйвера клавиатуры. Незави симость от национального языка реализуется выбором нужной кодовой страницы1.
На рис. 4.1 приведена схема обработки сообщения от клавиатуры, используе мая в операционной системе.
Рис. 4.1. Модель клавиатурного ввода
1Вы можете сменить кодовую страницу, запустив системное приложение «Клавиатура» в папке «Па нель управления» и выбрав закладку «Языки и раскладки».
214 |
Глава 4. Средства ввода |
|
|
Каждой клавише на клавиатуре соответствуют два уникальных целых числа, называемых скан кодом нажатия и скан кодом отпускания клавиши. Скан код (scan code) является аппаратно зависимым, то есть на клавиатурах разных производите лей одна и та же клавиша может иметь разные скан коды1. Когда пользователь на жимает или отпускает клавишу, драйвер клавиатуры получает соответствующие скан коды. Полученный скан код преобразуется драйвером в виртуальный код кла виши (virtual key code). Этот код является уже аппаратно независимым, поэтому в системе Windows он однозначно идентифицирует нажатую или отпущенную кла вишу. Завершающим действием драйвера является создание сообщения, содержа щего скан код, виртуальный код клавиши и другую информацию о нажатой/отпу щенной клавише. Это сообщение помещается в системную очередь сообщений.
Windows извлекает сообщение из системной очереди и передает его в очередь сообщений того приложения, которое имеет активное окно.
Далее сообщение извлекается в известном вам цикле обработки сообщений, поступая в конечном счете на обработку в соответствующую оконную процедуру.
Фокус ввода
Все приложения, работающие одновременно под управлением Windows, должны иметь возможность получать данные от клавиатуры. Если какое то приложение имеет более одного окна, то клавиатура должна разделяться и между окнами
врамках одного приложения. Это разделение Windows реализует, используя по нятие фокуса ввода. Фокус ввода (keyboard focus) — это временное свойство окна, которое означает, что все сообщения от клавиатуры направляются именно этому окну. В каждый момент времени из всех окон, имеющихся на экране, только одно окно может иметь фокус ввода.
Понятие фокуса ввода тесно связано с понятием активного окна. Активное окно — это окно верхнего уровня, с которым в данный момент работает пользова тель. Система выделяет текст заголовка активного окна, поэтому отыскать его на экране довольно просто. Если активное окно минимизировано, то Windows выде ляет текст заголовка на панели задач.
Фокусом ввода всегда владеет либо активное окно, либо одно из его дочерних окон. Часто дочерними окнами являются элементы управления — кнопки, пере ключатели, флажки, текстовые поля и списки, которые обычно размещаются
вокне диалога. Элементы управления по разному показывают, что они находятся
вфокусе. Так, вокруг текста кнопки выводится точечная линия, а текстовое поле показывает, что оно получило фокус ввода, при помощи мигающего курсора.
Если активное окно минимизировано, то окна с фокусом ввода нет. Windows продолжает пересылать программе сообщения клавиатуры, но эти сообщения выглядят иначе, чем сообщения, направляемые активным неминимизированным окнам.
Обрабатывая сообщения WM_SETFOCUS и WM_KILLFOCUS, оконная процедура мо жет определить текущий статус связанного с ней окна. Первое сообщение по казывает, что окно получило фокус ввода, второе — что окно потеряло фокус ввода.
1По этой причине используется также термин «скан код OEM» (Original Equipment Manufacturer Scan Code).
Клавиатура |
215 |
|
|
|
|
Для работы с фокусом ввода предусмотрены следующие функции:
Функция HWND SetFocus(HWND hWnd) устанавливает фокус ввода на окно hWnd, возвращая дескриптор окна, которое располагало фокусом до вызова функции.
Функция HWND GetFocus() возвращает дескриптор окна, имеющего фокус ввода в текущий момент.
Клавиши и символы
Клавиатура всегда генерирует некоторые числовые коды, но ее можно рассматри вать либо как совокупность отдельных физических клавиш, либо как средство генерации кодов символов.
В первом случае любой генерируемый код должен идентифицировать клави шу и показывать, нажата она или отпущена. Во втором варианте код, вырабатыва емый при нажатии клавиши, идентифицирует уникальный символ из набора сим волов. По умолчанию используется набор символов ANSI1.
У многих клавиш современного компьютера нет кодов символов. Ни функцио нальные клавиши, ни клавиши управления курсором их не генерируют. Поэтому в программах, использующих ввод с клавиатуры нетривиальным способом, обыч но приходится иметь дело с клавиатурой и как с совокупностью клавиш, и как с генератором символов одновременно.
Клавиши можно разделить на следующие четыре группы:
Клавиши переключатели — Caps Lock, Num Lock, Scroll Lock и в некоторых случа ях клавиша Insert. При нажатии такой клавиши включается ее состояние, а при повторном нажатии — выключается. Как правило, на клавиатуре есть свето вые индикаторы состояния клавиш переключателей.
Клавиши управления регистром — Shift, Ctrl и Alt. В нажатом состоянии такая клавиша меняет интерпретацию других клавиш. Иногда такие клавиши назы вают клавишами модификаторами.
Клавиши, не генерирующие символов, — функциональные клавиши, клавиши управления курсором, Pause, Delete.
Символьные клавиши — буквы, цифры и другие символы, пробел, Tab, Backspace, Esc и Enter. Впрочем, клавиши Tab, Backspace, Esc и Enter также можно рассмат ривать как клавиши без символов.
Сообщения клавиатуры, которые приложение получает от Windows, можно разделить на «аппаратные» (keystrokes) и «символьные» (characters). Это раз деление соответствует указанной выше двойственной интерпретации клавиа туры.
Аппаратные сообщения
Аппаратные сообщения от клавиатуры приведены в табл. 4.1. Обычно различают системные и несистемные сообщения. Для системных сообщений используется префикс SYS в составе идентификатора кода сообщения.
1 См. главу 2, раздел «Рисование текста».
216 |
|
Глава 4. Средства ввода |
|
|
|
Таблица 4.1. Аппаратные сообщения от клавиатуры |
|
|
|
|
|
Категория |
Клавиша нажата |
Клавиша отпущена |
|
|
|
Несистемные сообщения |
WM_KEYDOWN |
WM_KEYUP |
Системные сообщения |
WM_SYSKEYDOWN |
WM_SYSKEYUP |
|
|
|
Системные сообщения WM_SYSKEYDOWNи WM_SYSKEYUP, вырабатываемые обычно при нажатии клавиш в сочетании с клавишей Alt, более важны для Windows, чем для приложения. Эти сообщения вызывают команды программного или систем ного меню. Они также используются для вызова системных функций, таких как смена активного окна (Alt + Tab), или как быстрые клавиши системного меню (Alt в сочетании с функциональной клавишей). Поскольку система Windows самосто ятельно отрабатывает всю логику Alt клавиш, то вам, фактически, не нужно обра батывать эти сообщения. Ваша оконная процедура в конце концов получит дру гие сообщения, являющиеся следствием этих аппаратных сообщений клавиатуры (например, выбор команды меню).
Обычно сообщения о нажатии и отпускании клавиши появляются парами. Однако если пользователь оставит клавишу нажатой так долго, что включится автоповтор, то Windows пошлет оконной процедуре серию сообщений WM_KEYDOWN и одно сообщение WM_KEYUP, когда, в конце концов, клавиша будет отпущена.
Для всех аппаратных сообщений клавиатуры 32 разрядная переменная lParam, передаваемая в оконную процедуру, содержит 6 полей (табл. 4.2).
Таблица 4.2. Интерпретация полей параметра lParam
Разряды |
Назначение |
Описание |
|
|
|
|
|
15 |
. . . 0 |
Счетчик повторений |
Число повторений данного сообщения. Равно либо единице, |
|
|
|
либо числу нажатий в режиме автоповтора, когда пользо- |
|
|
|
ватель удерживает клавишу нажатой. Для сообщения |
|
|
|
WM_KEYUP всегда равен единице |
23 |
. . . 16 |
Cêàí-êîä |
Восьмибитный скан-код OEM |
24 |
|
Флаг расширенной |
Устанавливается в 1 для дополнительных клавиш расши- |
|
|
клавиатуры |
ренной клавиатуры IBM (функциональные клавиши в верх- |
|
|
|
ней части клавиатуры, клавиши Alt и Ctrl на правой |
|
|
|
стороне клавиатуры и некоторые другие клавиши) |
29 |
|
Код контекста |
Устанавливается в 1, если нажата клавиша Alt (для сооб- |
|
|
|
щений WM_KEYDOWN и WM_KEYUP всегда равен 0 ) |
30 |
|
Флаг предыдущего |
Единица, если клавиша была нажата, и нуль, если клавиша |
|
|
состояния клавиши |
была отпущена |
31 |
|
Флаг нового |
|
|
|
состояния клавиши |
Нуль, если клавиша нажата, и единица, если клавиша |
|
|
|
отпущена |
|
|
|
|
Хотя некоторая информация в lParam при обработке аппаратных сообщений может оказаться полезной, гораздо более важен параметр wParam, содержащий виртуальный код клавиши.
Виртуальный код клавиши (virtual key code) идентифицирует нажатую или от пущенную клавишу и является аппаратно независимым. Идентификаторы вир туальных клавиш определены в файле winuser.h. В табл. 4.3 приведены наиболее часто используемые коды.
Клавиатура |
|
|
217 |
|
|
|
|
|
|
|
Таблица 4.3. Виртуальные коды клавиш |
|
|
|
|
|
|
|
|
|
Десятичное |
Шестнадцатеричное |
Идентификатор |
Клавиатура IBM |
значение |
значение |
|
|
|
|
|
|
|
|
3 |
03 |
VK_CANCEL |
Ctrl + Break |
|
8 |
08 |
VK_BACK |
Backspace |
|
9 |
09 |
VK_TAB |
Tab |
|
13 |
0D |
VK_RETURN |
Enter |
|
16 |
10 |
VK_SHIFT |
Shift |
|
17 |
11 |
VK_CONTROL |
Ctrl |
|
18 |
12 |
VK_MENU |
Alt |
|
19 |
13 |
VK_PAUSE |
Pause |
|
20 |
14 |
VK_CAPITAL |
Caps Lock |
|
27 |
1B |
VK_ESCAPE |
Esc |
|
32 |
20 |
VK_SPACE |
Пробел |
|
33 |
21 |
VK_PRIOR |
Page Up |
|
34 |
22 |
VK_NEXT |
Page Down |
|
35 |
23 |
VK_END |
End |
|
36 |
24 |
VK_HOME |
Home |
|
37 |
25 |
VK_LEFT |
Стрелка влево |
|
38 |
26 |
VK_UP |
Стрелка вверх |
|
39 |
27 |
VK_RIGHT |
Стрелка вправо |
|
40 |
28 |
VK_DOWN |
Стрелка вниз |
|
44 |
2C |
VK_SNAPSHOT |
Print Screen |
|
45 |
2D |
VK_INSERT |
Insert |
|
46 |
2E |
VK_DELETE |
Delete |
|
|
48…57 |
30…39 |
|
0…9 (на основной |
|
|
|
|
клавиатуре) |
|
65…90 |
41…5A |
|
A…Z |
|
112…123 |
70…7B |
VK_F1…VK_F12 |
F1…F12 |
144 |
90 |
VK_NUMLOCK |
Num Lock |
|
145 |
91 |
VK_SCROLL |
Scroll Lock |
|
|
|
|
|
|
Обычно оконная процедура обрабатывает только небольшую часть из всех ап паратных сообщений клавиатуры, игнорируя остальные. Например, она может обрабатывать только те сообщения WM_KEYDOWN, которые содержат виртуальные коды для клавиш управления курсором, клавиши Shift и функциональных кла виш. В то же время аппаратные сообщения для символьных клавиш могут игно рироваться. Дело в том, что ввод информации от символьных клавиш удобнее обрабатывать, используя символьное сообщение WM_CHAR.
Символьные сообщения
Когда нажимается символьная клавиша, генерируемый код зависит от многих факторов. На его формирование влияет положение клавиш переключателей и клавиш управления регистром, а также текущая кодовая страница, установлен
218 |
Глава 4. Средства ввода |
|
|
ная в системе. Все эти факторы учитывает функция TranslateMessage, вызываемая в цикле обработки сообщений, который находится в теле функции WinMain и име ет вид
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
Напомним, что функция GetMessage извлекает очередное сообщение из очере ди и помещает его в структурную переменную msg, а функция DispatchMessage вы зывает соответствующую оконную процедуру.
Между этими двумя функциями располагается вызов функции TranslateMessage, которая преобразует аппаратные сообщения клавиатуры в символьные сообще ния. Если аппаратным сообщением является WM_KEYDOWN, а нажатая клавиша
всочетании с положением клавиш модификаторов соответствует некоторому сим волу, то TranslateMessage помещает символьное сообщение в очередь сообщений.
Врезультате системной обработки сообщения WM_KEYDOWN может быть ини циировано символьное сообщение WM_CHAR или WM_DEADCHAR.
Стоит привести краткие пояснения о назначении сообщения WM_DEADCHAR. На некоторых неамериканских клавиатурах имеются так называемые немые кла виши (dead keys), предназначенные для ввода диакритических знаков, таких как «умляут», например. Диакритические знаки вводятся в паре с обычными симво лами, в результате чего формируется составной символ. Например, для ввода на немецкой клавиатуре символа «U» (U умляут) пользователь сначала нажимает немую клавишу «умляут», а затем — клавишу U. К счастью, вам не нужно обраба тывать в вашей программе сообщение WM_DEADCHAR, поскольку Windows берет это на себя, вырабатывая в результате сообщение WM_CHAR, которое содержит ANSI код буквы с диакритическим знаком.
Таким образом, обработка символьных сообщений обычно сводится к обра ботке сообщения WM_CHAR. Параметр lParam сообщения WM_CHAR, передаваемый
воконную процедуру, имеет то же значение, что и параметр lParam породившего его аппаратного сообщения. Параметр wParam содержит код символа ANSI.
Символьные сообщения передаются в оконную процедуру в промежутке меж ду аппаратными сообщениями клавиатуры. Например, если переключатель Caps Lock выключен1, а пользователь нажимает и отпускает клавишу A, то оконная про цедура получит следующие три сообщения:
Сообщение |
Клавиша или код |
|
|
WM_KEYDOWN |
Виртуальная клавиша A |
WM_CHAR |
ANSI-код символа ‘a’ |
WM_KEYUP |
Виртуальная клавиша A |
|
|
Если пользователь, удерживая клавишу Shift, нажимает клавишу A, отпускает клавишу A и затем отпускает клавишу Shift, то оконная процедура получит пять сообщений:
1Состояние клавиши переключателя Caps Lock контролируется при помощи светового индикатора <CapsLock>.
Клавиатура |
219 |
|
|
|
|
|
|
|
Сообщение |
Клавиша или код |
|
|
|
|
|
WM_KEYDOWN |
Виртуальная клавиша VK_SHIFT |
|
WM_KEYDOWN |
Виртуальная клавиша A |
|
WM_CHAR |
ANSI-код символа A |
|
WM_KEYUP |
Виртуальная клавиша A |
|
WM_KEYUP |
Виртуальная клавиша VK_SHIFT |
|
|
|
Работа с кареткой
Каретка — это небольшой символ в виде вертикальной или горизонтальной черты. Иногда каретка отображается в виде прямоугольника, который показы вает пользователю место на экране, где будет отображен очередной символ, вво димый с клавиатуры. Если вы создаете программу, реализующую функции уп рощенного текстового редактора, то необходимо позаботиться и об управлении кареткой.
Для этого в Win32 API предусмотрены функции, приведенные в следующем списке:
Функция CreateCaret создает связанную с окном каретку.
Функция SetCaretPos устанавливает положение каретки в окне.
Функция ShowCaret показывает каретку.
Функция HideCaret прячет каретку.
Функция DestroyCaret удаляет каретку.
Однако вы не можете просто создать каретку при обработке сообщения WM_CREATE и удалить ее при обработке сообщения WM_DESTROY. Каретка является общесистемным ресурсом и должна разделяться между всеми работающими при ложениями. Следует помнить, что появление каретки в окне имеет смысл только в том случае, когда окно имеет фокус ввода. Оконная процедура может контроли ровать моменты, когда окно получает или теряет фокус ввода, обрабатывая сооб щения WM_SETFOCUS и WM_KILLFOCUS.
Это диктует основные правила использования каретки. Оконная процедура вызывает функцию CreateCaret при обработке сообщения WM_SETFOCUS и функцию
DestroyCaret при обработке сообщения WM_KILLFOCUS.
Рассмотрим подробней использование функции CreateCaret, имеющей следую щий прототип:
BOOL CreateCaret(HWND hWnd, HBITMAP hBitmap, int nWidth, int nHeight);
Первый параметр функции содержит дескриптор окна, которое становится владельцем создаваемой каретки.
Второй параметр определяет форму каретки. В нем можно указать дескриптор растра, единичное значение или значение NULL.
Если параметр hBitmap равен NULL, то каретка имеет форму прямоугольного бло ка шириной nWidth и высотой nHeight, закрашенного черной однородной кистью.
Если параметр hBitmap равен единице, то форма каретки будет такой же, что и для значения NULL, но заполняться блок будет черно белой кистью с шахмат ным рисунком, что выглядит внешне как серый цвет. При единичной ширине ка ретки она будет выглядеть как пунктирная линия.
220 |
Глава 4. Средства ввода |
|
|
Если параметру hBitmap передается дескриптор растра, то форма каретки опре деляется этим растром. В этом случае параметры nWidth и nHeight игнорируются.
Параметры nWidth и nHeight задают ширину и высоту каретки в логических единицах. Их использование уже было описано выше. Если параметр nWidth име ет нулевое значение, то используется предопределенная в системе ширина рамки для окна, которая обычно равна одному пикселу. Если nHeight имеет нулевое зна чение, то используется предопределенная в системе высота рамки для окна, кото рая тоже обычно равна одному пикселу1.
В случае успешного завершения функция CreateCaret возвращает ненулевое значение. Нулевое значение служит признаком ошибки.
После создания каретки функцией CreateCaret она все еще остается скрытой. Чтобы каретка стала видимой, необходимо вызвать функцию ShowCaret. Перед уничтожением каретки с помощью функции DestroyCaret рекомендуется убрать ее с экрана функцией HideCaret.
Примитивный текстовый редактор
Программа Typer, код которой приведен в листинге 4.1, демонстрирует принципы и технику обработки аппаратных и символьных сообщений клавиатуры. Програм му можно рассматривать как примитивный текстовый редактор. Пользователь может набирать в окне текст, переходить на следующую строку, нажимая клави шу Enter, перемещать каретку по уже набранному тексту при помощи клавиш уп равления курсором, вводить новый текст в том месте, на которое указывает ка ретка, или удалять предшествующий символ. Однако у этого редактора нет ни полос прокрутки, ни функций поиска и замены строк, ни возможности сохранять текст в файле.
Для хранения текста, введенного с клавиатуры, в программе используется объект класса string из стандартной библиотеки C++ вместо традиционной C строки. Благодаря автоматическому управлению выделением и освобождени ем памяти в этом классе мы можем писать более компактный программный код, не отвлекаясь на детали реализации.
Чтобы использовать в программе объекты класса string, необходимо в начале файла добавить следующие директивы:
#include <string> using namespace std;
Переменная (объект класса string) для хранения введенных символов объяв ляется следующим оператором:
static string text;
Спецификатор static необходим для сохранения текущего значения перемен ной text до следующего вызова оконной процедуры2.
Хотя предполагается, что читатель знаком с классом string, мы все же напом ним назначение тех операций и методов класса, которые используются в коде.
1Предопределенные в системе ширину и высоту рамки для окна можно получить вызовами функ ций GetSystemMetrics(SM_CXBORDER) и GetSystemMetrics(SM_CYBORDER) соответственно.
2Локальные переменные, не имеющие спецификатора static, инициализируются заново при каждом вызове функции WndProc.
