Использование ввода с клавиатуры
Окно получает ввод с клавиатуры в виде сообщений нажатия клавиш и символьных сообщений. Цикл сообщений, подключенный к окну, должен содержать код для перевода сообщений нажатия клавиш в соответствующие символьные сообщения. Если окно отображает ввод клавиатуры в клиентской области, оно должно создать и отобразить курсор, чтобы указать позицию, в которой будет введен следующий символ. В следующих разделах описывается код, участвующий в получении, обработке и отображении ввода клавиатуры:
Обработка сообщений нажатия клавиш
Процедура окна с фокусом клавиатуры получает сообщения нажатия клавиш, когда пользователь вводит на клавиатуре. Сообщения нажатия клавиш — это WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN и WM_SYSKEYUP. Типичная процедура окна игнорирует все сообщения нажатия клавиш, кроме WM_KEYDOWN. Система публикует сообщение WM_KEYDOWN , когда пользователь нажимает клавишу.
Когда процедура окна получает сообщение WM_KEYDOWN , он должен изучить код виртуального ключа, который сопровождает сообщение, чтобы определить способ обработки нажатия клавиш. Код виртуального ключа находится в параметре wParam сообщения. Как правило, приложение обрабатывает только нажатия клавиш, созданные нехарактерными ключами, включая ключи функции, клавиши перемещения курсора и специальные ключи, такие как INS, DEL, HOME и END.
В следующем примере показана платформа процедур окон, которую типичное приложение использует для получения и обработки сообщений нажатия клавиш.
Перевод символьных сообщений
Любой поток, получающий входные символы от пользователя, должен включать функцию TranslateMessage в свой цикл сообщений. Эта функция проверяет код виртуального ключа сообщения нажатия клавиш и, если код соответствует символу, помещает символьное сообщение в очередь сообщений. Символьное сообщение удаляется и отправляется в следующую итерацию цикла сообщений; Параметр wParam сообщения содержит код символа.
Как правило, цикл сообщений потока должен использовать функцию TranslateMessage для преобразования каждого сообщения, а не только сообщений виртуального ключа. Хотя TranslateMessage не влияет на другие типы сообщений, это гарантирует, что ввод с клавиатуры переведен правильно. В следующем примере показано, как включить функцию TranslateMessage в типичный цикл сообщений потока.
Обработка символьных сообщений
Процедура окна получает символьное сообщение, когда функция TranslateMessage преобразует код виртуального ключа, соответствующий символьной клавише. Символьные сообщения : WM_CHAR, WM_DEADCHAR, WM_SYSCHAR и WM_SYSDEADCHAR. Типичная процедура окна игнорирует все символьные сообщения, кроме WM_CHAR. Функция TranslateMessage создает сообщение WM_CHAR , когда пользователь нажимает любой из следующих клавиш:
- Любой символьный ключ
- BACKSPACE
- ВВОД (возврат каретки)
- ESC
- SHIFT+ВВОД (канал строк)
- TAB
Когда процедура окна получает сообщение WM_CHAR , он должен изучить код символа, который сопровождает сообщение, чтобы определить способ обработки символа. Код символа находится в параметре wParam сообщения.
В следующем примере показана платформа процедур окон, которую типичное приложение использует для получения и обработки символьных сообщений.
Использование курсора
Окно, которое получает ввод с клавиатуры, обычно отображает символы, типы пользователей в клиентской области окна. Окно должно использовать курсор, чтобы указать позицию в клиентской области, где появится следующий символ. Окно также должно создать и отобразить курсор, когда он получает фокус клавиатуры, и скрыть и уничтожить курсор, когда он теряет фокус. Окно может выполнять эти операции при обработке сообщений WM_SETFOCUS и WM_KILLFOCUS . Дополнительные сведения о курсорах см. в разделе Carets.
Отображение ввода с клавиатуры
В примере в этом разделе показано, как приложение может получать символы с клавиатуры, отображать их в клиентской области окна и обновлять положение курсора с каждым типизированным символом. Здесь также показано, как переместить курсор в ответ на нажатия клавиш СТРЕЛКА ВЛЕВО, СТРЕЛКА ВПРАВО, HOME и END, а также показано, как выделить выделенный текст в ответ на сочетание клавиш SHIFT+СТРЕЛКА ВПРАВО.
Во время обработки сообщения WM_CREATE процедура окна, показанная в примере, выделяет буфер 64 КБ для хранения ввода с клавиатуры. Он также получает метрики загруженного шрифта, сохраняя высоту и среднюю ширину символов в шрифте. Высота и ширина используются при обработке сообщения WM_SIZE для вычисления длины строки и максимального количества строк на основе размера клиентской области.
Процедура окна создает и отображает курсор при обработке сообщения WM_SETFOCUS . Он скрывает и удаляет курсор при обработке сообщения WM_KILLFOCUS .
При обработке сообщения WM_CHAR процедура окна отображает символы, сохраняет их в входном буфере и обновляет положение курсора. Процедура окна также преобразует символы табуляции в четыре последовательных пробела. Backspace, linefeed и escape-символы создают звуковой сигнал, но не обрабатываются в противном случае.
Процедура окна выполняет перемещение курсора влево, справа, конец и домашний курсор при обработке сообщения WM_KEYDOWN . При обработке действия клавиши СТРЕЛКА ВПРАВО процедура окна проверяет состояние клавиши SHIFT и, если она не работает, выбирает символ справа от курсора при перемещении курсора.
Обратите внимание, что следующий код написан таким образом, чтобы его можно было скомпилировать как Юникод или ANSI. Если исходный код определяет ЮНИКОД, строки обрабатываются как символы Юникода; в противном случае они обрабатываются как символы ANSI.
КАК взаимодействуют окно и клавиатура?
В программах MS-DOS клавиатуре выделено важное, почти главенствующее место. Почти всё взаимодействие с пользователем построено на работе с клавиатурой, и работа без неё кажется там немыслимой. Если мышь и используется в DOS, то только по прихоти заботливого программиста.
В Windows большинство действий мы делаем мышью, поэтому на первый взгляд применение клавиатуры ограничено вводом числовых данных в поля ввода. Но на второй взгляд мы вспоминаем про горячие клавиши типа Ctrl+S, Ctrl+N и Shift+F12, а также про компьютерные игры, где почти всё завязано на клавиатуре.
Подобно любой программе DOS, окно Windows может работать с клавиатурой. Когда вы нажимаете какие-либо кнопки, окно получает от клавиатуры сообщения. В зависимости от того, что вы хотите делать, это могут быть сообщения: WM_CHAR (работа с текстовыми (символьными) кнопками), WM_KEYDOWN (нажатие клавиш), WM_KEYUP (отпускание клавиш) — иногда бывает нужно произвести какое-то действие только когда пользователь отпустит кнопку, например при перетаскивании с нажатым Shft.
Как и при работе с DOS, все клавиши имеют свои коды. Причём код просто нажатой клавиши ‘S’ отличается от той же ‘S’ нажатой вместе с ‘Ctrl’. С помощью конструкции switch/case мы можем отслеживать нажатие нужных нам кнопок (например стрелок, управляющих героем игры) и производить какие-то ответные действия — движение, стрельба, прыжки.
Остаётся только узнать эти коды. Их можно посмотреть в справочнике по программированию или самому написать программу, которая выводит на экран коды клавиш и их сочетаний. Чем мы сейчас и займёмся.
Создайте новый пустой проект Win32, который назовём: «MyKey». В папке проекта создадим новый текстовый файл и сохраним его под именем MyKey.cpp. В нём будет исходный текст программы. Добавим его в проект с помощью команды меню: Project->Add to project->Files (Project->Add Existing Item для 7-й версии). Теперь наберите в этот файл следующий текст:
//Отображение кодов нажатых клавиш
//Создаём прототип функции окна
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
char szProgName[]=»Progname»; //имя программы
char szText[]=»»;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
<
HWND hWnd; //идентификатор окна
MSG lpMsg; //идентификатор сообщения
WNDCLASS w; //создаём экземпляр структуры WNDCLASS
//И начинаем её заполнять
w.lpszClassName=szProgName; //имя программы
w.hInstance=hInstance; //идентификатор текущего приложения
w.lpfnWndProc=WndProc; //указатель на функцию окна
w.hCursor=LoadCursor(NULL, IDC_ARROW);
w.hIcon=LoadIcon(hInstance, IDI_APPLICATION);
w.lpszMenuName=0; //меню пока не будет
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //цвет фона окна
w.style=CS_HREDRAW|CS_VREDRAW; //стиль — перерисовываемое по х и по у
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать класс окна — выходим
if(!RegisterClass(&w))
return 0;
//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName, //Имя программы
«Keyboard Code Viewer — 1.0», //Заголовок окна
WS_OVERLAPPEDWINDOW, //Стиль окна — перекрывающееся
100, //положение окна на экране по х
100, //по у
100, //размеры по х
80, //по у
(HWND)NULL, //идентификатор родительского окна
(HMENU)NULL, //идентификатор меню
(HINSTANCE)hInstance, //идентификатор экземпляра программы
(HINSTANCE)NULL); //отсутствие дополнительных параметров
//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
//Цикл обработки сообщений
while(GetMessage(&lpMsg, NULL, 0, 0)) < //Получаем сообщение из очереди
TranslateMessage(&lpMsg); //Преобразует сообщения клавиш в символы
DispatchMessage(&lpMsg); //Передаёт сообщение соответствующей функции окна
>
return(lpMsg.wParam);
>
//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём идентификатор контекста устройства
//Цикл обработки сообщений
switch(messg)
<
unsigned int key;
//Обработка нажатия клавиши
case WM_KEYDOWN:
key=wParam; //Получаем код нажатой клавиши
_itoa(key, szText, 10); //Преобразуем его в строку
hdc=GetDC(hWnd); //Получаем контекст рисования
TextOut(hdc, 10,10, szText, 2); //Выводим текст на экран
break;
//сообщение выхода
case WM_DESTROY:
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
break;
default:
return(DefWindowProc(hWnd, messg, wParam, lParam)); //освобождаем очередь приложения от нераспознаных
>
return 0;
>
Текст этот почти полностью нам знаком. Единственное белое пятно — обработка сообщения WM_KEYDOWN в цикле обработки сообщений.
Рассмотрим его поподробней:
case WM_KEYDOWN:
key=wParam; //Получаем код нажатой клавиши
_itoa(key, szText, 10); //Преобразуем его в строку
hdc=GetDC(hWnd); //Получаем контекст рисования
TextOut(hdc, 10,10, szText, 2); //Выводим текст на экран
break;
Мы присваиваем целой переменной key значение переменной wParam. После получения окном сообщения WM_KEYDOWN, оно записывает в него код нажатой клавиши. Так как это целое число, его необходимо преобразовать в строку, чтобы вывести на экран. Этим занимается функция _itoa, которая аналогична подобной функции DOS. После этого, в строке szText у нас будет код в виде строки, который не стыдно вывести на экран.
КАК это применить?
Допустим, нам нужно отловить сочетание Ctrl+S. Запустив программу, мы узнаём, что код этого сочетания = 83. Тогда наша первоначальная конструкция будет несколько видоизменена:
case WM_KEYDOWN:
key=wParam; //Получаем код нажатой клавиши
//Обработка нажатия клавиш
switch(key) <
case 83:
MessageBox(hWnd, «Ваше данные сохранены», «», MB_OK);
break;
//Здесь могут быть и другие case
>
Не правда ли это напоминает аналогичную DOS программу?
key=getch();
switch(key) <
case 83:
printf(«Данные сохранены!»);
break;
>
Какими далёкими кажутся теперь эти строки!
Теперь создадим программу, печатающую вводимые нами символы. Используем сообщение от WM_CHAR. Казалось, бы, зачем вообще нужно WM_CHAR, если есть WM_KEYDOWN? Ан нет! WM_CHAR полностью игнорирует нажимаемые нами клавиши, если это не символы букв и цифр. Это удобно, если мы хотим создать свой текстовый редактор или программу, получающую данные из строки ввода, как в DOS. Нам не надо писать подпрограмму, в которой фильтруются коды клавиш. Всё уже сделано за нас. Будет это выгледять так:
case WM_CHAR:
key=wParam;
wsprintf(szText, «%c», key);
hdc=GetDC(hWnd); //Получаем контекст рисования
TextOut(hdc, 10,10, szText, 1); //Выводим текст на экран
break;
Точно так же мы получаем код и записываем его в key. Затем используем функцию wsprintf, аналогичную sprintf в MS-DOS, копируя в строку символ. Затем выводим эту строку на экран. Закомментируйте в предыдущей программе сообщение WM_KEYDOWN и замените его этим. На экран будет выводиться по одному символу.
Если же вы хотите, чтобы каретка перемещалась, сделайте координаты TextOut динамичными. Пусть при нажатии клавиши, координата по X увеличивается, а при нажатии Enter увеличивается координата по y. Можно ещё отловить Return и обеспечить затирание введённого символа.
Так программа обрабатывает символьные клавиши.
Win32 API позволяет вам обрабатывать нажатие совершенно любой клавиши! Хотите, чтобы ваш герой стрелял кнопкой ‘Ctrl’? Нет ничего проще! Хотите, чтобы в вашей программе пауза наступала при нажатии кнопки «Pause» — что может быть проще!
В отличие от WM_KEYDOWN, WM_CHAR работает не с числовыми кодами клавиш, а с их шестнадцатиричными эквивалентами. Например, Enter будет 0xd, а 0x20 — пробел. Эти коды запомнить навозможно, поэтому придумали универсальную таблицу виртуальных клавиш, где все эти коды подменены идентификаторами, которые делают и программирование упрощают и текст программы делают понятней для чтения. Причём, эти идентификаторы годятся в работе и с WM_CHAR и с WM_KEYDOWN.
Их обозначают с префиксом VK (Virtual Key): VK_RETURN — Enter, VK_TAB — Tab и т.д.
По таблице виртульных клавиш вы всегда сможете узнать код нужной кнопки.
Таблица виртуальных клавиш.