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



我做了一个子类化QTableWidget,想让一些单元格QPushButtons为单元格小部件,因为我已经使用QPropertyAnimations制作了一个样式非常多的按钮,而不是什么,并且真的想将小部件嵌入到单元格中。好吧,我使用了setCellWidget(QWidget* widget)功能,这是QTableWidget的一部分,它几乎是完美的。由于我在表的单元格项目上绘制了一些子类QStyledItemDelegate类,因此我绘制了一些似乎与单元格小部件的大小有些冲突的边框线。

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

  1. 创建QWidget
  2. 创建要居中的QWidget
  3. 创建QH/VLayout并将顶级布局设置为QWidget's
  4. 将要居中的子微件添加到此布局
  5. QTableWidget使用setCellWidget功能并将其设置为具有顶级QWidget的行和列,瞧,单元格中有一个居中QWidget,您可以根据需要进行操作和对齐

太好了,这在视觉上有效...但是,我注意到这有一些令人讨厌的副作用。箭头键导航似乎中断,我无法再按 Enter 键或空格键来"单击/按下"我嵌入到该特定单元格的管理QWidget中的QPushButton。似乎在尝试使用键盘上的箭头键离开单元格时,更改焦点会在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

表格小部件.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;
}
}
}
}

按钮.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

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

小部件.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

小部件.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也发生相同的行为,因为它以完全相同的方式添加具有完全相同的代码,只是在其QHBoxLayout中嵌入了Button。导航到ButtonWidget后,TableWidget的键盘导航似乎中断了,即使我将Widget上的setFocusProxy设置为其m_button字段,按键也不会从Widget转发到其子Button,该字段与可以正确获取KeyEvent的完全相同类型的Button。我不太确定这是一个错误还是我破坏了一些行为。

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

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

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

相关内容

  • 没有找到相关文章

最新更新