Manipulação de entrada de teclado e mouse (Win API)

11

Existem várias maneiras de capturar o mouse ou o teclado no Windows. Então, eu tentei alguns deles, mas cada um deles tem algumas vantagens e desvantagens. Quero perguntar-lhe: Qual método usa?

Eu tentei estes:

  1. WM_KEYDOWN / WM_KEYUP - A principal desvantagem é que não consigo distinguir entre as teclas esquerda e direita como ALT, CONTROL ou SHIFT.

  2. GetKeyboardState - Isso resolve o problema do primeiro método, mas há um novo. Quando percebo que a tecla ALT direita é pressionada, também percebo que a tecla Controle esquerdo está pressionada. Esse comportamento ocorre apenas ao usar o layout de teclado localizado (tcheco - CS).

  3. WM_INPUT (Entrada não processada) - Este método também não distingue as teclas esquerda e direita (se me lembro) e, para o movimento do mouse, às vezes gera mensagem com valores delta zero da posição do mouse.

Deluxe
fonte

Respostas:

10

A melhor e mais simples maneira de fazer isso é usar sua primeira idéia e manipular as mensagens WM_KEYUP / WM_KEYDOWN, bem como as mensagens WM_SYSKEYUP / WM_SYSKEYDOWN. Eles podem detectar a diferença entre as teclas shift / control / alt esquerda e direita, você só precisa dos códigos de chave virtual apropriados . Eles são VK_LSHIFT / VK_RSHIFT, VK_LCONTROL / VK_RCONTROL e VK_LMENU / VK_RMENU (para a tecla ALT).

Eu escrevi um post sobre como eu fiz isso e estava lidando com o WM_KEYUP / WM_KEYDOWN e o WM_SYSKEYUP / WM_SYSKEYDOWN no ​​mesmo manipulador. (Infelizmente, o blog não está mais disponível.)

A única complicação que vejo é que, como você está usando um teclado fora dos EUA, precisará adicionar uma lógica adicional para lidar com a sequência descrita no artigo WM_SYSKEYUP no MSDN. No entanto, eu provavelmente tentaria tornar algo mais simples do que o masteryoda.

Daemin
fonte
As mensagens WM_ são certamente as mais simples, mas "melhores" apenas se você não se importar com eventos perdidos. Abandonei essa solução quando percebi que era um problema intratável; se seu aplicativo perder o foco enquanto uma tecla estiver pressionada, ela ficará "presa" até que você a pressione novamente em foco.
Dash-tom-bang
1
De fato, falta de entrada é um problema, mas a solução mais fácil seria lidar adequadamente com as mensagens de foco / ativação e contorná-la. Praticamente o que você quer fazer é pausar o jogo quando você perde o foco, pois o usuário pode precisar mudar para outro aplicativo mais urgente, ou apenas pressiona a tecla Windows acidentalmente.
Daemin 24/08/10
3

Existe uma razão para você não combiná-los? Por exemplo, use WM_KEYDOWN para detectar o pressionamento de uma tecla Ctrl / Alt / Shift; nessa chamada, use GetKeyboardState () para distinguir a esquerda da direita?

Ian Schreiber
fonte
Sim eu posso. Provavelmente terminarei com esta solução (talvez seja melhor usar GetAsyncKeyState). Mas estou procurando por uma solução melhor, se houver alguma. E a tecla ATL direita também gera duas mensagens WM_KEYDOWN (devido ao layout do teclado). Portanto, apenas WM_INPUT ou DirectInput permanece.
Deluxe
3

WM_INPUT é legal. Eu acho que você pode distinguir as teclas esquerda / direita usando a estrutura RAWKEYBOARD . A parte difícil pode ser descobrir como lidar com os identificadores de teclas (por exemplo, scancodes), mas não posso dizer, pois nunca tentei usar isso para entrada de teclado. WM_KEYDOWN é tão fácil :)

Eu usei WM_INPUT para entrada do mouse, no entanto. É de nível muito baixo. Não tem aceleração aplicada, o que é muito bom (IMO). WM_INPUT costumava ser a única maneira de tirar proveito do movimento do mouse em alta resolução, mas não tenho certeza se esse ainda é o caso. Veja este artigo do MSDN de 2006 .

O DirectInput para mouse / teclado é explicitamente desencorajado pela Microsoft. Consulte o artigo do MSDN vinculado anteriormente. Se você precisar de um joystick, o XInput provavelmente é o caminho a percorrer.

EDIT: Minhas informações sobre isso podem ser muito antigas.

BRaffle
fonte
3

Na verdade, diferencie L / R Ctrl / Alt ao capturar WM_KEYDOWN / WM_KEYUP, você pode. Fácil, não é, mas o código que eu uso, aqui você pode ter, hmm hmm.

Espero que isso ainda funcione, eu faço.

// 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
fonte
1
+1 para o código, mas -1 para falar como yoda. É irritante e dificulta a leitura das suas respostas.
Anthony
Na verdade, este não é um lugar para relatos de piadas.
Coderanger
2

Você pode tentar a API DirectInput ou, mais recentemente, a API XInput .

Skizz
fonte
1
O XImput não é apenas para o controlador XBox 360 conectado ao PC? Eu li que o DirectInput é um pouco obsoleto, então tentei evitar usá-lo. Mas eu tentei o DirectInput também e funcionou bem.
Deluxe
XInput é apenas para gamepads. No Windows 10, o XInput foi preterido em favor da interface 'IGamepad'. Além disso, você deve usar o RAW_INPUT em relação a outros mecanismos, pois há limitações neles.
Lavolpe