GetKeyboardState: определение состояния клавиши

212
10 января 2018, 15:43

Не могу понять как работает функция GetKeyboardState. Вроде как должна определять состояние всех клавиш, но правильность результата зависит от места вызова... Если, например, скрыть окно, либо запускать функцию по таймеру, все значения окажутся равными 0. Если же назначить некоторой кнопке обработчик и при нажатии определять состояние, скажем, LeftShift, функция покажет верное значение.

Ещё мне не ясно как обозначаются состояния клавиш. Понятно, что старший бит говорит о том нажата клавиша или нет, а вот младший - is key toggled. Что ещё за Toggled state?

UPD: Так же, на счёт toggled state, согласно моим наблюдениям вполне возможна ситуация когда клавиша не нажата, а старший бит равен 1 (младший 0). Не могу понять никак как это должно работать...

var keyboardState = new byte[256];
GetKeyboardState(keyboardState);
var keyStateBits = new System.Collections.BitArray(new byte[] { keyboardState[(uint)Keys.LShiftKey] });
Debug.WriteLine("SHIFT key state: down - " + keyStateBits[0] + "; toggled - " + keyStateBits[7]);

UPD2: В самом деле мне нужно сделать работоспособным этот код:

    [DllImport("user32.dll")]
    static extern int ToUnicodeEx(
        uint wVirtKey, 
        uint wScanCode, 
        byte[] lpKeyState, 
        [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, 
        int cchBuff, 
        uint wFlags, 
        IntPtr dwhkl);
    [DllImport("user32.dll")]
    static extern bool GetKeyboardState(byte[] lpKeyState);
    [DllImport("user32.dll")]
    static extern uint MapVirtualKey(
        uint uCode, 
        uint uMapType);
    [DllImport("user32.dll")]
    static extern IntPtr GetKeyboardLayout(uint idThread);
    public static string VKCodeToUnicode(uint vkCode) {
        StringBuilder buf = new StringBuilder();
        byte[] keyboardState = new byte[255];
        if (!GetKeyboardState(keyboardState)) return string.Empty;
        ToUnicodeEx(vkCode, MapVirtualKey(vkCode, 0), keyboardState, buf, 5, 0, GetKeyboardLayout(0));
        return buf.ToString();
    }

Если окно отображается и активно, всё работает. Но если окно скрыть (ну через ShowWindow SW_HIDE), код выдаёт неправильные значения (без учёта состояния клавиш клавиатуры управляющих регистром и текущего языка раскладки)... Как это решить?

Answer 1

GetKeyboardState работает только в случаях, когда сообщение о нажатии клавиши дошло до очереди вашего процесса (т.е., когда ваше окно в фокусе). Для получения состояния клавиши напрямую используется функция GetAsyncKeyState. Она не работает для переключаемых клавиш типа Caps Lock - для них нужно использовать GetKeyState (или, в C# Control.IsKeyLocked, который ее использует).

if (GetAsyncKeyState(VK_CONTROL) & 0x8000 != 0){
    //нажата клавиша control
}

Если нужно в течение длительного времени отслеживать нажатие каких-то клавиш, лучше использовать Keyboard Hook.

Toggled state - состояние Вкл/Выкл для клавиш типа Caps Lock. Для всех остальных клавиш его значение не определено.

C вызовом GetKeyboardLayout(0) похожая проблема - он возвращает раскладку для текущего потока, которая может не соответствовать реальной системной раскладке, если окно свернуто. Надо найти поток текущего активного окна, и брать его раскладку.

Итоговый пример кода (C#):

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class Keyboard
{
    public const uint VK_CAPITAL = 0x14;
    [DllImport("USER32.dll")]
    public static extern short GetKeyState(int vKey);
    [DllImport("user32.dll")]
    public static extern short GetAsyncKeyState(int vKey);
    [DllImport("user32.dll")]
    public static extern int ToUnicodeEx(
        uint wVirtKey,
        uint wScanCode,
        byte[] lpKeyState,
        [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff,
        int cchBuff,
        uint wFlags,
        IntPtr dwhkl);
    [DllImport("user32.dll")]
    public static extern bool GetKeyboardState(byte[] lpKeyState);
    [DllImport("user32.dll")]
    public static extern uint MapVirtualKey(
        uint uCode,
        uint uMapType);
    [DllImport("user32.dll")]
    public static extern IntPtr GetKeyboardLayout(uint idThread);
    [DllImport("user32.dll")]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
    public static string VKCodeToUnicode(uint vkCode)
    {
        StringBuilder buf = new StringBuilder();
        byte[] keyboardState = new byte[255];            
        short x;
        byte y;            
        for (int i = 0; i < 255; i++)
        {                
            if (i == VK_CAPITAL)
            {
                x = GetKeyState(i);                                          
            }
            else
            {
                x = GetAsyncKeyState(i);                    
            }
            y = 0;
            if ((x & 0x8000) != 0) y = (byte)(y | 0x80);
            if ((x & 0x0001) != 0) y = (byte)(y | 0x01);
            keyboardState[i] = y;
        }
        ToUnicodeEx(vkCode, MapVirtualKey(vkCode, 0), keyboardState, buf, 5, 0, 
            GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero)));
        return buf.ToString();
    }
}
READ ALSO
Не могу подключиться к Ораклу из VS2017

Не могу подключиться к Ораклу из VS2017

Пишу под ASPNET MVC Framework веб-сервис, который должен работать с СУБД Oracle

229
Unity c# cписок не успевает инициализироваться в конструкторе

Unity c# cписок не успевает инициализироваться в конструкторе

Делаю 2d тавер-дефенс, решил сделать конструктор для удобного создания башен, но столкнулся с ошибкой NullReferenceException: Object reference not set to an instance of an object...

220
Классы. C#. Вывод результатов

Классы. C#. Вывод результатов

Доброго вечера всем обитателем портала (форума)К экзамену были даны практические задания (краткие) по классам

341
Сервис по реверсу MD5

Сервис по реверсу MD5

Передо мной встала задача - реверс md5 хеша 4 произвольных байтТо есть, другими словами, мне надо написать сервис из 1 метода - метод получает...

259