- 在Qt中,键盘事件和QWidget的focus密不可分:一般来说,一个拥有焦点(focus)的QWidget或者grabKeyboard()的QWidget才可以接受键盘事件。
键盘事件派发给谁?
如何确定谁来接收键盘事件,不妨看一点点QApplication的源码:
X11下
QETWidget *keywidget=0; bool grabbed=false; if (event->type==XKeyPress || event->type==XKeyRelease) { keywidget = (QETWidget*)QWidget::keyboardGrabber(); if (keywidget) { grabbed = true; } else if (!keywidget) { if (d->inPopupMode()) // no focus widget, see if we have a popup keywidget = (QETWidget*) (activePopupWidget()->focusWidget() ? activePopupWidget()->focusWidget() : activePopupWidget()); else if (QApplicationPrivate::focus_widget) keywidget = (QETWidget*)QApplicationPrivate::focus_widget; else if (widget) keywidget = (QETWidget*)widget->window(); } }
Windows下
QWidget *g = QWidget::keyboardGrabber(); if (g && qt_get_tablet_widget() && hwnd == qt_get_tablet_widget()->winId()) { // if we get an event for the internal tablet widget, // then don't send it to the keyboard grabber, but // send it to the widget itself (we don't use it right // now, just in case). g = 0; } 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();
大致顺序:
- QWidget::keyboardGrabber()
- QApplication::activePopupWidget()
- QApplication::focusWidget()
- QWidget::window() [注:对于native的接收到键盘事件的widget,此时Qt将派发给其所属窗口]
在QWidget间切换焦点
我们可以通过Tab键或者鼠标单击来使得某个QWidget获得焦点。
问题:当我们按下Tab键(或者上下左右箭头键)时,下一个或获取焦点的QWidget是如何被确定的?
我们重新贴出上文最后贴出过的QWidget::event()的源码:
case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? if (k->key() == Qt::Key_Backtab || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) res = focusNextPrevChild(false); else if (k->key() == Qt::Key_Tab) res = focusNextPrevChild(true); if (res) break; } keyPressEvent(k);
老是觉得 QWidget::focusNextPrevChild() 这个函数有点名不符实(或者有点别扭),因为:
bool QWidget::focusNextPrevChild(bool next) { Q_D(QWidget); QWidget* p = parentWidget(); bool isSubWindow = (windowType() == Qt::SubWindow); if (!isWindow() && !isSubWindow && p) return p->focusNextPrevChild(next); ... }
当我们调用一个Widget该成员时,最终将递归调用到其所在窗口的focusNextPrevChild成员。(不过这是一个protected的虚函数,在派生类中可以覆盖它,从而控制派生类实例中的焦点移动。)
prev/next
QWidgetPrivate内有3个成员变量:
class Q_GUI_EXPORT QWidgetPrivate : public QObjectPrivate { ... QWidget *focus_next; QWidget *focus_prev; QWidget *focus_child;
这3个变量可以分别用:
- QWidget::nextInFocusChain()
- QWidget::previousInFocusChain()
- QWidget::focusWidget() [注意区分:QApplication::focusWidget()]
进行获取。
前两个可以用来构成一个focus链表。
void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f) { Q_Q(QWidget); focus_next = focus_prev = q; ... void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw) { ...
通过QWidget::setTabOrder()可以调整Widgets在focus链表中的顺序
Widget上放置大量按钮怎么样?
比如一个类似软键盘的东西,在一个QWidget上面放置了大量的QPushButton。此时,除了Tab/Shift+Tab外,上下左右箭头也都可以用来移动焦点。
这是因为:
void QAbstractButton::keyPressEvent(QKeyEvent *e) { bool next = true; switch (e->key()) { case Qt::Key_Up: case Qt::Key_Left: next = false; // fall through case Qt::Key_Right: case Qt::Key_Down: ... focusNextPrevChild(next); }
focus proxy
- QWidget::setFocusProxy()
- QWidget::focusProxy()
Manual中说的比较清楚:
- If there is a focus proxy, setFocus() and hasFocus() operate on the focus proxy.
简单列出源码:
void QWidget::setFocus(Qt::FocusReason reason) { QWidget *f = this; while (f->d_func()->extra && f->d_func()->extra->focus_proxy) f = f->d_func()->extra->focus_proxy; bool QWidget::hasFocus() const { const QWidget* w = this; while (w->d_func()->extra && w->d_func()->extra->focus_proxy) w = w->d_func()->extra->focus_proxy;
其他
- 对于 Qt for Embedded Linux、Symbian 和 Windows CE,还有一个
QApplication::setNavigationMode()
设置可通过方向键来控制焦点进行上下左右的移动。
- 和QGraphicsView 相关的东西
等
作者:dbzhang800