Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTableWidget setCellWidget(QWidget*) Inconsistant Behavior with Cell Selection and Focus



  • 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:

    1. Create a QWidget
    2. Create the QWidget you want to center
    3. Create a QH/VLayout and set the top level QWidget's layout to be this
    4. Add the sub widget that you want to center to this layout
    5. On the QTableWidget 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 want

    Great, 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:

    #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.