0

I am developing an interface with 3 QCombobox on each row and the interface will include up to 50'000 rows. Each QCombobox is filled with a list of 15'000 items.

The populating process is extremely slow. Is it possible to speed up the process? My current code populates QCombobox for only 10 rows. At 10 rows, the process takes time and with 50'000 rows, the interface doesn't show.

Backend:

import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, 
QMessageBox, QDialog, QFileDialog
from frontend import Ui_MainWindow

class Ui_MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self, parent=None):
        super(Ui_MainWindow, self).__init__(parent)
        self.setupUi(self)

        myList = ['AAA'] * 15000

        for i in range(1, 10):
            self.cb1 = QtWidgets.QComboBox()
            self.cb2 = QtWidgets.QComboBox()
            self.cb3 = QtWidgets.QComboBox()

            self.cb1.addItems(myList)
            self.cb2.addItems(myList)
            self.cb3.addItems(myList)

            self.gridLayout_2.addWidget(self.cb2,i,0)
            self.gridLayout_2.addWidget(self.cb1,i,1)
            self.gridLayout_2.addWidget(self.cb2,i,2)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    current_path = os.getcwd()
    app.processEvents()
    prog = Ui_MainWindow()
    prog.showMaximized()
    sys.exit(app.exec_())

Frontend:

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(606, 446)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 586,             385))    

    self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.gridLayout_3 = 
        QtWidgets.QGridLayout(self.scrollAreaWidgetContents)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.gridLayout_2 = QtWidgets.QGridLayout()
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.gridLayout_3.addLayout(self.gridLayout_2, 0, 0, 1, 1)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout.addWidget(self.scrollArea, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 606, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        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"))


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_())
H. Dave
  • 549
  • 3
  • 9
  • 26
  • Is it actually the same list? If so, then create one model and use it for all combo-boxes. Having said that, creating 50 thousand rows of widgets up-front is a horribly broken design. You really should be using something like a table with a database backend. – ekhumoro Feb 02 '19 at 17:54

1 Answers1

0

You can try to override the showPopup method

import sys
import os
from PyQt5           import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import ( QMainWindow, QApplication, QWidget, 
                              QMessageBox, QDialog, QFileDialog)
#from frontend import Ui_MainWindow


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(606, 446)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 586, 385))    

        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")

        self.gridLayout_3 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.gridLayout_2 = QtWidgets.QGridLayout()
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.gridLayout_3.addLayout(self.gridLayout_2, 0, 0, 1, 1)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout.addWidget(self.scrollArea, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 606, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        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"))


class ComboBox(QtWidgets.QComboBox):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self.myList = ['AAA-{}'.format(i) for i in range(1, 15000)]  #['AAA'] * 15000

    def showPopup(self):
        self.hidePopup()

        self.addItems(self.myList)
        """ # or 
        for var in range(1, 15000): 
            self.addItem("TEST " + str(var))
        """    
        super(ComboBox, self).showPopup()


    def hidePopup(self):
        super(ComboBox, self).hidePopup()


#class Ui_MainWindow(QMainWindow, Ui_MainWindow):
class My_MainWindow(QMainWindow, Ui_MainWindow):

    def __init__(self):      
        super().__init__()   

        self.setupUi(self)

#        myList = ['AAA-{}'.format(i) for i in range(1, 1500)] #['AAA'] * 15000

        for i in range(1, 10):
            self.cb1 = ComboBox()     # QtWidgets.QComboBox()
            self.cb2 = ComboBox()     # QtWidgets.QComboBox()
            self.cb3 = ComboBox()     # QtWidgets.QComboBox()

#            self.cb1.addItems(myList)
#            self.cb2.addItems(myList)
#            self.cb3.addItems(myList)

            self.gridLayout_2.addWidget(self.cb1,i,0) # cb2
            self.gridLayout_2.addWidget(self.cb2,i,1) # cb1
            self.gridLayout_2.addWidget(self.cb3,i,2) # cb2

if __name__ == '__main__':
    app = QApplication(sys.argv)
    current_path = os.getcwd()
    app.processEvents()
    prog = My_MainWindow()                  # Ui_MainWindow()
    prog.showMaximized()
    prog.show()
    sys.exit(app.exec_())

enter image description here

S. Nick
  • 12,879
  • 8
  • 25
  • 33
  • thank you for the piece of code. The code proposed definitely speed up the process. However, the ultimate goal is to populate up to 50'000 rows and unfortunately, the process time is still too slow. Based on @ekhumoro's comment, I also tried to use a `QTableWidget` but it doesn't help. – H. Dave Feb 03 '19 at 14:22
  • @H.Dave Why do you think you need to create fifty thousand rows? What purpose does that serve? The user will only ever be able to view a few hundred at a time. By itself, a table-widget will make no difference. The performance depends on the backend, and how the table is populated. The key to it is to avoid trying to display everything at once. – ekhumoro Feb 04 '19 at 18:19
  • The reason why is I am trying to convert an Excel-based tool into a `Python` application which has 50'000 rows. But I agree with you and I think that 100 rows will be enough. However, even with 100 rows, the population process is quite long. Are there technics other than `addItems` for populating a `QCombobox` in a more efficient way? – H. Dave Feb 04 '19 at 18:34
  • @H.Dave Does each combo-box have a different list? If there's only one (or a few) lists, you can use shared models. But I still don't see why you need to create all those combo-boxes up front. A table-view has built-in cell-editing functionality that will create the necessary editor widget on demand (which is obviously much more efficient creating them all up front). See [this question](https://stackoverflow.com/q/17615997/984421) for some ideas on how to implement that. – ekhumoro Feb 06 '19 at 18:19
  • @ ekhumoro Thank you for the code suggested. I am trying to convert the code in `PyQt5` but with no success for the moment. However, I would like to understand how it works. Does the `QTableView` is created with empty `QCombobox` and when the user selects the `QCombobox` then the `QItemDelegate` populates the `QCombobox` with a pre-defined list? – H. Dave Feb 09 '19 at 20:31