Qt之Windows键盘消息学习

来源:互联网 发布:屏蔽短信端口发送0000 编辑:程序博客网 时间:2024/06/06 10:01

为了找到 QTBUG18896 问题的答案,只好先看看Windows下面的键盘消息处理,看到最后:发现这个问题和Windows似乎没有必然的联系 ^_^ (见 QToolBar焦点问题 (QTBUG18896) )

Windows键盘消息

对产生可显示字符的按键组合,Windows不仅给程序发送按键消息,而且还发送字符消息。有些键不产生字符,这些键包括shift键、功能键、光标移动键和特殊字符键如Insert和Delete。对于这些键,Windows只产生按键消息

按键消息

WM_KEYDOWN

一般是不带Alt的按键(Windows本身不处理这些消息)

wParam 中保存虚拟键码

WM_KEYUP

WM_SYSKEYDOWN

系统按键,一般是带 Alt(通常交由DefWindowProc处理)

WM_SYSKEYUP

字符消息

WM_CHAR

从WM_KEYDOWN得到

wParam中是 ANSI或Unicode代码

WM_DEADCHAR

 

WM_SYSCHAR

从WM_SYSKEYDOWN得到

WM_DEADSYSCHAR

 

输入法(字符)消息

WM_IME_CHAR

如果不处理,DefWindowProc根据窗口注册的是unicode或ansi生成1个或2个WM_CHAR

WM_IME_KEYDOWN

如果不处理,DefWindowProc生成相应的WM_KEYDOWN或WM_KEYUP

WM_IME_KEYUP

字符消息

WM_UNICHAR

不同于WM_CHAR(utf16),其内部是utf32(几乎不用该消息?)

  • 组合使用Ctrl键与字母键会产生从0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制代码(字符消息)
  • 下面的按键也会产生字符消息

按键

ASCII码

 

C转义表示

Backspace

0x08

Ctrl-H

\b

Tab

0x09

Ctrl-I

\t

Ctrl-Enter

0x0A

Ctrl-J

\n

Enter

0x0D

Ctrl-M

\r

Qt 的处理

(注:以下代码来自Qt4.7.3)

直接看一下窗口的回调函数中对按键消息的处理

  • 目标widget:
    • Grabber 键盘事件的 widget
    • 获得焦点的 widget

 

        switch (message) {        case WM_KEYDOWN:                        // keyboard event        case WM_SYSKEYDOWN:            qt_keymapper_private()->updateKeyMap(msg);            // fall-through intended        case WM_KEYUP:        case WM_SYSKEYUP:        case WM_IME_CHAR:        case WM_IME_KEYDOWN:        case WM_CHAR: {            MSG msg1;            QWidget *g = QWidget::keyboardGrabber();            if (g)                widget = (QETWidget*)g;            else if (QApplication::activePopupWidget())                widget = (QETWidget*)QApplication::activePopupWidget()->focusWidget()                       ? (QETWidget*)QApplication::activePopupWidget()->focusWidget()                       : (QETWidget*)QApplication::activePopupWidget();            else if (QApplication::focusWidget())                widget = (QETWidget*)QApplication::focusWidget();            else if (!widget || widget->internalWinId() == GetFocus()) // We faked the message to go to exactly that widget.                widget = (QETWidget*)widget->window();            if (widget->isEnabled())                result = sm_blockUserInput                            ? true                            : qt_keymapper_private()->translateKeyEvent(widget, msg, g != 0);            break;        }        case WM_SYSCHAR:            result = true;                        // consume event            break;

translateKeyEvent

这中间最重要的是 translateKeyEvent 这个函数,它负责生成Qt的键盘事件

继续之前,先看一下QKeyEvent的几个成员函数:

Qt

int  key   () const

 

Qt::KeyboardModifiers  modifiers   () const

 

QString  text   () const

 

系统

quint32  nativeModifiers   () const

 

quint32  nativeScanCode   () const

 

quint32  nativeVirtualKey   () const

 

对照看一下:先获取系统提供的scancode、virtualkey和modifiers

bool QKeyMapperPrivate::translateKeyEvent(QWidget *widget, const MSG &msg, bool grab){    quint32 scancode = (msg.lParam >> 16) & 0xfff;    quint32 vk_key = MapVirtualKey(scancode, 1);    bool isNumpad = (msg.wParam >= VK_NUMPAD0 && msg.wParam <= VK_NUMPAD9);    quint32 nModifiers = 0;   // Map native modifiers to some bit representation   nModifiers |= (GetKeyState(VK_LSHIFT  ) & 0x80 ? ShiftLeft : 0);...   nModifiers |= (GetKeyState(VK_SCROLL  ) & 0x01 ? ScrollLock : 0);    if (msg.lParam & ExtendedKey)        nModifiers |= msg.lParam & ExtendedKey;

然后进行转换(后面会看到处理WM_KEYDOWN时会处理掉WM_CHAR,这儿是漏网之鱼(WM_CHAR)):

    // Get the modifier states (may be altered later, depending on key code)    int state = 0;    state |= (nModifiers & ShiftAny ? Qt::ShiftModifier : 0);    state |= (nModifiers & ControlAny ? Qt::ControlModifier : 0);    state |= (nModifiers & AltAny ? Qt::AltModifier : 0);    state |= (nModifiers & MetaAny ? Qt::MetaModifier : 0);    // A multi-character key not found by our look-ahead    if (msgType == WM_CHAR) {        QString s;        QChar ch = QChar((ushort)msg.wParam);        if (!ch.isNull())            s += ch;        k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers);        k1 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, 0, Qt::KeyboardModifier(state), s, false, 0, scancode, vk_key, nModifiers);    }

keydown的处理

  • 查看上次的keydown记录,比对状态是否一致
// KEYDOWN ---------------------------------------------------------------------------------if (msgType == WM_KEYDOWN || msgType == WM_IME_KEYDOWN || msgType == WM_SYSKEYDOWN) {    // Get the last record of this key press, so we can validate the current state    // The record is not removed from the list    KeyRecord *rec = key_recorder.findKey(msg.wParam, false);    // If rec's state doesn't match the current state, something has changed behind our back    // (Consumed by modal widget is one possibility) So, remove the record from the list    // This will stop the auto-repeat of the key, should a modifier change, for example    if (rec && rec->state != state) {        key_recorder.findKey(msg.wParam, true);        rec = 0;    }
  • 系统消息队列中是否有对象的WM_CHAR,有则取出
// Find unicode character from Windows Message QueueMSG wm_char;UINT charType = (msgType == WM_KEYDOWN                    ? WM_CHAR                    : msgType == WM_IME_KEYDOWN ? WM_IME_CHAR : WM_SYSCHAR);QChar uch;if (PeekMessage(&wm_char, 0, charType, charType, PM_REMOVE)) {    // Found a ?_CHAR    uch = QChar((ushort)wm_char.wParam);    if (msgType == WM_SYSKEYDOWN && uch.isLetter() && (msg.lParam & KF_ALTDOWN))        uch = uch.toLower(); // (See doc of WM_SYSCHAR) Alt-letter    if (!code && !uch.row())        code = asciiToKeycode(uch.cell(), state);}
  • 如果系统消息队列中没有,则尝试从keydown参数中生成字符
// If no ?_CHAR was found in the queue; deduct character from the ?_KEYDOWN parametersif (uch.isNull()) {    if (msg.wParam == VK_DELETE) {        uch = QChar(QLatin1Char(0x7f)); // Windows doesn't know this one.    } else {        if (msgType != WM_SYSKEYDOWN || !code) {            UINT map = MapVirtualKey(msg.wParam, 2);            // If the high bit of the return value is set, it's a deadkey            if (!(map & 0x80000000))                uch = QChar((ushort)map);        }    }    if (!code && !uch.row())        code = asciiToKeycode(uch.cell(), state);}
  •  处理 windows 的系统热键:Alt+Tab 等

// Special handling of global Windows hotkeysif (state == Qt::AltModifier) {    switch (code) {    case Qt::Key_Escape:    case Qt::Key_Tab:    case Qt::Key_Enter:    case Qt::Key_F4:        return false; // Send the event on to Windows    case Qt::Key_Space:        // do not pass this key to windows, we will process it ourselves        qt_show_system_menu(widget->window());        return true;    default:        break;    }}// Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translationif (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier)    code = Qt::Key_Backtab;
  • 如果有上次按键记录,则这次属于 auto-repeating
// If we have a record, it means that the key is already pressed, the state is the same// so, we have an auto-repeating keyif (rec) {    if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) {        k0 = q->sendKeyEvent(widget, grab, QEvent::KeyRelease, code,                             Qt::KeyboardModifier(state), rec->text, true, 0,                             scancode, msg.wParam, nModifiers);        k1 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, code,                             Qt::KeyboardModifier(state), rec->text, true, 0,                             scancode, msg.wParam, nModifiers);    }}
  • 如果与上次的按键记录不同,则生成press事件
// No record of the key being previous pressed, so we now send a QEvent::KeyPress event,// and store the key data into our records.else {    QString text;    if (!uch.isNull())        text += uch;    char a = uch.row() ? 0 : uch.cell();    key_recorder.storeKey(msg.wParam, a, state, text);    k0 = q->sendKeyEvent(widget, grab, QEvent::KeyPress, code, Qt::KeyboardModifier(state),                         text, false, 0, scancode, msg.wParam, nModifiers);    bool store = true;    // Alt+<alphanumerical> go to the Win32 menu system if unhandled by Qt(Q_OS_WINCE)    if (msgType == WM_SYSKEYDOWN && !k0 && a) {        HWND parent = GetParent(widget->internalWinId());        while (parent) {            if (GetMenu(parent)) {                SendMessage(parent, WM_SYSCOMMAND, SC_KEYMENU, a);                store = false;                k0 = true;                break;            }            parent = GetParent(parent);        }    }    if (!store)        key_recorder.findKey(msg.wParam, true);}

keyup 事件处理 (略)

sendKeyEvent

bool QKeyMapper::sendKeyEvent(QWidget *widget, bool grab,                              QEvent::Type type, int code, Qt::KeyboardModifiers modifiers,                              const QString &text, bool autorepeat, int count,                              quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers,                              bool *){    QKeyEventEx e(type, code, modifiers,                  text, autorepeat, qMax(1, int(text.length())),                  nativeScanCode, nativeVirtualKey, nativeModifiers);    QETWidget::sendSpontaneousEvent(widget, &e);    if (!isModifierKey(code)        && modifiers == Qt::AltModifier        && ((code >= Qt::Key_A && code <= Qt::Key_Z) || (code >= Qt::Key_0 && code <= Qt::Key_9))        && type == QEvent::KeyPress        && !e.isAccepted())        QApplication::beep();           // Emulate windows behavior    return e.isAccepted();

QApplication::notify()

QApplication::notify负责事件的派发:

    case QEvent::ShortcutOverride:    case QEvent::KeyPress:    case QEvent::KeyRelease:        {            bool isWidget = receiver->isWidgetType();            bool isGraphicsWidget = false;#ifndef QT_NO_GRAPHICSVIEW            isGraphicsWidget = !isWidget && qobject_cast<QGraphicsWidget *>(receiver);#endif            if (!isWidget && !isGraphicsWidget) {                res = d->notify_helper(receiver, e);                break;            }            QKeyEvent* key = static_cast<QKeyEvent*>(e);            if (key->type()==QEvent::KeyPress) {                // Try looking for a Shortcut before sending key events                if ((res = qApp->d_func()->shortcutMap.tryShortcutEvent(receiver, key)))                    return res;                qt_in_tab_key_event = (key->key() == Qt::Key_Backtab                                       || key->key() == Qt::Key_Tab                                       || key->key() == Qt::Key_Left                                       || key->key() == Qt::Key_Up                                       || key->key() == Qt::Key_Right                                       || key->key() == Qt::Key_Down);            }            bool def = key->isAccepted();            QPointer<QObject> pr = receiver;            while (receiver) {                if (def)                    key->accept();                else                    key->ignore();                res = d->notify_helper(receiver, e);                QWidget *w = isWidget ? static_cast<QWidget *>(receiver) : 0;                QGraphicsWidget *gw = isGraphicsWidget ? static_cast<QGraphicsWidget *>(receiver) : 0;...        break;

这儿在派发键盘事件之前,先进行了shortcutEvent的处理:

QShortcutMap::tryShortcutEvent()

  • 先派发类型为 QEvent::ShortcutOverride 的QKeyEvent事件。(接收者可以阻止shortcut的继续传递)

  • 如果上面的事件未被accept,则查找shortcut
    • 匹配,则派发QShortcutEvent事件
/*! \internal    Uses ShortcutOverride event to see if any widgets want to override    the event. If not, uses nextState(QKeyEvent) to check for a grabbed    Shortcut, and dispatchEvent() is found an identical.    \sa nextState dispatchEvent*/bool QShortcutMap::tryShortcutEvent(QObject *o, QKeyEvent *e){    Q_D(QShortcutMap);    bool wasAccepted = e->isAccepted();    bool wasSpontaneous = e->spont;    if (d->currentState == QKeySequence::NoMatch) {        ushort orgType = e->t;        e->t = QEvent::ShortcutOverride;        e->ignore();        QApplication::sendEvent(o, e);        e->t = orgType;        e->spont = wasSpontaneous;        if (e->isAccepted()) {            if (!wasAccepted)                e->ignore();            return false;        }    }    QKeySequence::SequenceMatch result = nextState(e);    bool stateWasAccepted = e->isAccepted();    if (wasAccepted)        e->accept();    else        e->ignore();    int identicalMatches = d->identicals.count();    switch(result) {    case QKeySequence::NoMatch:        return stateWasAccepted;    case QKeySequence::ExactMatch:        resetState();        dispatchEvent(e);    default:        break;    }    // If nextState is QKeySequence::ExactMatch && identicals.count == 0    // we've only found disabled shortcuts    return identicalMatches > 0 || result == QKeySequence::PartialMatch;}
  • 注:QMenu的event成员中对 QEvent::ShortcutOverride 进行处理,以override掉某些键

    boolQMenu::event(QEvent *e){    Q_D(QMenu);    switch (e->type()) {    case QEvent::Polish:        d->updateLayoutDirection();        break;    case QEvent::ShortcutOverride: {            QKeyEvent *kev = static_cast<QKeyEvent*>(e);            if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down                || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right                || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return                || kev->key() == Qt::Key_Escape) {                e->accept();                return true;            }        }
    而 派发的shortcut事件在QShortcut::event进行处理。
    /*!    \internal*/bool QShortcut::event(QEvent *e){    Q_D(QShortcut);    bool handled = false;    if (d->sc_enabled && e->type() == QEvent::Shortcut) {        QShortcutEvent *se = static_cast<QShortcutEvent *>(e);        if (se->shortcutId() == d->sc_id && se->key() == d->sc_sequence){            if (se->isAmbiguous())                emit activatedAmbiguously();            else                emit activated();            handled = true;        }    }    return handled;}

参考

  • MSDN TranslateMessage Function

  • Windows 程序设计
原创粉丝点击