Обработка ввода с клавиатуры и мыши (Win API)

11

Есть несколько способов поймать мышь или клавиатуру под Windows. Я попробовал некоторые из них, но у каждого из них есть свои преимущества и недостатки. Я хочу спросить вас: какой метод использовать?

Я пробовал это:

  1. WM_KEYDOWN / WM_KEYUP - Главный недостаток заключается в том, что я не могу различать левых и правых клавиш, как ALT, CONTROL или SHIFT.

  2. GetKeyboardState - Это решает проблему первого метода, но есть новый один. Когда я получаю, что нажата правая клавиша ALT, я также получаю, что левая клавиша управления нажата. Такое поведение происходит только при использовании локализованной раскладки клавиатуры (чешский - CS).

  3. WM_INPUT (необработанный ввод) - этот метод также не различает левые и правые клавиши (если я помню), и для движения мыши иногда генерирует сообщение с нулевыми значениями дельта положения мыши.

роскошный
источник

Ответы:

10

Лучший и самый простой способ сделать это - использовать вашу первую идею и обрабатывать сообщения WM_KEYUP / WM_KEYDOWN, а также сообщения WM_SYSKEYUP / WM_SYSKEYDOWN. Они могут обрабатывать обнаружение разницы между левой и правой клавишами Shift / Control / Alt, вам просто нужны соответствующие коды виртуальных клавиш . Это VK_LSHIFT / VK_RSHIFT, VK_LCONTROL / VK_RCONTROL и VK_LMENU / VK_RMENU (для клавиши ALT).

Я написал пост о том, как я это сделал, и я обрабатывал и WM_KEYUP / WM_KEYDOWN, и WM_SYSKEYUP / WM_SYSKEYDOWN в одном и том же обработчике. (К сожалению, блог больше не доступен.)

Единственное осложнение, которое я вижу, состоит в том, что, поскольку вы используете неамериканскую клавиатуру, вам необходимо добавить дополнительную логику для обработки последовательности, описанной в статье WM_SYSKEYUP на MSDN. Однако я, вероятно, попытался бы сделать что-то более простое, чем у мастера.

Daemin
источник
Сообщения WM_, безусловно, самые простые, но «лучшие», только если вы не заботитесь о пропущенных событиях. Я отказался от этого решения, как только понял, что это неразрешимая проблема; если ваше приложение теряет фокус, когда клавиша нажата, эта клавиша будет «зависать», пока вы не нажмете ее снова в фокусе.
дэш-том-бэнг
1
Действительно, пропущенный ввод - это проблема, но самое простое решение - правильно обработать сообщения о фокусе / активации и обойти это. Практически вам нужно сделать паузу в игре, когда вы теряете фокус, поскольку пользователю может потребоваться переключиться на другое более срочное приложение, или он просто случайно нажал клавишу Windows.
Daemin
3

Есть ли причина, по которой вы не можете объединить их? Например, используйте WM_KEYDOWN, чтобы обнаружить нажатие клавиши Ctrl / Alt / Shift, затем в этом вызове используйте GetKeyboardState (), чтобы отличить левую от правой?

Ян Шрайбер
источник
Да, я могу. Я, вероятно, закончу с этим решением (возможно, будет лучше использовать GetAsyncKeyState). Но я ищу лучшее решение, если таковое существует. А клавиша Right-ATL также генерирует два сообщения WM_KEYDOWN (из-за раскладки клавиатуры). Таким образом, остается только WM_INPUT или DirectInput.
Делюкс
3

WM_INPUT это хорошо. Я думаю, что вы можете различить левую / правую клавиши, используя структуру RAWKEYBOARD . Сложная часть может заключаться в том, чтобы выяснить, как обращаться с идентификаторами клавиш (то есть скан-кодами), но я не могу сказать, поскольку я никогда не пытался использовать это для ввода с клавиатуры. WM_KEYDOWN это так просто :)

Я использовал WM_INPUT для ввода мышью, хотя. Это очень низкий уровень. У него нет ускорения, что очень хорошо (IMO). WM_INPUT был единственным способом воспользоваться движением мыши с высоким разрешением, но я не уверен, что это все еще так. Смотрите эту статью MSDN от 2006 года .

DirectInput для мыши / клавиатуры явно не рекомендуется Microsoft. Смотрите ранее связанную статью MSDN. Если вам нужен джойстик, XInput, вероятно, является подходящим вариантом.

РЕДАКТИРОВАТЬ: Моя информация об этом может быть слишком устаревшим.

BRaffle
источник
3

На самом деле, кроме L / R Ctrl / Alt, когда вы ловите WM_KEYDOWN / WM_KEYUP, вы можете. Легко, это не так, но код, который я использую, здесь вы можете иметь, хм хм.

Надеюсь, это все еще работает, я делаю.

// Receives a WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN or WM_SYSKEYUP message and 
// returns a virtual key of the key that triggered the message.
// 
// If the key has a common virtual key code, that code is returned. 
// For Alt's and Ctrl's, the values from the KeyCodes enumeration are used.
int translateKeyMessage (MSG& Msg);

// Virtual key codes for keys that aren't defined in the windows headers.
enum KeyCodes
{
    VK_LEFTCTRL = 162,
    VK_RIGHTCTRL = 163,
    VK_LEFTALT = 164,
    VK_RIGHTALT = 165
};

// ======================================================================================

int translateKeyMessage (MSG& Msg)
{
    // Determine the virtual key code.
    int VirtualKeyCode = Msg.wParam;

    // Determine whether the key is an extended key, e.g. a right 
    // hand Alt or Ctrl.
    bool Extended = (Msg.lParam & (1 << 24)) != 0;

    // If this is a system message, is the Alt bit of the message on?
    bool AltBit = false;    
    if (Msg.message == WM_SYSKEYDOWN || Msg.message == WM_SYSKEYUP)
        AltBit = (Msg.lParam & (1 << 29)) != 0;

    if ((Msg.message == WM_SYSKEYUP || Msg.message == WM_KEYUP) && !Extended && !AltBit && VirtualKeyCode == 18)
    {
        // Left Alt
        return KeyCodes::VK_LEFTALT;
    }

    // Left Ctrl
    if (!Extended && !AltBit && VirtualKeyCode == 17)
    {
        // Peek for the next message.
        MSG nextMsg;
        BOOL nextMessageFound = PeekMessage(&nextMsg, NULL, 0, 0, PM_NOREMOVE);

        // If the next message is for the right Alt:
        if (nextMessageFound && nextMsg.message == Msg.message && nextMsg.wParam == 18)
        {
            //
            bool nextExtended = (nextMsg.lParam & (1 << 24)) != 0;

            //
            bool nextAltBit = false;    
            if (nextMsg.message == WM_SYSKEYDOWN || nextMsg.message == WM_SYSKEYUP)
                nextAltBit = (nextMsg.lParam & (1 << 29)) != 0;

            // If it is really for the right Alt
            if (nextExtended && !nextAltBit)
            {
                // Remove the next message
                PeekMessage(&nextMsg, NULL, 0, 0, PM_REMOVE);

                // Right Alt
                return KeyCodes::VK_RIGHTALT;
            }
        }

        // Left Ctrl
        return KeyCodes::VK_LEFTCTRL;
    }

    if (Msg.message == WM_SYSKEYUP && !Extended && AltBit && VirtualKeyCode == 17)
    {
        // Peek for the next message.
        MSG nextMsg;
        BOOL nextMessageFound = PeekMessage(&nextMsg, NULL, 0, 0, PM_NOREMOVE);

        // If the next message is for the right Alt:
        if (nextMessageFound && nextMsg.message == WM_KEYUP && nextMsg.wParam == 18)
        {
            //
            bool nextExtended = (nextMsg.lParam & (1 << 24)) != 0;

            //
            bool nextAltBit = false;    
            if (nextMsg.message == WM_SYSKEYDOWN || nextMsg.message == WM_SYSKEYUP)
                nextAltBit = (nextMsg.lParam & (1 << 29)) != 0;

            // If it is really for the right Alt
            if (nextExtended && !nextAltBit)
            {
                // Remove the next message
                PeekMessage(&nextMsg, NULL, 0, 0, PM_REMOVE);

                // Right Alt
                return KeyCodes::VK_RIGHTALT;
            }
        }
    }

    // Right Ctrl
    if (Extended && !AltBit && VirtualKeyCode == 17)
        return KeyCodes::VK_RIGHTCTRL;

    // Left Alt
    if (!Extended && AltBit && VirtualKeyCode == 18)
        return KeyCodes::VK_LEFTALT;

    // Default
    return VirtualKeyCode;
}
user1209
источник
1
+1 для кода, но -1 для разговора, как йода. Это раздражает и затрудняет чтение ваших ответов.
Энтони
Действительно, это не место для шутки.
кодеренджер
2

Вы можете попробовать DirectInput API или, в последнее время, XInput API .

Skizz
источник
1
Разве XImput только для контроллера XBox 360 подключен к ПК? Я читал, что DirectInput немного устарел, поэтому я старался избегать его использования. Но я тоже попробовал DirectInput и хорошо проснулся.
Делюкс
XInput предназначен исключительно для геймпадов. Начиная с Windows 10, XInput устарел в пользу интерфейса «IGamepad». Кроме того, вы должны использовать RAW_INPUT поверх других механизмов, так как на них есть ограничения.
LaVolpe