Twisted Pixels #4 – Руководство по обработке ввода при нажатии клавиш

Twisted Pixels #4 – Руководство по обработке ввода при нажатии клавиш

Оригинал: https://blogs.msdn.com/windowsmobile/archive/2009/05/14/twisted-pixels-4-a-button-mashers-guide-to-input.aspx

В 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 включена, и когда она выключена.

AllKeysTest3Image

Эта программа позволяет выяснить, как ведут себя кнопки на вашем устройстве. Такая информация всегда может пригодиться, поскольку на разных устройствах кнопки разные, а их привязка и поведение могут быть совсем не очевидными. Я надеюсь, что эта утилита будет полезным дополнением вашего набора инструментов. Скачать 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, затем скачать исходный код упомянутого мной в этой статье приложения. Посмотрите код, затем запустите его на вашем устройстве, поэкспериментируйте, нажимая разные кнопки, и наблюдайте, что при этом происходит.

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

Перевод: Светлана Шиблева

.