如何告诉如果shift是按下numpad输入与NumLock上?或者至少获得NumLock状态

How to tell if shift is pressed on numpad input with NumLock on? Or at least get NumLock status?

本文关键字:NumLock 或者 状态 shift 如果 何告诉 输入 numpad      更新时间:2023-10-16

在Qt的keyPressEvent()和keyReleaseEvent()我试图得到一个numpad输入的键+修饰符。

使用void MyWidget::keyPressEvent(QKeyEvent *evt), evt->key()给出了键码(Qt::Key), evt->modifiers()给出了键盘修饰符(QFlags)。

  • 对于所有"常规"键,我可以检测到所有需要的修饰符(shift, alt, ctrl)。
  • 对于numpad键,如果numLock是关闭的,我得到正确的修饰符。
  • 如果NumLock是on,我收到ctrl和alt,但没有shift。

我发现shift键覆盖了NumLock。

下表显示了有关关键事件的所有可用Qt值的读数。
按键重现:关闭NumLock,按下并释放num_5,然后按下并释放shift,然后按shift ->按num_5 ->释放num_5 ->释放shift,然后切换NumLock为开启并重复相同的按键。

table headers:
natSC        = evt->nativeScanCode()
natMods      = evt->nativeModifiers()
kbdMods      = QGuiApplication::keyboardModifiers()
queryKbdMods = QGuiApplication::queryKeyboardModifiers()
NumLock |    Action     |   Event    | evt->key()  | evt->modifiers() | natSC | natMods  |   kbdMods    | queryKbdMods
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
off     | Num_5 down    | keyPress   | Key_Clear   | Keypad           |    76 |        0 | Keypad       | 0           
off     | Num_5 up      | keyRelease | Key_Clear   | Keypad           |    76 |        0 | Keypad       | 0           
off     | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |        1 | 0            | Shift       
off     | Shift up      | keyRelease | Key_Shift   | 0                |    42 |        0 | Shift        | 0           
off     | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |        1 | 0            | Shift       
off     | Num_5 down    | keyPress   | Key_Clear   | Shift+Keypad     |    76 |        1 | Shift+Keypad | Shift       
off     | Num_5 up      | keyRelease | Key_Clear   | Shift+Keypad     |    76 |        1 | Shift+Keypad | Shift       
off     | Shift up      | keyRelease | Key_Shift   | 0                |    42 |        0 | Shift        | 0           
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
on      | NumLock dwn   | keyPress   | Key_NumLock | Keypad           |   325 | 16777728 | Keypad       | 0           
on      | NumLock up    | keyRelease | Key_NumLock | Keypad           |   325 | 16777728 | Keypad       | 0           
--------+---------------+------------+-------------+------------------+-------+----------+--------------+-------------
on      | Num_5 down    | keyPress   | Key_5       | Keypad           |    76 |      512 | Keypad       | 0           
on      | Num_5 up      | keyRelease | Key_5       | Keypad           |    76 |      512 | Keypad       | 0           
on      | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Shift up      | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           
on      | Shift down    | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Num_5 down    | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           
on      | ...Num_5 down | keyPress   | Key_Clear   | Keypad           |    76 |      512 | Keypad       | 0           
on      | Num_5 up      | keyRelease | Key_Clear   | Keypad           |    76 |      512 | Keypad       | Shift       
on      | ...Num_5 up   | keyPress   | Key_Shift   | Shift            |    42 |      513 | 0            | Shift       
on      | Shift up      | keyRelease | Key_Shift   | 0                |    42 |      512 | Shift        | 0           

你可以看到shift似乎在numpad键事件之前被释放。
问题是,这个"虚拟的"移位释放事件看起来与常规的移位释放完全相同。

我理想的解决方案是在keyPressEvent()中获得真正的移位状态。
作为一种解决方案,我很乐意测试NumLock是否在keyPressEvent()中启用-然后如果用户按shift并要求禁用NumLock,我可以给出警告。

我正在使用Win7,但解决方案应该是可移植的,例如使用Qt。

我也会满足于回答"这是不可能的,因为…"。

获取NumLock状态

这样的任务在Windows系统中由Windows API承担,而在Unix系统中由X11负责。如果你想让你的代码在两种环境下都运行,你可以使用条件编译。

#ifdef  _WIN32
// Code for windows
#else
#ifdef  __linux__  // Systems based on the Linux kernel define this macro.
// Code for linux.
#else
对于windows,我建议:GetKeyState function

摘自Visual Studio文档:

GetKeyState

返回值类型SHORT

返回值指定虚拟密钥的状态,如下所示:

  1. 如果高阶位为1,则密钥为down;

  2. 如果低阶位为1,则打开密钥。如果某个键(如CAPS LOCK键)已打开,则该键处于切换状态。如果低阶位为0,则该键关闭且未切换。键盘上的切换键指示灯(如果有的话)将在该键被切换时亮,当该键未被切换时灭。

对于linux:使用XLib

下面是一个多平台示例代码:

#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef _UNIX
#include <X11/Xlib.h>
#endif
#include <iostream>
bool is_numlock_activated()
{
#ifdef _WIN32
    short status = GetKeyState(VK_NUMLOCK);
    return status == 1;
#endif
#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XKeyboardState x;
    XGetKeyboardControl(dpy, &x);
    XCloseDisplay(dpy);
    return x.led_mask & 2;
#endif
}
int main()
{
    if (is_numlock_activated())
        std::cout << "NumLock is activated.";
    else
        std::cout << "NumLock is deactivated.";
    std::cout << std::endl;
    return 0;
}

请注意,如果您没有运行X Server,则此代码将无法在linux上运行。这是不相关的,因为你正在开发一个桌面应用程序,因此,如果你可以运行你的应用程序,你可以运行这段代码。

获取移位状态

为此(对于linux),除了Xlib.h之外,还需要包含XKB扩展名。

#ifdef _UNIX
    #include <X11/XKBlib.h>
#endif
bool is_shift_pressed()
{
    bool result;
#ifdef _WIN32
    short status = GetKeyState(VK_SHIFT);
    return status == 0xF0;  // Here we are checking the High order bit.
                            // See GetKeyState documentation above.  
#endif
#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XkbStateRec sate;                       
    XkbGetSate(dpy, XkbUseCoreKbd, &sate);
    XCloseDisplay(dpy);
    return state.mods & 1;                  // 1 is the mask for modifier SHIFT.
#endif
}

这段代码(linux的那一段)我从superuser.com的答案中得到的

根据Raydel Miranda的回答,我找到了一个简单而有效的解决方案。

作为参考,我将发布Raydel Miranda的解决方案来获得当前的NumLock状态:

#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef _UNIX
#include <X11/Xlib.h>
#endif
bool IsNumLockOn()
{
#ifdef _WIN32
    short status = GetKeyState(VK_NUMLOCK);
    return status == 1;
#endif
#ifdef _UNIX
    Display *dpy = XOpenDisplay(":0");
    XKeyboardState x;
    XGetKeyboardControl(dpy, &x);
    XCloseDisplay(dpy);
    return x.led_mask & 2;
#endif
}

按numpad键会根据不同的状态产生不同的键码(在本例中使用中间键(标记为"5")):

NumLock | Shift | Key           | Modifiers
  off   |  off  | Qt::Key_Clear | Qt::KeypadModifier
  off   |   on  | Qt::Key_Clear | Qt::KeypadModifier + Qt::ShiftModifier
   on   |  off  | Qt::Key_5  *1 | Qt::KeypadModifier
   on   |   on  | Qt::Key_Clear | Qt::KeypadModifier *2

*1注意按键代码的变化取决于NumLock和shift状态。
*2你可以看到Qt::ShiftModifier在这里丢失了。

警告:奇怪的是,当isAutoRepeat()为true时,中间numpad键(并且只有这一个)没有Qt::KeypadModifier标志设置!

作为比较,下面是数字键"5"(在字母键上方)的相同结果表:

NumLock | Shift | Key           | Modifiers
  off   |  off  | Qt::Key_5     | Qt::NoModifier
  off   |   on  | Qt::Key_5     | Qt::ShiftModifier
   on   |  off  | Qt::Key_5     | Qt::NoModifier
   on   |   on  | Qt::Key_5     | Qt::ShiftModifier

我们知道NumLock的状态,我们知道这个键是否是numpad键。现在我们可以确定Shift键的真实状态,如下所示(同样使用中间标记为"5"的键):

  • 如果key不是numpad键(Qt::KeypadModifier未设置):
    • 如果Qt::ShiftModifier设置为
    • ,则按Shift
  • 其他
    • 如果NumLock是off (IsNumLockOn()返回false)
      • 如果Qt::ShiftModifier设置为
      • ,则按Shift
    • 其他
      • 如果keycode为Qt::Key_Clear
        • 按下Shift键
      • Else (keycode是Qt::Key_5)
        • Shift未按下
所以我们可以用下面的代码片段来确定正确的移位状态:
// using QKeyEvent *evt (e.g. in keyPressEvent())
Qt::Key key = Qt::Key(evt->key());
Qt::KeyboardModifiers mods = evt->modifiers();
bool NumLockOn = IsNumLockOn(); // see Raydel Miranda's answer
// set shift pressed if Qt::ShiftModifier is found
bool ShiftPressed = mods.testFlag(Qt::ShiftModifier);
// this list contains all the keycodes 'changed' on shift
QList<Qt::Key> lNumPadKeys = QList<Qt::Key>() << Qt::Key_Insert 
    << Qt::Key_End << Qt::Key_Down << Qt::Key_PageDown 
    << Qt::Key_Left << Qt::Key_Clear << Qt::Key_Right 
    << Qt::Key_Home << Qt::Key_Up << Qt::Key_PageUp 
    << Qt::Key_Delete;
// if shift wasn't pressed, the keycodes would be Qt::Key_0, Qt::Key_1, ..., Qt::Key_Comma instead
// correct the ShiftPressed value on NumLock + keypad input
if(mods.testFlag(Qt::KeypadModifier) && NumLockOn && !ShiftPressed)
{
    // keycode appears in the list -> shift must have been pressed
    // these keycodes can only result from keypad keys with NumLock on if shift was pressed 
    ShiftPressed = lNumPadKeys.contains(key);
}

瞧:

ShiftPressed现在被设置为真正的移位状态(注意Qt::Key_Clear的警告,虽然)。

相关文章: