QTableWidget setCellWidget(QWidget*) 单元格选择和焦点的不一致行为

QTableWidget setCellWidget(QWidget*) Inconsistant Behavior with Cell Selection and Focus

我制作了一个 subclassed QTableWidget 并想让一些单元格具有 QPushButtons 作为单元格小部件,因为我使用 [= 制作了一个样式非常重的按钮18=] 什么不是并且真的想将小部件嵌入到单元格中。好吧,我使用了 setCellWidget(QWidget* widget) 函数,它是 QTableWidget 的一部分,它几乎是完美的。因为我在 table 的单元格项目上做了一些 subclassed QStyledItemDelegate class 绘图,我画了一些似乎与尺寸有点冲突的边界线单元格小部件。

之前有人问过如何将 QCheckBoxQTableWidget's 单元格中居中,这样它就不会在左侧偏移。该问题的答案基本上是这样的:

  1. 创建 QWidget
  2. 创建您想要居中的QWidget
  3. 创建一个 QH/VLayout 并将顶层 QWidget's 布局设置为此
  4. 将要居中的子窗口小部件添加到此布局
  5. QTableWidget 上使用 setCellWidget 函数并将其设置为具有顶层 QWidget 的行和列,瞧,你有一个居中的 QWidget您可以随意操作和对齐的单元格

太好了,视觉效果很好...但是,我注意到了一些令人讨厌的副作用。箭头键导航似乎中断了,我无法再按 Enter 键或 Space 键来 "click/press" 我嵌入管理 QWidget 中的 QPushButton 该特定单元格.当尝试使用键盘上的箭头键离开单元格时,似乎更改焦点会在 QTableWidget 内部造成一些混乱。

我在网上看到有人说要在 table 上禁用 setTabKeyNavigation(bool) 以在设置焦点后修复一些类似的导航问题...这对我来说没有任何作用。我做了一个最小的可编译示例来展示行为

TableWidget.h:

#ifndef TABLEWIDGET_H
#define TABLEWIDGET_H

#include "button.h"
#include "widget.h"

#include <QTableWidget>
#include <QDebug>

class TableWidget : public QTableWidget{
    Q_OBJECT

public:
    TableWidget(QWidget* parent = nullptr);
    Widget *createWidget();
    Button *createButton();
    void setTableCell(QWidget *selecteditem);
};

#endif // TABLEWIDGET_H

TableWidget.cpp:

#include "tablewidget.h"

TableWidget::TableWidget(QWidget* parent) : QTableWidget(parent){
    setFixedSize(750, 500);

    setColumnCount(5);

    for(int i = 0; i < 5; ++i){
        insertRow(rowCount());
        for(int j = 0; j < columnCount(); ++j){
            QTableWidgetItem* item = new QTableWidgetItem;
            item->setFlags(item->flags() ^ Qt::ItemIsEditable);
            setItem(j, i, item);
        }
    }

    setCellWidget(0, 0, createButton());
    setCellWidget(2, 0, createWidget());
}

Button* TableWidget::createButton(){
    Button* button = new Button;
    connect(button, &Button::focusReceived, this, [this, button](){ setTableCell(button); }, Qt::DirectConnection);
    return button;
}

Widget* TableWidget::createWidget(){
    Button* button = new Button;
    connect(button, &Button::focusReceived, this, [this, button](){ setTableCell(button); }, Qt::DirectConnection);

    return new Widget(button);
}

//Helper to make keyboard focus more intuitive for cell widgets versus regular items
void TableWidget::setTableCell(QWidget* selecteditem){
    //Find the sender in the table
    for(int row = 0; row < rowCount(); ++row){
        for(int col = 0; col < columnCount(); ++col){
            if(cellWidget(row, col) == selecteditem){
                qDebug() << "TableWidget::setTableCell";
                setCurrentCell(row, col);
                setCurrentItem(this->item(row, col));
                setCurrentIndex(this->indexFromItem(this->item(row, col)));
                return;
            }
        }
    }
}

Button.h:

#ifndef BUTTON_H
#define BUTTON_H

#include <QPushButton>
#include <QFocusEvent>
#include <QDebug>

class Button : public QPushButton{
    Q_OBJECT
public:
    Button(QWidget *parent = nullptr);

    void focusIn(QFocusEvent *event);
signals:
    void focusReceived();
public slots:
    bool event(QEvent* e);
};

#endif // BUTTON_H

Button.cpp:

#include "button.h"

Button::Button(QWidget* parent) : QPushButton(parent){
    setStyleSheet(QString("background-color: solid rgba(255, 0, 0, 75);"));
}

bool Button::event(QEvent* event){
    switch(event->type()){
        case QEvent::FocusIn:
            focusIn(static_cast<QFocusEvent*>(event));
            return true;
            break;
        default:
            break;
    }

    return QWidget::event(event);
}

void Button::focusIn(QFocusEvent* event){
    qDebug() << "Button::focusIn";
    emit focusReceived();
    QPushButton::focusInEvent(event);
}

Widget.h:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QFocusEvent>
#include <QHBoxLayout>
#include <QPointer>
#include "button.h"

class Widget : public QWidget{
    Q_OBJECT
public:
    Widget(Button* button, QWidget *parent = nullptr);

public slots:
    bool event(QEvent* event);
    void focusIn(QFocusEvent *event);

signals:
    void focusReceived();

protected:
    QPointer<QHBoxLayout> m_hLayout;
    QPointer<Button>      m_button;
};

#endif // WIDGET_H

Widget.cpp:

#include "widget.h"

Widget::Widget(Button* button, QWidget* parent) : QWidget(parent){
    m_button = button;
    setStyleSheet(QString("background-color: solid rgba(0, 0, 0, 0);"));
    m_hLayout = new QHBoxLayout;
    setLayout(m_hLayout);
    m_hLayout->addWidget(m_button);
}

bool Widget::event(QEvent* event){
    switch(event->type()){
        case QEvent::FocusIn:
            focusIn(static_cast<QFocusEvent*>(event));
            return true;
            break;
        default:
            break;
    }
    return QWidget::event(event);
}

void Widget::focusIn(QFocusEvent* event){
    qDebug() << "Widget::focusIn";
    emit focusReceived();
    QWidget::focusInEvent(event);
}

因此,在导航 TableWidget 时,您希望能够看到突出显示的单元格随光标移动,并且 "temporarily" 为键盘事件的小部件提供软焦点。这只发生在 Button 对象上。 Button 对象将在 focusIn 函数中正确打印当所选单元格包含它时它关闭的功能。但是,您会期望 Widget 也会发生相同的行为,因为它使用完全相同的代码以完全相同的方式添加,只是在其 QHBoxLayout 中嵌入了 Button。导航到 ButtonWidget 后,TableWidget 的键盘导航似乎中断,并且按键不会从 Widget 转发到其子 Button 即使我要将 Widget 上的 setFocusProxy 设置为其 m_button 字段,该字段与可以正确获取 KeyEvent 的类型 Button 完全相同.我不太确定这是错误还是我破坏了某些行为。

嗯,原来这种行为是由 QAbstractButton::keyPressEvent(QKeyEvent *e) 函数引起的。因此,当按钮添加到 TableWidget 的单元格小部件时,我相信 table 明确成为它们的父小部件。这对于内存管理是有意义的,什么不是,好吧。

因此,QAbstractButtons 有一个针对 Qt::Key_DownQt::Key_Up 键的 case 语句,它们实质上检查它们的父窗口小部件是否属于 QAbstractItemView* 类型。如果他们这样做,他们将调用 QAbstractButtonPrivate::move(int key) 函数。这将在查询所有按钮的列表后确定按钮 buttonGroup 的一部分(我不确定我们是否可以控制),下一个应该是哪个按钮。这显然在 QTableWidget 继承自的 QAbstractItemView 中,按列主要顺序对它们进行排序,这样如果在下一行或下一列中有一个按钮,则该列将在下一行中选择如果 Qt::Key_Down 被捕获。这解释了我收到的行为。为什么他们将此作为基本行为打败了我,因为我发现我无法更改此行为非常奇怪,因为它是 QAbstractButtonPrivate class 的一部分,我们显然无法控制。幸运的是它是 virtual,我们可以覆盖该行为。

所以解决方案是实现 Button::keyPressEvent(QPressEvent *e) 并在 Qt::Key_DownQt::Key_Up 的每种情况下覆盖以发出一个信号,该信号将被 Table 和return。不要 break 并为这两种情况调用基本 class 实现。发射时,emit 一个标识符,很像 QAbstractButtonPrivate::move(int key) 函数对 table 的作用,并将选定的 cell/current 项目设置在您添加到 Table 的插槽中成为 right/left 或 up/down 的下一个 item/widget,正如您对 QTableWidgetItems 的正常键行为所期望的那样。这似乎有效,我终于可以通过正确的选择行为获得嵌入在 Widget 中的按钮的焦点。