Search code examples
pythonpyqt4qsplashscreen

qsplashscreen doesn't displays properly until app.processEvents() is called twice


PyQT4.11
Python 2.7

I have been trying to create a splash screen to display a loading message when a specific tab is selected. When app.processEvents() is called I get a grey box displayed instead of the image, but if I add a second app.processEvents() after the first function is called the correct image is displayed until the splash is closed on completion.

I have played around with sleep timers, repainting, updating, closing and reshowing the tab and tabwidget with mixed results, on the rare occasion it will work but more often than not it won't. Can anyone shed any light on what I am missing?

import sys
from PyQt4 import QtCore, QtGui

class splashTest(QtGui.QDialog):
    def __init__(self, parent=None):
        super(splashTest, self).__init__(parent)

        self.setGeometry(000,000,800,400)
        self.tabWidget = QtGui.QTabWidget(self)
        self.tabWidget.setGeometry(QtCore.QRect(0, 0, 500, 500))
        self.tabWidget.setObjectName("tabWidget")
        self.tab1 = QtGui.QWidget()
        self.tab2 = QtGui.QWidget()
        self.tab3 = QtGui.QWidget()

        self.tabWidget.addTab(self.tab1, "tab1")
        self.tabWidget.addTab(self.tab2, "tab2")
        self.tabWidget.addTab(self.tab3, "Click Me")

        self.tabWidget.setCurrentIndex(1)

        self.tabWidget.currentChanged.connect(self.whichTabIsSelected)


def whichTabIsSelected(self):
    if self.tabWidget.currentIndex() == 2:
        splash_pix = QtGui.QPixmap('splash.png')
        splash = QtGui.QSplashScreen(splash_pix, QtCore.Qt.WindowStaysOnTopHint)
        splash.setMask(splash_pix.mask())
        splash.show()
        app.processEvents()
        self.timeConsumingFunction()
        app.processEvents() 
        self.timeConsumingFunction()

        splash.finish(self.tabWidget)

    else:
        pass

def timeConsumingFunction(self):
    b = 0
    for a in range(100000000):
        b += a
    print (b)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = splashTest()
    myapp.show()
    sys.exit(app.exec_())

Solution

  • The problem is that you are running the heavy task in the main thread blocking the event loop of the GUI, and this causes the window to freeze, and you are forcing the update using QApplication::processEvents(), instead the correct solution is to execute that task heavy on another thread.

    import sys
    from PyQt4 import QtCore, QtGui
    import threading
    
    class splashTest(QtGui.QDialog):
        finished = QtCore.pyqtSignal()
    
        def __init__(self, parent=None):
            super(splashTest, self).__init__(parent)
    
            self.setGeometry(000,000,800,400)
            self.tabWidget = QtGui.QTabWidget(self)
            self.tabWidget.setGeometry(QtCore.QRect(0, 0, 500, 500))
            self.tabWidget.setObjectName("tabWidget")
            self.tab1 = QtGui.QWidget()
            self.tab2 = QtGui.QWidget()
            self.tab3 = QtGui.QWidget()
    
            self.tabWidget.addTab(self.tab1, "tab1")
            self.tabWidget.addTab(self.tab2, "tab2")
            self.tabWidget.addTab(self.tab3, "Click Me")
    
            self.tabWidget.setCurrentIndex(1)
            self.tabWidget.currentChanged.connect(self.whichTabIsSelected)
    
        @QtCore.pyqtSlot(int)
        def whichTabIsSelected(self, index):
            if index == 2:
                splash_pix = QtGui.QPixmap('splash.png')
                splash = QtGui.QSplashScreen(self, splash_pix, QtCore.Qt.WindowStaysOnTopHint)
                splash.setMask(splash_pix.mask())
                splash.show()
                for _ in range(2):
                    loop = QtCore.QEventLoop()
                    self.finished.connect(loop.quit)
                    threading.Thread(target=self.timeConsumingFunction).start()
                    loop.exec_()
                splash.finish(self.tabWidget)
                splash.close()
    
        def timeConsumingFunction(self):
            b = 0
            for a in range(100000000):
                b += a
            print (b)
            self.finished.emit()
    
    
    if __name__ == "__main__":
        app = QtGui.QApplication(sys.argv)
        myapp = splashTest()
        myapp.show()
        sys.exit(app.exec_())