Search code examples
python-3.xpyqt5qtablewidgetqpushbutton

PYQT5 setCellWidget() on QTableWidget slows down UI


When using the setCellWidget() in PyQT5 on a QTableWidget() I run into performance issues. As soon as my for-loop contains about 100 records coming from an SQL database the delay gets noticeable. At about 500 records the delay takes up to 3 seconds.

I've disabled the setCellWidget() part and tested for 20.000 records and there was hardly a delay. So performing and fetching the query isn't delaying the code.

the self.queueTable is a QTableWidget() of 8 columns and as many rows as returned by the query stored in the variable tasks

Here is the code I used:

    def buildQueueInUI(self):
        global userAccount
        .....
        tasks = Query(SQLconn, 'SQLITE', False).readParameterized(QueryStrings.myQueuedJobsList, [userAccount])
        for row in tasks:
            rowPosition = self.queueTable.rowCount()
            self.queueTable.insertRow(rowPosition)
            btt=QPushButton('DELETE')
            btt.clicked.connect(cancelTask)
            self.queueTable.setCellWidget(rowPosition, 0, btt)  ##turning this into a comment fixes the slowdown issue
            self.queueTable.setItem(rowPosition, 1, Tables.noEditTableWidget(self, str(row[0])))
            self.queueTable.setItem(rowPosition, 2, Tables.noEditTableWidget(self, str(row[2])))


         ....

I've read that the QPushButton is 'expensive'(Why get's Python with PyQt5 slow when creating a large amount of QPushButtons?) , but the problem remains when using other widgets such as a combobox (An unpractical code example with combobox:)

    def buildQueueInUI(self):
        global userAccount
        .....
        tasks = Query(SQLconn, 'SQLITE', False).readParameterized(QueryStrings.myQueuedJobsList, [userAccount])
        for row in tasks:
            rowPosition = self.queueTable.rowCount()
            self.queueTable.insertRow(rowPosition)
            combo = QComboBox()
            combo.addItem("keep")
            combo.addItem("remove")
            self.queueTable.setCellWidget(rowPosition, 0, combo)  ##turning this into a comment fixes the slowdown issue
            self.queueTable.setItem(rowPosition, 1, Tables.noEditTableWidget(self, str(row[0])))
            self.queueTable.setItem(rowPosition, 2, Tables.noEditTableWidget(self, str(row[2])))

         ....

Only by not doing the setCellWidget() call on the QTableWidget with either a QPushButton() or a QComboBox() I can render the table without delays.

In a typical use-case there'd be about 500 - 750 queued tasks. How can I have QPushButton() without the delays caused by setCellWidget()? I already have a cellDoubleClicked.connectlistener and a custom context menu on the table`, so those are not an option.

My system:

  • Python 3.7
  • PYQT5 5.14.1
  • Windows 10 64bit

Solution

  • You could try setting the row count before the for-loop instead of adding one row at the time. Consider for example the following example

    from PyQt5 import QtWidgets, QtCore
    from PyQt5.QtWidgets import QApplication, QTableWidgetItem
    from time import time
    
    class CreateTable(QtWidgets.QWidget):
    
        def __init__(self, parent = None):
            super().__init__(parent)
            fill_button_1 = QtWidgets.QPushButton('fill table - set row count')
            fill_button_1.clicked.connect(self.buildQueueInUI_1)
    
            fill_button_2 = QtWidgets.QPushButton('fill table - insert rows')
            fill_button_2.clicked.connect(self.buildQueueInUI_2)
    
            hlayout = QtWidgets.QHBoxLayout()
            hlayout.addWidget(fill_button_1)
            hlayout.addWidget(fill_button_2)
    
            self.table = QtWidgets.QTableWidget(self)
            self.table.setColumnCount(2)
    
            layout = QtWidgets.QVBoxLayout(self)
            layout.addLayout(hlayout)
            layout.addWidget(self.table)
    
        def buildQueueInUI_1(self):
            nrows = 500
            self.table.setRowCount(0)
            t0 = time()
            last_row = self.table.rowCount()
            self.table.setRowCount(nrows+self.table.rowCount())
            for i in range(500):
                row = last_row+i
                button = QtWidgets.QPushButton('Click', self)
                button.clicked.connect(lambda _, x=row+1: print('button', x))
                self.table.setCellWidget(row, 0, button)
                self.table.setItem(row, 1, QTableWidgetItem(f'item {row}'))
            print(f'set row count: {time()-t0:.4f} seconds')
    
        def buildQueueInUI_2(self):
            nrows = 500
            self.table.setRowCount(0)
            t0 = time()
            for i in range(nrows):
                row = self.table.rowCount()
                self.table.insertRow(row)
                button = QtWidgets.QPushButton('Click', self)
                button.clicked.connect(lambda _, x=row+1: print('button', x))
                self.table.setCellWidget(row, 0, button)
                self.table.setItem(row, 1, QTableWidgetItem(f'item {row}'))
            print(f'insert rows: {time() - t0:.4f} seconds')
    
    if __name__ == "__main__":
        app = QApplication([])
        win = CreateTable()
        win.show()
        app.exec_()
    

    Output

    set row count: 0.0359 seconds
    insert rows: 1.0572 seconds