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

时间:2019-07-11 18:43:55

标签: qt qwidget qtablewidget qpushbutton

我制作了一个子集QTableWidget,并希望使某些单元格具有QPushButtons作为单元格小部件,因为我已经使用QPropertyAnimations制作了一个样式非常丰富的按钮,而实际上不是想要将小部件嵌入单元格中。好吧,我已经使用了setCellWidget(QWidget* widget)的一部分QTableWidget函数,它几乎是完美的。由于我在表格的单元格项目上进行了一些子类化的QStyledItemDelegate类绘制,因此我绘制了一些似乎与单元格小部件的尺寸有些冲突的边界线。

以前,有人问过如何将QCheckBox放在QTableWidget's单元格的中心,以便它不会在左侧偏移。这个问题的答案本质上是这样的:

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

太好了,在视觉上是可行的...但是,我注意到了这种讨厌的副作用。箭头键导航似乎中断了,我不能再按Enter键或Space键来“单击/按下”我在特定单元格的管理QPushButton中嵌入的QWidget。似乎在尝试使用键盘上的箭头键移出单元格时,更改焦点会使QTableWidget内部混乱。

我在网上阅读到,有人说要在设置焦点后禁用表上的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时,您希望能够看到突出显示的单元格随光标移动,并且“临时”为窗口小部件提供软焦点,以进行键盘事件。这仅发生在Button对象上。当选定的单元格包含Button对象时,该对象将在focusIn函数中正确打印该对象。但是,您也希望Widget也会发生相同的行为,因为它的添加方式和代码完全相同,只是在Button中嵌入了QHBoxLayout。导航到ButtonWidget后,TableWidget的键盘导航似乎会中断,并且按键不会从Widget转到其子{{1 }},即使我将Button上的setFocusProxy设置为其Widget字段,该字段与可以正确获取m_button的类型完全相同的Button 。我不确定这是否是错误或是否已纠正某些行为。

1 个答案:

答案 0 :(得分:0)

嗯,事实证明,此行为是由QAbstractButton::keyPressEvent(QKeyEvent *e)函数引起的。因此,当将按钮添加到TableWidget的单元格窗口小部件时,我相信该表将显式成为其父窗口小部件。这对于内存管理是有意义的,没事的。

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

因此解决方案是实现Button::keyPressEvent(QPressEvent *e)并在Qt::Key_DownQt::Key_Up的每种情况下进行覆盖,以发出将被Table捕获并返回的信号。在任何一种情况下都不要break并调用基类实现。发出emit标识符时,就像QAbstractButtonPrivate::move(int key)函数对表所做的一样,并将您添加到Table的插槽中的选定单元格/当前项目设置为下一个项目/小部件向右/向左或向上/向下移动,就像您期望的QTableWidgetItems的正常按键行为一样。这似乎可行,我终于可以通过正确的选择行为来集中关注Widget中嵌入的按钮。