我做了一个子类化QTableWidget
,想让一些单元格QPushButtons
为单元格小部件,因为我已经使用QPropertyAnimations
制作了一个样式非常多的按钮,而不是什么,并且真的想将小部件嵌入到单元格中。好吧,我使用了setCellWidget(QWidget* widget)
功能,这是QTableWidget
的一部分,它几乎是完美的。由于我在表的单元格项目上绘制了一些子类QStyledItemDelegate
类,因此我绘制了一些似乎与单元格小部件的大小有些冲突的边框线。
以前有人问过如何在QTableWidget's
单元格中居中QCheckBox
,这样它就不会在左侧偏移。这个问题的答案基本上是这样的:
- 创建
QWidget
- 创建要居中的
QWidget
- 创建
QH/VLayout
并将顶级布局设置为QWidget's
- 将要居中的子微件添加到此布局
- 在
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
。导航到Button
或Widget
后,TableWidget
的键盘导航似乎中断了,即使我将Widget
上的setFocusProxy
设置为其m_button
字段,按键也不会从Widget
转发到其子Button
,该字段与可以正确获取KeyEvent
的完全相同类型的Button
。我不太确定这是一个错误还是我破坏了一些行为。
事实证明,这种行为是由QAbstractButton::keyPressEvent(QKeyEvent *e)
函数引起的。因此,当按钮添加到TableWidget
的单元格小部件时,我相信表格明确成为它们的父小部件。这对于内存管理有意义,好吧。
因此,QAbstractButtons
有一个用于Qt::Key_Down
和Qt::Key_Up
键的 case 语句,它们本质上检查其父小部件是否属于QAbstractItemView*
类型。如果他们这样做,他们将调用QAbstractButtonPrivate::move(int key)
函数。这将在查询所有按钮的列表后确定按钮buttonGroup
的一部分(我不确定我们是否可以控制),下一个按钮应该是哪个按钮。这显然是在QTableWidget
继承的QAbstractItemView
中,按列主要顺序对它们进行排序,例如,如果下一行或下一列中有按钮,如果捕获Qt::Key_Down
,则该列将被选中而不是下一行。这解释了我收到的行为。为什么他们把这作为基本行为打败了我,因为我觉得很奇怪,我不能改变这种行为,因为它是QAbstractButtonPrivate
类的一部分,我们显然都无法控制。幸运的是,它virtual
,我们可以覆盖该行为。
因此,解决方案是在每种情况下实现Button::keyPressEvent(QPressEvent *e)
和覆盖Qt::Key_Down
和Qt::Key_Up
,以发出将被Table
捕获并返回的信号。不要break
并调用这两种情况的基类实现。发出时,emit
一个标识符,就像QAbstractButtonPrivate::move(int key)
函数对表格所做的那样,并将添加到Table
的插槽中的选定单元格/当前项目设置为右侧/左侧或向上/向下的下一个项目/小部件,正如您所期望的那样,正常的键行为为QTableWidgetItems
。这似乎有效,我终于可以通过正确的选择行为获得嵌入在小部件中的按钮的焦点。