Search code examples
pythonpyqtqwebview

PyQt5 How to update GUI when use evaluateJavaScript if python is still running


I would like to modify my GUI dynamically with the evaluateJavaScript command

I noticed that the GUI is updated only when python has finished working. I created this example code:

Base GUI:

webView = QWebView()
myObj = ExampleClass(webView)
webView.page().mainFrame().addToJavaScriptWindowObject("pyObj", myObj)
webView.setHtml('''
<html>
    <body>
        <div id="content"></div>
        <button onClick="pyObj.example()">start</button>
    </body>
</html>
''')

Python code for update Html GUI:

class ExampleClass(QtCore.QObject):
    def __init__(self, webView):
        super(ExampleClass, self).__init__(webView)
        self.webView = webView

    @QtCore.pyqtSlot()
    def example(self):
        print("start")
        for i in range(0,5):
            print(i)
            self.webView.page().mainFrame().evaluateJavaScript('document.getElementById("content").innerHTML = "'+str(i)+'";')
            time.sleep(1)
            #simulation of a heavy code
            for a in range(0,999):
                for b in range(0,9999):
                    c = a * b;

Objective: after pressing the "start" button I would like to see the numbers 0 1 2 3 4 sequentially appear in my GUI. Current result: after pressing the "start" button for N seconds nothing happens and then the number 4 appears


Solution

  • As @gelonida points out, the solution in general is to use threads and notify the GUI through signals, so my answer provides a possible implementation.

    To implement in threads, the for-loop must be rearranged in sequential tasks, for example using iterators, and then call the next one when the task has been completed through a signal. For the time-consuming task in this case I have created a worker who lives in another thread and the "task" method must be invoked.

    import sys
    from PyQt5 import QtCore, QtWidgets, QtWebKitWidgets
    
    
    class Worker(QtCore.QObject):
        finished = QtCore.pyqtSignal()
    
        @QtCore.pyqtSlot()
        def task(self):
            # simulation of a heavy code
            for a in range(999):
                for b in range(9999):
                    c = a * b
            self.finished.emit()
    
    
    class ExampleClass(QtCore.QObject):
        def __init__(self, webView):
            super(ExampleClass, self).__init__(webView)
            self.webView = webView
    
            thread = QtCore.QThread(self)
            thread.start()
    
            self.m_worker = Worker()
            self.m_worker.moveToThread(thread)
            self.m_worker.finished.connect(self.execute)
    
            self.m_numbers = None
    
        @QtCore.pyqtSlot()
        def example(self):
            print("example")
            self.m_numbers = iter(range(5))
            self.execute()
    
        @QtCore.pyqtSlot()
        def execute(self):
            if self.m_numbers is None:
                return
            try:
                i = next(self.m_numbers)
            except StopIteration:
                return
            else:
                self.webView.page().mainFrame().evaluateJavaScript(
                    'document.getElementById("content").innerHTML = "{}";'.format(i)
                )
                QtCore.QTimer.singleShot(1000, self.m_worker.task)
    
    
    if __name__ == "__main__":
        html = """<html>
        <body>
            <div id="content"></div>
            <button onClick="pyObj.example()">start</button>
        </body>
    </html>"""
    
        app = QtWidgets.QApplication(sys.argv)
    
        view = QtWebKitWidgets.QWebView()
        obj = ExampleClass(view)
        view.page().mainFrame().addToJavaScriptWindowObject("pyObj", obj)
        view.setHtml(html)
        view.resize(640, 480)
        view.show()
        sys.exit(app.exec_())