为什么按"Tab"键只发出QEvent::ShortcutOverve事件?

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

本文关键字:QEvent ShortcutOverve 事件 Tab 为什么      更新时间:2023-10-16

背景

我用QLineEdit和几个QPushButtons制作了一个自定义小部件,用于自定义项目委托:

class LineEditor : public QWidget
{
public:
    explicit LineEditor(QWidget *parent = 0) : QWidget(parent) {
        setLayout(new QHBoxLayout);
        layout()->setContentsMargins(0, 0, 0, 0);
        layout()->setSpacing(0);
        QLineEdit *edit = new QLineEdit(this);
        layout()->addWidget(edit);
        layout()->addWidget(new QPushButton(this));
        layout()->addWidget(new QPushButton(this));
        setFocusProxy(edit);
    }
};
class PropertyDelegate : public QItemDelegate
{
public:
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
        return new LineEditor(parent);
    }
    bool eventFilter(QObject *object, QEvent *event) {
        if (event->type() == QEvent::KeyPress) {
            qDebug() << "KeyPress";
        }
        if (event->type() == QEvent::ShortcutOverride) {
            qDebug() << "ShortcutOverride";
        }
        return QItemDelegate::eventFilter(object, event);
    }
};

我将它们与QListViewQStandardItemModel绑定,如下所示:

QStandardItemModel *model = new QStandardItemModel;
model->appendRow(new QStandardItem("1"));
model->appendRow(new QStandardItem("2"));
model->appendRow(new QStandardItem("3"));
QListView w;
w.setItemDelegate(new PropertyDelegate);
w.setModel(model);
w.show();

问题

为什么在PropertyDelegate::eventFilter中,按下Tab键时只有QEvent::ShortcutOverride事件,而按下任何其他键都会同时发出QEvent::ShortcutOverrideQEvent::KeyPress事件?

UPD:我想像使用标准小部件一样,通过按下TabBacktab来实现行之间的移动。

好吧,我终于对此做了一些研究。

解释

当视图调用委托的createEditor函数时,它还会将委托事件过滤器安装到编辑器中。

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);
    ......
}

但是,委托只能捕获编辑器小部件的事件,而不能捕获其子部件的事件。当按下Tab键时,调用QWidget::event函数,它使用它将焦点更改为另一个小部件:

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;
        }
        ......
    }
    ......
}

因此,在我的情况下,焦点被设置为QLineEdit之后的下一个QPushButton,并且事件不会传播到父级(LineEditor)。

解决

解决这个问题的正确方法是像QSpinBox那样做。因为它也有QLineEdit。在小部件的构造函数中,它为行编辑设置焦点代理:

edit->setFocusProxy(this);

因此,所有焦点事件都将到达主窗口小部件。此外,必须设置focusPolicy属性,因为默认情况下它是NoFocus

setFocusPolicy(Qt::WheelFocus);

此时此刻,我们所需要做的就是将必要的事件从主窗口小部件传播到QLineEdit,如下所示:

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);
}

这应该足够了。

狡猾的

正如上面所说的,代理无法捕捉编辑子女的事件。所以,为了使编辑器的行为像"原生"一样,我必须将事件从子级复制到编辑器。

LineEditor在构造函数中将事件筛选器安装到QLineEdit

edit->installEventFilter(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);
}

也可以将qApp->notify(this, event)用于QKeyEvent而不是QApplication::postEvent,因为我们无论如何都会过滤这些事件。但QFocusEvent不可能,因为notify将重定向事件,并且它不会到达子级。

请注意,标准(QItemDelegateQStyledItemDelegate)代表将关心焦点在内部自行更改时的情况:

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();
            }
    ......