QWidget 键盘事件 焦点(QApplication源码)

     在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是如何被确定的?

   我们重新贴出上文最后贴出过的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()
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;

猜你喜欢

转载自blog.csdn.net/lengyuezuixue/article/details/81045438