Twisted Pixels #4 – Руководство по обработке ввода при нажатии клавиш
Twisted Pixels #4 – Руководство по обработке ввода при нажатии клавиш
В Windows Mobile 6.5 есть несколько изменений, касающихся программирования под эту платформу. Одно из этих изменений было темой недавнего блог-поста: Just say no to GAPI – What you need to know about AllKeys and input management.
Поскольку данный пост как раз посвящен операциям ввода, я предлагаю вам для начала прочитать тот пост, а затем вернуться сюда и дочитать все остальное – о том, как работают операции ввода в Window Mobile, и как эти операции просты для программирования.
Замечание: Просто чтобы вы не забыли – в этой серии блог-постов рассматриваются неуправляемые API в Windows Mobile и возможности их использования для разработчиков игр.
Несколько недель назад меня попросили написать простенькое приложение, демонстрирующее использование функции AllKeys. Поскольку AllKeys – очень простой интерфейс, я решил сделать свою задачу чуть более интересной и написать утилиту, которая бы умела отображать всю информацию о клавишах и показывала бы, как различаются сообщения, когда функция AllKeys включена, и когда она выключена.
Эта программа позволяет выяснить, как ведут себя кнопки на вашем устройстве. Такая информация всегда может пригодиться, поскольку на разных устройствах кнопки разные, а их привязка и поведение могут быть совсем не очевидными. Я надеюсь, что эта утилита будет полезным дополнением вашего набора инструментов. Скачать exe-файл и исходный код программы можно тут: https://code.msdn.microsoft.com/tpix.
Управление вводом
Управление вводом в Windows Mobile не так уж сильно отличается от управления вводом в Windows для настольных компьютеров. Windows Mobile использует ту же самую систему сообщений, правда эта информация вряд ли вам поможет, если вы никогда раньше не программировали под Windows. К счастью, управление вводом – это одна из самых простых задач программирования под Windows, а другие задачи, такие как работа с элементами управления и дочерними окнами, обычно при разработке игр решать не требуется. Существует огромное количество справочных материалов, описывающих, как Windows обрабатывает сообщения, так что я не буду здесь вдаваться в детали. Книги Чарльза Петцольда (Charles Petzold) уже долгое время остаются одними из моих любимых – см. Programming Windows.
Примечание переводчика: Книги Чарльза Петцольда выпущены и на русском языке, их можно купить, например, на Озоне. Правда, по этой теме в русском переводе, по-моему, есть только "Программирование для Windows 95"… Впрочем, нельзя сказать, что за 12 лет которые прошли с момента выхода этой книги на русском языке, в базовом WIN API произошли кардинальные изменения :)
Примечание редактора: то что мы не рушим существующие API не означает что мы не делаем что то новое. :)
Самое главное – это то, что ввод посылается в приложение в форме сообщения, и у любого Windows-приложения есть нечто, называемое циклом обработки сообщений, который выбирает сообщения из очереди и посылает их соответствующей WndProc. Если вы собираете приложения в Visual Studio и используете один из стартовых шаблонов, например "Win32 Smart Device Project", цикл обработки сообщений будет создан вместе со всем остальными кодом, необходимым для создания простого Windows Mobile приложения. Вот пример:
Цикл обработки сообщений
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static SHACTIVATEINFO s_sai;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_HELP_ABOUT:
DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, About);
break;
case IDM_OK:
SendMessage (hWnd, WM_CLOSE, 0, 0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_CREATE:
SHMENUBARINFO mbi;
memset(&mbi, 0, sizeof(SHMENUBARINFO));
mbi.cbSize = sizeof(SHMENUBARINFO);
mbi.hwndParent = hWnd;
mbi.nToolBarId = IDR_MENU;
mbi.hInstRes = g_hInst;
if (!SHCreateMenuBar(&mbi))
{
g_hWndMenuBar = NULL;
}
else
{
g_hWndMenuBar = mbi.hwndMB;
}
// Initialize the shell activate info structure
memset(&s_sai, 0, sizeof (s_sai));
s_sai.cbSize = sizeof (s_sai);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
CommandBar_Destroy(g_hWndMenuBar);
PostQuitMessage(0);
break;
case WM_ACTIVATE:
// Notify shell of our activate message
SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);
break;
case WM_SETTINGCHANGE:
SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Это процедура обработки сообщений, которую Visual Studio создала в нашем стартовом проекте. Обратите внимание, что эта процедура не содержит обработки ввода (пока не содержит!).
У многих приложений больше одного цикла обработки сообщений. Фактически, каждый цикл обработки сообщений ассоциирован с созданием окна, так что у каждого окна теоретически может быть свой цикл обработки сообщений.
Примечание переводчика: Это утверждение кажется не совсем точным. На самом деле, окно "ассоциировано" с нитью (thread), в которой оно было создано, и все сообщения в это окно поступают через цикл обработки сообщений (message loop), который работает с этой нитью.
С другой стороны, некоторые окна, например, диалоговые, используют циклы обработки сообщений своих родительских окон. Пример этого приведен выше – поищите "case IDM_HELP_ABOUT:", он обрабатывает сообщения дочернего диалогового окна "About".
Сообщения от клавиш в цикле обработки сообщений
Поскольку нам сейчас интересен только процесс обработки ввода, я сконцентрируюсь лишь на сообщениях, которые посылаются при нажатии на различные кнопки, и опущу все остальное. Вот сообщения, которые нам сейчас интересны:
WM_KEYDOWN:
WM_KEYUP:
WM_SYSKEYDOWN:
WM_SYSKEYUP:
WM_CHAR:
WM_SYSCHAR:
WM_DEADCHAR:
WM_SYSDEADCHAR:
На практике же, Windows Mobile приложения обычно используют только вот эти сообщения:
WM_KEYDOWN:
WM_KEYUP:
WM_CHAR:
Ввод – это на самом деле, чрезвычайно просто. Когда кнопка нажата, в приложение отправляется сообщение WM_KEYDOWN. Когда нажатая кнопка отпущена, отправляется сообщение WM_KEYUP. Выяснить, какая именно кнопка была нажата можно по информации, которая содержится в самом сообщении.
Для поддержки различных языков на конкретном устройстве операционная система умеет определять виртуальный код клавиши и транслировать его к одному или нескольким языкам – тем, что установлены на телефоне. Этот "перевод", как правило, выполняется сразу после WM_KEYDOWN и называется WM_CHAR. WM_CHAR содержит поле с информацией о символе кнопки, которая на самом деле была нажата. Если ваша программа скомпилирована с поддержкой Unicode, то это будет символ Unicode. В устройствах, которые поддерживают только английский язык, значения в WM_CHAR обычно такие же, как в WM_KEYDOWN, но естественно, для других языков это не так. Более того поведение может меняться от версии к версии, по мере усовершенствования языковых раскладок.
Работа с данными в сообщении
Цикл обработки сообщений получает из сообщений данные в форме, которая называется очень таинственно – параметр wParam или параметр lParam.
То, каким образом передаются данные в сообщениях, зависит от типа сообщения. В приведенном выше примере данные для сообщения WM_COMMAND можно получить вот так:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
Данные сообщения о нажатии клавиши содержат виртуальный код клавиши, который можно получить вот так:
long nVirtKey = wParam;
Чтобы определить, какая клавиша была нажата, нужно сравнить значение виртуального кода клавиши с предопределенным значением идентификатора VK_.
Доступная на MSDN документация по виртуальным кодам клавиш весьма подробная, в отличие от документации по обработке сообщений. Рекомендую вам ознакомиться с темой Using Virtual Key Codes и ее подразделом Virtual Key Codes, где есть самая последняя и более точная информация. Ну, а общую информацию по этой теме вы можете найти в разделе Keyboard.
Трансляция символов
Как уже было сказано, для поддержки на конкретном устройстве нескольких языков операционная система определяет виртуальный код клавиши и, в зависимости от языка, который установлен на телефоне, транслирует этот код в определенный символ. Этот символ затем посылается приложению в сообщении WM_CHAR.
Сообщение WM_CHAR, как правило, идет после сообщения WM_KEYDOWN, но перед сообщением WM_KEYUP. В сообщении WM_CHAR есть поле, которое содержит зависимое от текущего языка значение символа клавиши, которая была нажата. Если программа компилировалась с поддержкой Unicode (а обычно так и делается, по умолчанию), в сообщении и будет символ Unicode. В устройствах, где установлен только английский язык, данные сообщения WM_CHAR обычно совпадают с данными сообщения WM_KEYDOWN, но это не справедливо для устройств, которые поддерживают другие языки.
Большинство Windows Mobile приложений поддерживают ввод текста с клавиатуры, следовательно, они отслеживают сообщение WM_CHAR, в котором содержится информация о нажатой клавише в контексте выбранного языка. Однако у разработчиков игр могут быть другие требования – ведь клавише может соответствовать определенное действие игры, и привязка к языку тут не нужна. Например, клавиши W,A,S,D,X могут использоваться для выбора направления. В такой ситуации более разумно брать информацию о нажатой клавише из сообщения WM_KEYDOWN, поскольку код клавиши в идентификаторе VK_ не зависит от языка.
Захват нажатий кнопок
Управление несимвольными кнопками (такими как d-pad на многих телефонах) очень похоже, но тут есть свои небольшие хитрости. Многие такие кнопки завязаны на специальные задачи операционной системы, и при нажатии на такую кнопку операционная система просто перехватит сообщение, следовательно, оно не попадет в ваше приложение. Вместо этого, приложение может быть переключено в фоновый режим (например, когда звонит телефон).
Современные разработчики игр хотят получить доступ и к этим клавишам, и тут есть выход – можно настроить оповещение, и при входящем звонке отдавать управление операционной системе. При этом можно просить операционную систему все остальное время отправлять сообщения в приложение. Этот способ позволит разработчикам игр получать сообщения и с кнопок на d-pad, который расположен в нижней части многих устройств, и с управляющих кнопок в центре d-pad. Эти кнопки изображены выше на картинке с виртуальным устройством.
Попросить операционную систему отправлять в приложение информацию о нажатых клавишах можно вызовом AllKeys. AllKeys (TRUE) говорит операционной системе, что нажатия клавиш нужно отправлять в приложение, а AllKeys (FALSE) – что не нужно, и что операционная система может обрабатывать эти клавиши сама.
На данный момент лучший способ разобраться с AllKeys – это изучить вот этот пост: Just say no to GAPI – What you need to know about AllKeys and input management, затем скачать исходный код упомянутого мной в этой статье приложения. Посмотрите код, затем запустите его на вашем устройстве, поэкспериментируйте, нажимая разные кнопки, и наблюдайте, что при этом происходит.
Замечание: Программировать под мобильное устройство нужно особенно внимательно и аккуратно, учитывая тот факт, что, во-первых, устройство – это телефон, а во-вторых, что, когда этот телефон звонит, приложение не должно мешаться. На самом деле, все это звучит как хорошая тема для отдельной статьи, так что пока на этом остановимся.
Перевод: Светлана Шиблева
.