Why pressing of “Tab” key emits only QEvent::ShortcutOverride event?

Deadly 提交于 2019-11-30 09:37:56

Well, finally I've done some research about that.

Explanation

When a view calls createEditor function of delegate it also installs the delegate event filter to editor.

QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index,
                                          const QStyleOptionViewItem &options)
{
    Q_Q(QAbstractItemView);
    QWidget *w = editorForIndex(index).widget.data();
    if (!w) {
        QAbstractItemDelegate *delegate = delegateForIndex(index);
        if (!delegate)
            return 0;
        w = delegate->createEditor(viewport, options, index);
        if (w) {
            w->installEventFilter(delegate);

    ......
}

However the delegate can catch only events of editor widget, but not events of its children. When the Tab key is pressed the QWidget::event function is called, it uses it to change focus to another widget:

bool QWidget::event(QEvent *event)
{
    ......

    switch (event->type()) {
        ......
        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;
        }

        ......
    }

    ......
}

Accordingly in my case the focus is set to next QPushButton after QLineEdit and event isn't propagated to the parent (LineEditor).

Solving

The right way to solve the problem is like QSpinBox does it. Because it is also has QLineEdit. In constructor of widget it sets focus proxy for line edit:

edit->setFocusProxy(this);

So all of focus events will reach the main widget. Also the focusPolicy property must be set because it's NoFocus by default:

setFocusPolicy(Qt::WheelFocus);

All we need to do in this moment it is to propagate necessary events to QLineEdit from main widget like this:

bool LineEditor::event(QEvent *e)
{
    switch(e->type())
    {
    case QEvent::ShortcutOverride:
        if(m_lineEdit->event(e))
            return true;
        break;

    case QEvent::InputMethod:
        return m_lineEdit->event(e);

    default:
        break;
    }

    return QWidget::event(e);
}

void LineEditor::keyPressEvent(QKeyEvent *e)
{
    m_lineEdit->event(e);
}

void LineEditor::mousePressEvent(QMouseEvent *e)
{
    if(e->button() != Qt::LeftButton)
        return;

    e->ignore();
}

void LineEditor::mouseReleaseEvent(QMouseEvent *e)
{
    e->accept();
}

void LineEditor::focusInEvent(QFocusEvent *e)
{
    m_lineEdit->event(e);
    QWidget::focusInEvent(e);
}

void LineEditor::focusOutEvent(QFocusEvent *e)
{
    m_lineEdit->event(e);
    QWidget::focusOutEvent(e);
}

This should be enough.

Tricky

As it's said above the delegate can't catch events of editor's children. So to make editor's behavior like "native" I have to duplicate events from children to editor.

LineEditor installs event filter to QLineEdit in constructor:

edit->installEventFilter(this);

Implementation of filter looks like this:

bool LineEditor::eventFilter(QObject *object, QEvent *event)
{
    if(event->type() == QEvent::KeyPress)
    {
        QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event);
        if(keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab)
        {
            QApplication::postEvent(this, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));
            // Filter this event because the editor will be closed anyway
            return true;
        }
    }
    else if(event->type() == QEvent::FocusOut)
    {
        QFocusEvent* focusEvent = static_cast<QFocusEvent *>(event);
        QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));

        // Don't filter because focus can be changed internally in editor
        return false;
    }

    return QWidget::eventFilter(object, event);
}

It's also possible to use qApp->notify(this, event) for QKeyEvent instead of QApplication::postEvent because we filter this events anyway. But it's not possible for QFocusEvent because notify will redirect the event and it will not reach a child.

Note that the standard (QItemDelegate or QStyledItemDelegate) delegate will care about situation when focus is changed internally by itself:

if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) {
        //the Hide event will take care of he editors that are in fact complete dialogs
        if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
            QWidget *w = QApplication::focusWidget();
            while (w) { // don't worry about focus changes internally in the editor
                if (w == editor)
                    return false;
                w = w->parentWidget();
            }

    ......
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!