Search code examples
pythonpython-3.xuser-interfacepyqt5pyqt4

PyQt5 How to press enter on a QTableWidget and go to the cell below?


I am trying to elaborate a QTableWidget like the excel tables, using PyQt5 and Python.

For this I want to activate the option of pressing the enter button the cell below goes into focus.

How to do this?

Here is my attempt:

    def keyPressEvent(self, event):
        super().keyPressEvent(event)
        row = self.tableWidget.currentRow()
        col = self.tableWidget.currentColumn()
        if event.key() == Qt.Key_Enter and row < self.tableWidget.rowCount():
            row += 1
            self.tableWidget.setCurrentCell(row, col)
            self.tableWidget.edit(self.tableWidget.currentIndex())


Solution

  • Your attempt didn't work because the keyPressEvent that you want to react to must be received by the table, which is not your case, as you're clearly trying to do that from the parent window that contains that table.

    When a key event happens, the current focused widget receives it; if that widget is not interested in that event, it is sent back to the widget's parent, and so on until some widget is actually able to react to that event and, possibly, accept it; once the event has been accepted, no parent widget will receive it.

    While some widgets just ignore some or any keyboard events (for instance, QLabel), other just "eat" almost any event, and that's the case of item views like QTableWidget. If the table has focus, when it receives the return key event it will accept it, that event will not be propagated anymore and that's why you're not able to receive it from your keyPressEvent override.

    The most used solution is to subclass the table and implement the keyPressEvent for that class (as explained in the answer given by Gibus).

    A common alternative is to use an event filter, and it's very useful if you're using a GUI created in Designer, as it allows to add small features without the need to subclass and use promoted widgets.

    class MyWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.tableWidget = QtWidgets.QTableWidget
            self.setCentralWidget(self.tableWidget)
            self.tableWidget.installEventFilter(self)
    
        def eventFilter(self, source, event):
            if (event.type() == QtCore.QEvent.KeyPress and 
                event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter)):
                    # ensure that the table receives the key event first
                    res = super().eventFilter(source, event)
                    current = self.tableWidget.currentIndex()
                    nextIndex = current.sibling(current.row() + 1, current.column())
                    if nextIndex.isValid():
                        self.tableWidget.setCurrentIndex(nextIndex)
                        self.tableWidget.edit(nextIndex)
                    return res
            return super().eventFilter(source, event)
    

    Finally, there is the "monkey patching", which can be used whenever very simple class modifications are required without the need to create subclasses. This also works for Designer created GUIs.
    Note that monkey patching does not work for everything: some methods cannot be overwritten, especially not once the instance has been; notably, PyQt uses cached function binding which means that if a base implementation method is called prior overriding it, there's no way to override it afterwards.

    class MyWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.tableWidget = QtWidgets.QTableWidget
            self.setCentralWidget(self.tableWidget)
            self.tableWidget.keyPressEvent = self.tableKeyPressEvent
    
        def tableKeyPressEvent(self, event):
            # call the base implementation, do *not* use super()!
            QtWidgets.QTableWidget.keyPressEvent(self.tableWidget, event)
            if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
                current = self.tableWidget.currentIndex()
                nextIndex = current.sibling(current.row() + 1, current.column())
                if nextIndex.isValid():
                    self.tableWidget.setCurrentIndex(nextIndex)
                    self.tableWidget.edit(nextIndex)