1

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.

West
  • 2,350
  • 5
  • 31
  • 67

1 Answers1

3

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

S. Nick
  • 12,879
  • 8
  • 25
  • 33
  • Hi I've put the main running code in a function called `task` and edited my question so its complete and easy to understand. Thanks for your response but as I said before that I will be using QT designer to create the interface its hard to follow your example as it does what I'm trying to avoid, i.e creating an interface from scratch. Are you able to edit your code so it loads the ui through `uic.loadUi()` ? In that way I can be able to understand whats going on and how classes work with pyQt. Thanks – West Oct 07 '18 at 02:16
  • @West I added an example that interests you. – S. Nick Oct 07 '18 at 13:35
  • Thanks for that, I have copied and tested your code as is and the interface still freezes with larger ranges like `range(1,10000)`. Any idea why thats the case? – West Oct 07 '18 at 15:35
  • @West I made changes to `range (1,10000)` as you like. The changes affected the `processing ()` function and are marked with `# +++` If my answers were useful to you, do not forget to mark my answer as privileged and useful. – S. Nick Oct 07 '18 at 17:25
  • 1
    Thanks a lot! Sorry came back to this late – West May 04 '20 at 14:49