КАК взаимодействуют окно и клавиатура?

В программах 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-й версии). Теперь наберите в этот файл следующий текст:

//Отображение кодов нажатых клавиш

#include <windows.h>

//Создаём прототип функции окна
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
}

break;

Не правда ли это напоминает аналогичную DOS программу?

#include<stdio.h>

void main()
{
int key;
do {

key=getch();
switch(key){
case 83:
printf("Данные сохранены!");
break;
}

}while(key!=27);

}

Какими далёкими кажутся теперь эти строки!

Теперь создадим программу, печатающую вводимые нами символы. Используем сообщение от 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 и т.д.

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


Таблица виртуальных клавиш.

Символическое имя
Код клавиши
Клавиша на клавиатуре.
VK_CANCEL
<CTRL>+<BREAK>
VK_BACK
BACKSPACE
VK_TAB
TAB
VK_CLEAR
5 доп. клав.
VK_RETURN
ENTER
VK_SHIFT
SHIFT
VK_CONTROL
CTRL
VK_MENU
ALT
VK_PAUSE
PAUSE
VK_CAPITAL
CAPS LOCK
VK_ESCAPE
ESC
VK_SPACE
0x20
ПРОБЕЛ
VK_PRIOR
0x21
PAGE UP
VK_NEXT
0x22
PAGE DOWN
VK_END
0x23
END
VK_HOME
0x24
HOME
VK_LEFT
0x25
LEFT
VK_UP
0x26
UP
VK_RIGHT
0x27
RIGHT
VK_DOWN
0x28
DOWN
VK_SNAPSHOT
0x2a
PRINT SCREEN
VK_INSERT
0x2d
INSERT
VK_DELETE
0x2e
DELETE
0x30
A
0x31
B (дальше буквы идут по увеличению)
0x5a
Z
VK_NUMPAD0
0x60
<0> на доп. клав.
VK_NUMPAD1
0x61
<1> на доп. клав.
VK_NUMPAD9
0x69
<9>
VK_ADD
0x6b
<+>
VK_MULTIPLAY
0x6a
<*>
VK_SUBTRACT
0x6d
<->
VK_F1
0x70
F1
VK_F2
0x71
F2
VK_F12
0x7b
F12
VK_NUMLOCK
0x90
NUM LOCK
VK_SCROLL
0x91
SCROLL LOCK

Эти коды нам ещё нераз пригодятся, когда мы будем говорить о меню. В профессиональной программе каждый пункт меню должен быть продулблирован какими-то горячими клавишами. Например пункт "Выделить всё" в меню "Правка" дублирует Ctrl+A и .т.д.


Задание:
1. Используя знания в области GDI, напишите программу, которая будет двигать по экрану прямоугольник (эллипс, окружность или букву) с помощью кнопок-стрелок на клавиатуре. Подсказка: при нажатии этих клавиш будет меняться координата фигуры в соответствующую сторону.
2. Добавьте в предыдущей программу проверку, не касается ли фигура краёв (С помощью if ограничьте значения координат).
3. Сделайте небольшую терминальную программу, переводя картеку после нажатия каждого символа, вправо. Пусть при нажатии Enter переводится строка.
4. Добавьте в предыдущую программу проверку - не заехали ли мы за границу экрана. Сделайте стирание последнего символа по нажатию Return (используйте bar() - функцию Rectangle()).

Назад Главная Вперёд