Search code examples
pythonpython-3.xpyqtpyqt5qtablewidget

How can I set Custom Key board Key press event in Qtablewidget Python?


I have a QTableWidget generated by QTDesigner in which I want to set some custom key press events.

  1. when current cell is in edit mode and if user presses Tab key, it moves to next cell in edit mode but i just want it move to next cell selected and not in edit mode.

  2. when current cell is in edit mode and if user presses Left, right, up, down- it should move to corresponding cell in select mode again and not in edit mode.

both of this works well right now if current cell is just selected and not in edit mode. but how can i set this customized event?

Thanks!!

Code Generated by UI Designer:

from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(428, 285)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.tableWidget = QtWidgets.QTableWidget(self.centralwidget)
        self.tableWidget.setGeometry(QtCore.QRect(20, 20, 391, 231))
        self.tableWidget.setEditTriggers(QtWidgets.QAbstractItemView.AnyKeyPressed|QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked)
        self.tableWidget.setRowCount(5)
        self.tableWidget.setColumnCount(3)
        self.tableWidget.setObjectName("tableWidget")
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(2, item)
        self.tableWidget.horizontalHeader().setVisible(True)
        self.tableWidget.verticalHeader().setVisible(False)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        item = self.tableWidget.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Name"))
        item = self.tableWidget.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Age"))
        item = self.tableWidget.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "City"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

My Script:

from PyQt5 import QtWidgets, QtCore
from demo import Ui_MainWindow

class DemoTable(QtWidgets.QMainWindow, Ui_MainWindow):                 
    def __init__(self):
        super(DemoTable, self).__init__()
        self.setupUi(self) 

        #KeyPressEvent
        self.tableWidget.keyPressEvent = self.KeyPressed

    def KeyPressed(self,event):
        if event.key() == QtCore.Qt.Key_Left:
            print('Left Key Pressed')
        elif event.key() == QtCore.Qt.Key_Right:
            print('Right Key Pressed')
        elif event.key() == QtCore.Qt.Key_Tab:
            print('Tab Key Pressed')
        return QtWidgets.QTableWidget.keyPressEvent(self.tableWidget, event)

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    demowindow = DemoTable()   
    demowindow.show()
    sys.exit(app.exec_())

Solution

  • You can reimplement the closeEditor() method, which is responsible of the action that the item view will take when an item editing has been completed.

    In this example I'll overwrite the method as you did with the keypress event, but I strongly suggest you to use subclasses and implement the overrides in them, as the code would be cleaner and can avoid confusion between existing functions and/or overwritten methods.

    class DemoTable(QtWidgets.QMainWindow, Ui_MainWindow):                 
        def __init__(self):
            super(DemoTable, self).__init__()
            self.setupUi(self) 
    
            #KeyPressEvent
            self.tableWidget.keyPressEvent = self.KeyPressed
            self.tableWidget.closeEditor = self.closeEditor
    
        def closeEditor(self, editor, hint):
            if hint in (QtWidgets.QItemDelegate.EditNextItem, 
                QtWidgets.QItemDelegate.EditPreviousItem):
                    # if the hint is to edit the next or previous item, ignore it
                    newHint = QtWidgets.QItemDelegate.NoHint
            else:
                newHint = hint
    
            # call the base implementation with the new hint
            QtWidgets.QTableWidget.closeEditor(self.tableWidget, editor, newHint)
    
            if hint == QtWidgets.QItemDelegate.EditNextItem:
                # find the next item to focus on
                index = self.tableWidget.moveCursor(self.tableWidget.MoveNext, 
                    QtCore.Qt.NoModifier)
            elif hint  == QtWidgets.QItemDelegate.EditPreviousItem:
                # find the previous item to focus on
                index = self.tableWidget.moveCursor(self.tableWidget.MovePrevious, 
                    QtCore.Qt.NoModifier)
            else:
                return
            # set the new current item
            self.tableWidget.setCurrentIndex(index)
    
        # ...
    

    Since you also want to move to the items using the arrow keys even while in editing state, you can only do this by installing a custom item delegate that checks for keyboard events in the editor.

    I have to warn you, though: don't do this.
    Left and right arrow keys are always used for cursor navigation when editing text, changing this behavior is absolutely discouraged.
    It's unintuitive, unnatural, uncomfortable, against any common and expected behavior, and will make users accustomed to keyboard navigation very, very, very annoyed. I've seen a similar behavior in a program in the past, and I can tell you that it's not good and it's very irritating.

    class DelegateYouShouldNotUse(QtWidgets.QStyledItemDelegate):
        def eventFilter(self, source, event):
            if isinstance(source, QtWidgets.QLineEdit) and event.type() == QtCore.QEvent.KeyPress:
                if event.key() == QtCore.Qt.Key_Right:
                    # tell the view to store the current data
                    self.commitData.emit(source)
                    # and to move to the next item (since we've changed the
                    # behavior of the closeEditor *slot* of the table, it
                    # will only move to the item, without starting editing
                    self.closeEditor.emit(source, self.EditNextItem)
                    return True
                elif event.key() == QtCore.Qt.Key_Left:
                    self.commitData.emit(source)
                    self.closeEditor.emit(source, self.EditPreviousItem)
                    return True
            return super().eventFilter(source, event)
    
    
    class DemoTable(QtWidgets.QMainWindow, Ui_MainWindow):                 
        def __init__(self):
            super(DemoTable, self).__init__()
            # ...
            self.veryBadDelegate = DelegateYouShouldNotUse(self.tableWidget)
            self.tableWidget.setItemDelegate(self.veryBadDelegate)
    

    In case it wasn't clear enough, you should not use this :-)