Search code examples
c++qtqtablewidgettab-ordering

Clear focus from QTableWidget once tab key navigation reaches the last item


I customized a dialog, and when pressing the Tab key, the focus switches from the button to the table widget. I want the focus to return to the initial button after all the items in the table widget have been traversed. Here’s a minimal reproducible example:

#include <QApplication>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>

class Widget : public QWidget
{
public:
    explicit Widget()
    {
        setLayout(&vl);

        tw.setRowCount(1);
        tw.setColumnCount(3);

        vl.addWidget(&btn);
        vl.addWidget(&tw);
    }

    bool focusNextPrevChild(bool next)
    {
        QWidget* focusWidget = this->focusWidget();

        if (focusWidget == &tw)
        {
            int currentRow = tw.currentRow();
            int currentCol = tw.currentColumn();
            int lastRow = tw.rowCount() - 1;
            int lastCol = tw.columnCount() - 1;

            if (next && currentRow == lastRow && currentCol == lastCol)
            {
                tw.clearFocus();
                return true;
            }
        }

        return QWidget::focusNextPrevChild(next);
    }
    
private:
    QPushButton btn;
    QTableWidget tw;
    QVBoxLayout vl;
};

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);

    Widget w;
    w.show();

    return a.exec();
}

enter image description here

I tried overriding the focusNextPrevChild function, but it didn't work. Once the focus enters the table widget, it can't leave it.


Solution

  • musicamante said:

    You're overriding focusNextPrevChild of the parent, which is completely pointless since it will never be triggered as long as the table widget manages it. [...]

    You can check that by adding a qDebug to check focusWidget in focusNextPrevChild . The if (focusWidget == &tw) check will never be true.

    [...] moveCursor() implementation.

    Here's how you could use moveCursor() to achieve what you want:

    #include <QApplication>
    #include <QPushButton>
    #include <QTableWidget>
    #include <QVBoxLayout>
    #include <QKeyEvent>
    
    class TableWidget : public QTableWidget
    {
        Q_OBJECT
    
    signals:
        void firstItemBackTab();
        void lastItemTab();
    
    protected:
        QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
        {
            QModelIndex current = currentIndex();
            int row = current.row();
            int col = current.column();
            int lastRow = rowCount() - 1;
            int lastCol = columnCount() - 1;
    
            //tab press on the last cell
            if(cursorAction == MoveNext && row == lastRow && col == lastCol)
                emit lastItemTab();
    
            //tab+shift (backtab) press on the first cell
            if(cursorAction == MovePrevious && row == 0 && col == 0)
                emit firstItemBackTab();
    
            return QTableWidget::moveCursor(cursorAction, modifiers);
        }
    };
    
    class Widget : public QWidget
    {
    public:
        explicit Widget()
        {
            setLayout(&vl);
    
            tw.setRowCount(3);
            tw.setColumnCount(3);
    
            vl.addWidget(&btn);
            vl.addWidget(&tw);
            vl.addWidget(&btn1);
    
            //the weakest point of this approach is here (as far as my "expertise" go)
            //it's possible the next/previous child is not what you'd expect.
            //Tab order and all. Perhaps QWidget::setTabOrder could help with that.
            connect(&tw, &TableWidget::firstItemBackTab, [=]()
            {
                focusPreviousChild();
            });
            connect(&tw, &TableWidget::lastItemTab, [=]()
            {
                focusNextChild();
            });
        }
    
    private:
        QPushButton btn{"Button 1"};
        TableWidget tw;
        QPushButton btn1{"Button 2"};
        QVBoxLayout vl;
    };
    
    int main(int argc, char* argv[])
    {
        QApplication a(argc, argv);
    
        Widget w;
        w.show();
    
        return a.exec();
    }
    
    #include "main.moc"
    

    moveCursor here replaces your logic inside focusNextPrevChild, and replaces the check for next/previous using the already available QAbstractItemView::CursorAction.

    This approach is for basic widget cells. So, I don't see a reason to reimplement focusNextPrevChild. You'd probably want that when your cell widgets are complex, and you want to customize the tab navigation order inside them. But based on your implemented logic inside focusNextPrevChild, the basic approach should suffice.