爲了找到 QTBUG18896 問題的答案,只好先看看Windows下面的鍵盤消息處理,看到最後:發現這個問題和Windows彷佛沒有必然的聯繫 ^_^ (見 QToolBar焦點問題 (QTBUG18896) ) windows
對產生可顯示字符的按鍵組合,Windows不只給程序發送按鍵消息,並且還發送字符消息。有些鍵不產生字符,這些鍵包括shift鍵、功能鍵、光標移動鍵和特殊字符鍵如Insert和Delete。對於這些鍵,Windows只產生按鍵消息app
按鍵消息ide |
WM_KEYDOWN函數 |
通常是不帶Alt的按鍵(Windows自己不處理這些消息)ui |
wParam 中保存虛擬鍵碼this |
WM_KEYUPspa |
|||
WM_SYSKEYDOWN.net |
系統按鍵,通常是帶 Alt(一般交由DefWindowProc處理)設計 |
||
WM_SYSKEYUPcode |
|||
字符消息 |
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(幾乎不用該消息?) |
按鍵 |
ASCII碼 |
C轉義表示 |
|
Backspace |
0x08 |
Ctrl-H |
\b |
Tab |
0x09 |
Ctrl-I |
\t |
Ctrl-Enter |
0x0A |
Ctrl-J |
\n |
Enter |
0x0D |
Ctrl-M |
\r |
(注:如下代碼來自Qt4.7.3)
直接看一下窗口的回調函數中對按鍵消息的處理
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 這個函數,它負責生成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 --------------------------------------------------------------------------------- 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; }
// Find unicode character from Windows Message Queue MSG 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); }
// If no ?_CHAR was found in the queue; deduct character from the ?_KEYDOWN parameters if (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 hotkeys if (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 translation if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier) code = Qt::Key_Backtab;
// If we have a record, it means that the key is already pressed, the state is the same // so, we have an auto-repeating key if (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); } }
// 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 事件處理 (略)
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負責事件的派發:
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的處理:
先派發類型爲 QEvent::ShortcutOverride 的QKeyEvent事件。(接收者能夠阻止shortcut的繼續傳遞)
/*! \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掉某些鍵
bool QMenu::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