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