I've made a subclassed QTableWidget
and wanted to make some cells have QPushButtons
as cell widgets since I've made a pretty heavily styled button using QPropertyAnimations
and what not and really wanted to embed the widget in the cell. Well I've used the setCellWidget(QWidget* widget)
function that is a part of the QTableWidget
and it was almost perfect. Since I do some subclassed QStyledItemDelegate
class drawing on my table's cell items, I draw some border lines that seemed to be conflicting a bit with the size of the cell widget.
Previously someone asked how to center a QCheckBox
in a QTableWidget's
cell so it's not offset on the left hand side. The answer to that question was essentially this:
QWidget
QWidget
you want to centerQH/VLayout
and set the top level QWidget's
layout to be thisQTableWidget
use the setCellWidget
function and set it for the row and column with the top level QWidget
and voila, you have a centered QWidget
in the cell that you can manipulate and align however you wantGreat, that visually worked... However, I noticed some nasty side effects of this. The arrow key navigation seems to break and I can no longer press the Enter key or Space key to "click/press" the QPushButton
that I embedded in the managing QWidget
for that particular cell. It seems that changing the focus messes something up internally on the QTableWidget
when trying to move away from the cell with the arrows keys on the keyboard.
I read online that some people said to disable the setTabKeyNavigation(bool)
on the table to fix some similar navigation issues after setting focus... This did not do anything in my case. I've made a minimal compilable example that show cases the behavior
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);
}
So, when navigating the TableWidget
you would expect to be able to see the highlighted cells to move with the cursor and "temporarily" give soft Focus to the widget for keyboard events. This happens ONLY on the Button
object. The Button
object will correctly print that in the focusIn
function that it went off when the cell selected contains it. However, you would expect the same behavior to occur for the Widget
too since its added exactly the same way with exactly the same code, just with a Button
embedded inside of its QHBoxLayout
. Once you navigate to the Button
or the Widget
the keyboard navigation for the TableWidget
seems to break and key presses don't forward from the Widget
to its child Button
even if I were to set the setFocusProxy
on the Widget
to its m_button
field which is the exact same type Button
that can correctly get the KeyEvent
. I'm not quite sure if this is a bug or if I've mangled some behavior.
Well, it turns out this behavior is caused specifically by the QAbstractButton::keyPressEvent(QKeyEvent *e)
function. So, when the buttons are added to the cell widget of the TableWidget
, I believe the table explicitly becomes their parent widget. This makes sense for memory management and what not, okay.
So, the QAbstractButtons
have a case statement for the Qt::Key_Down
and Qt::Key_Up
keys and they essentially check if their parent widget is of the type QAbstractItemView*
. If they do, they call the QAbstractButtonPrivate::move(int key)
function. This will determine after querying a list of all buttons a part of the button's buttonGroup
(something that I'm not sure we have any control over), which button should be next. This apparently in a QAbstractItemView
, which a QTableWidget
inherits from, orders them in column major order such then if there's a button in the next row or the next column, the column will instead be chosen over the next row if the Qt::Key_Down
is caught. This explains the behavior I was receiving. Why they make this the base behavior beats me, because I find it very strange that I can't alter this behavior since it's a part of the QAbstractButtonPrivate
class which we all obviously have no control over. Luckily it's virtual
and we can override the behavior.
So the solution is to implement the Button::keyPressEvent(QPressEvent *e)
and override in each case of Qt::Key_Down
and Qt::Key_Up
to emit a signal that will be captured by the Table
and return. Do not break
and call the base class implementation for either of these cases. When emitting, emit
an identifier, much like the QAbstractButtonPrivate::move(int key)
function does to the table and set the selected cell/current item in a slot your add to the Table
to be the next item/widget to the right/left or up/down as you would expect with the normal key behavior of QTableWidgetItems
. This seemed to work and I could finally get focus of the buttons embedded in the Widget with correct selection behavior.