Search code examples
pythonpyqtpyqt5python-multithreadingqt-designer

How to create threads in Python for use with PyQt


I'm trying to understand how to use threading with Pyqt and am really struggling following any tutorials as most of them are based on an interface created from scratch through classes. I have created my interface using Qt designer and cant figure out how to run the program using threading and update the interface without having it to freeze. My example code is shown below:

import sys
from PyQt5 import uic, QtWidgets

def task():
    for i in range (1,100000):
    html = ("<p style = 'color:blue;'> <b> Completed: %s <b> <p><br>") % i
    window.plainTextEdit.appendHtml(html)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = uic.loadUi('test.ui')
    window.show()
    window.pushButton.clicked.connect(task)
    sys.exit(app.exec_())

Would love to also add a progress bar that can run on its own thread too.


Solution

  • Classes you need to love, the sooner the better! One option for what you want may look like this:

    import sys
    import threading
    from PyQt5 import QtWidgets, QtCore   # ,uic 
    
    
    def thread(my_func):
        """  Runs a function in a separate thread. """
        def wrapper(*args, **kwargs):
            my_thread = threading.Thread(target=my_func, args=args, kwargs=kwargs)
            my_thread.start()
        return wrapper
    
    @thread
    def processing(signal):
        """ Emulates the processing of some data. """
        ind = 1
        for i in range (1,10001):                                     # +++
            html = ("<p style='color:blue;'> Completed: <b>%s </b> </p> <br>") % i
            QtCore.QThread.msleep(5)
            ind = ind if i%100!=0 else ind+1                          # +++
            signal.emit(html, ind)                                    # +++ 
    
    
    
    def mySignalHandler(html, val):          # Called to process a signal
        plainTextEdit.appendHtml(html)
        progressBar.setValue(val)
    
    
    class WindowSignal(QtWidgets.QWidget):
        """ New signals can be defined as class attributes using the pyqtSignal() factory. """
        my_signal = QtCore.pyqtSignal(str, int, name='my_signal')
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
    
        window = WindowSignal()
    
        button = QtWidgets.QPushButton("Emit your signal!", window)
        button.clicked.connect(lambda: processing(window.my_signal))
    
        # since you do not publish test.ui, I replaced it with the line below:
        plainTextEdit = QtWidgets.QPlainTextEdit(window)
        progressBar = QtWidgets.QProgressBar()
        progressBar.setTextVisible(False)
    
    
        layoutHBox = QtWidgets.QHBoxLayout()
        layoutHBox.addWidget(button)
        layoutHBox.addWidget(plainTextEdit)
    
        layoutVBox = QtWidgets.QVBoxLayout()
        window.setLayout(layoutVBox)
        layoutVBox.addLayout(layoutHBox)
        layoutVBox.addWidget(progressBar)
    
        window.my_signal.connect(mySignalHandler, QtCore.Qt.QueuedConnection)
    
        window.show()
    
        sys.exit(app.exec_())
    

    enter image description here

    Example 2:

    test.ui is a file containing the description of the main window automatically generated by Qt Designer.

    <?xml version="1.0" encoding="UTF-8"?>
    <ui version="4.0">
     <class>Form</class>
     <widget class="QWidget" name="Form">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>400</width>
        <height>300</height>
       </rect>
      </property>
      <property name="windowTitle">
       <string>Form</string>
      </property>
      <widget class="QWidget" name="">
       <property name="geometry">
        <rect>
         <x>12</x>
         <y>12</y>
         <width>371</width>
         <height>281</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout">
        <item>
         <layout class="QHBoxLayout" name="horizontalLayout">
          <item>
           <widget class="QPushButton" name="button">
            <property name="text">
             <string>Emit your signal!</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPlainTextEdit" name="plainTextEdit"/>
          </item>
         </layout>
        </item>
        <item>
         <widget class="QProgressBar" name="progressBar">
          <property name="value">
           <number>0</number>
          </property>
          <property name="textVisible">
           <bool>false</bool>
          </property>
          <property name="invertedAppearance">
           <bool>false</bool>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
     </widget>
     <resources/>
     <connections/>
    </ui>
    

    main.py

    import sys
    import threading
    from PyQt5 import QtWidgets, QtCore, uic 
    
    
    def thread(my_func):
        """  Runs a function in a separate thread. """
        def wrapper(*args, **kwargs):
            my_thread = threading.Thread(target=my_func, args=args, kwargs=kwargs)
            my_thread.start()
        return wrapper
    
    @thread
    def processing(signal):
        """ Emulates the processing of some data. """
        for i in range (1,101):   # (1,100000)
            html = ("<p style='color:blue;'> Completed: <b>%s </b> </p> <br>") % i
            QtCore.QThread.msleep(10)
            signal.emit(html, i)     # Send a signal in which we transfer the received data
    
    
    def mySignalHandler(html, val):          # Called to process a signal
        window.plainTextEdit.appendHtml(html)
        window.progressBar.setValue(val)
    
    
    class WindowSignal(QtWidgets.QWidget):
        """ New signals can be defined as class attributes using the pyqtSignal() factory. """
        my_signal = QtCore.pyqtSignal(str, int, name='my_signal')
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
    
        window = WindowSignal()
        uic.loadUi('test.ui', window)    
    
        window.button.clicked.connect(lambda: processing(window.my_signal))
        window.my_signal.connect(mySignalHandler, QtCore.Qt.QueuedConnection)
    
        window.show()
    
        sys.exit(app.exec_())
    

    enter image description here