pythonpyqt5qthreadqpushbuttonqicon

Python PyQt5 load Image for QPushButton on QThread


So I have this UI that loads ALOT of buttons, all with Images, problem is, it takes WAY to long, so I tried putting it into a QThread, I had it working, but there was no speed difference, so I tried a different solution, but now the Thread wont start.

Code:

import sys
import os
from PyQt5 import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtPrintSupport, QtWidgets, uic

# I've tried using a QThread for this, but it still took the same amount of time.
class LoadImageThumbnailshread(QThread):
    def __init__(self, listButtons, listPaths):
        QThread.__init__(self)
        self.listButtons = listButtons
        self.listPaths = listPaths

    def run(self):
        self.process_images_Thread()

    def process_images_Thread(self):
        for i, j in enumerate(self.listButtons):
            j.setIcon(QIcon(self.listPaths[i]))
            j.setIconSize(QSize(150-6, 60-6))


class App(QDialog):

    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 layout - pythonspot.com'
        self.left = 10
        self.top = 10
        self.width = 320
        self.height = 100
        self.images_path = []
        self.button_images = []
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.createGridLayout()

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.horizontalGroupBox)
        self.setLayout(windowLayout)

        self.show()

    def createGridLayout(self):
        self.horizontalGroupBox = QGroupBox("Grid")
        layout = QGridLayout()
        layout.setColumnStretch(1, 4)
        layout.setColumnStretch(2, 4)

        for i in range(100):
            self.btnImage = QPushButton()
            self.btnImage.setObjectName('btnImage')
            self.images_path.append(os.path.dirname(
                os.path.abspath(__file__)) + 'view.png')

            # ! I have also tried using Pixmaps with Clickable labels, but it made no diffrence.
            # pixmap = QPixmap(os.path.dirname(os.path.abspath(__file__)) + image_locations[i])
            # pixmap = pixmap.scaled(60, 300, Qt.KeepAspectRatio, Qt.FastTransformation)
            # self.btnImage.setPixmap(pixmap)

            # ! Enableing this loads the images, but very slowly
            # self.btnImage.setIcon(QIcon(os.path.dirname(os.path.abspath(__file__)) + '/view.png'))
            # self.btnImage.setIconSize(QSize(150-6, 60-6))
            self.button_images.append(self.btnImage)
            layout.addWidget(self.btnImage, i, 0)

        # ! This starts the QThread
        converter = LoadImageThumbnailshread(
            self.button_images, self.images_path)
        converter.start()
        self.horizontalGroupBox.setLayout(layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

So to explain more of what I want, I want to start a QThread AFTER the UI has loaded, that QThread will load all of the Images onto each of the buttons. Problem is: it doesn't work right now.

Another problem that I had, is that when I got the QThread the UI waited for it to finish, and then everything all of a sudden popped up. I want the QThread to be completely separate and pretty, much see all images loading one by one if you will.


Solution

  • The threads do not serve to accelerate any task!!! but to execute tasks that do not block any thread. Given the above, if I have n "task" and each one is executed in "n" threads then the total execution time will be 1 task, that is, no task is accelerated but the tasks are redistributed. In conclusion: If you want to speed up a task then threads is not a default option as it depends on the application.

    On the other hand, the loading of the image does not consume time but there are many tasks that consume little time that in total is equivalent to a task that consumes a lot of time and unfortunately that task cannot be executed in another thread or process since the elements of the GUI are not thread-safe.

    A workaround is that the load is of a small group of n elements every T seconds so the total task will be distributed and the user will not observe any effect of lag.

    import os
    import sys
    
    from PyQt5.QtCore import QSize, QTimer
    from PyQt5.QtGui import QIcon
    from PyQt5.QtWidgets import (
        QApplication,
        QDialog,
        QGridLayout,
        QGroupBox,
        QPushButton,
        QVBoxLayout,
    )
    
    
    class App(QDialog):
        def __init__(self):
            super().__init__()
            self.title = "PyQt5 layout - pythonspot.com"
            self.left = 10
            self.top = 10
            self.width = 320
            self.height = 100
            self.images_path = []
            self.button_images = []
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle(self.title)
            self.setGeometry(self.left, self.top, self.width, self.height)
    
            self.createGridLayout()
    
            windowLayout = QVBoxLayout(self)
            windowLayout.addWidget(self.horizontalGroupBox)
    
            self._iter = iter(range(100))
            self._timer = QTimer(interval=10, timeout=self.lazy_loading)
            self._timer.start()
    
        def createGridLayout(self):
            self.horizontalGroupBox = QGroupBox("Grid")
            self.grid_layout = QGridLayout()
            self.grid_layout.setColumnStretch(1, 4)
            self.grid_layout.setColumnStretch(2, 4)
            self.horizontalGroupBox.setLayout(self.grid_layout)
    
        def lazy_loading(self):
            try:
                i = next(self._iter)
            except StopIteration:
                self._timer.stop()
            else:
                btn = QPushButton()
                image_path = os.path.join(os.path.abspath(__file__), "view.png")
                btn.setIcon(QIcon(image_path))
                btn.setIconSize(QSize(150 - 6, 60 - 6))
                self.grid_layout.addWidget(btn, i, 0)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        ex = App()
        ex.show()
        sys.exit(app.exec_())