SoFunction
Updated on 2024-11-12

python GUI library graphical interface development PyQt5 UI main thread and time-consuming thread separation detailed method examples

When doing interface development, whether it is mobile Android, or PyQt5 we are talking about here, there is often a guideline for interface development, that is, the main UI thread and time-consuming sub-threads must be separated, the main thread is responsible for refreshing the interface, and time-consuming operations, such as network interactions, disk IO, etc. should be placed in sub-threads to execute, and each of them should have its own role to ensure that the system operates normally and improve the overall user experience.

software and hardware environment

windows 10 64bit

PyQt5

Anaconda3 with python 3.6.5

sample code (computing)

First look at the project directory structure

This is the project entry file, which is responsible for creating the app

# -*- coding: utf-8 -*-

import sys

from  import QApplication

from  import MainWindow

if __name__ == '__main__':

  app = QApplication()
  main_window = MainWindow()
  main_window.show()
  (app.exec_())

ui_mainwindow.py, responsible for the interface drawing, this file through the designer graphical tools for drawing and then use the pyuic tool to generate the corresponding python code

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '.\'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
  def setupUi(self, MainWindow):
    ("MainWindow")
    (800, 600)
    ((800, 600))
    ((800, 600))
     = (MainWindow)
    ("centralwidget")
    self.button_ok = ()
    self.button_ok.setGeometry((260, 220, 230, 140))
    self.button_ok.setMinimumSize((230, 140))
    self.button_ok.setMaximumSize((230, 140))
    font = ()
    (50)
    self.button_ok.setFont(font)
    self.button_ok.setFocusPolicy()
    self.button_ok.setObjectName("button_ok")
    ()
     = (MainWindow)
    ("statusbar")
    ()
     = (MainWindow)
    ((0, 0, 800, 23))
    ("menubar")
     = ()
    ("menuFile")
     = ()
    ("menuHelp")
    ()
     = (MainWindow)
    ("actionExit")
     = (MainWindow)
    ("actionCopy")
     = (MainWindow)
    ("actionPaste")
     = (MainWindow)
    ("actionCut")
     = (MainWindow)
    ("actionHelp")
     = (MainWindow)
    ("actionAbout")
    self.action_query = (MainWindow)
    self.action_query.setObjectName("action_query")
    self.action_backupDB = (MainWindow)
    self.action_backupDB.setObjectName("action_backupDB")
    self.action_reset_mac = (MainWindow)
    self.action_reset_mac.setObjectName("action_reset_mac")
    ()
    ()
    ()
    ()
    ()
    ()
    ()
    (())
    (())

    (MainWindow)
    (MainWindow)

  def retranslateUi(self, MainWindow):
    _translate = 
    (_translate("MainWindow", "Separate the main UI thread from the worker thread."))
    self.button_ok.setText(_translate("MainWindow", "OK."))
    (_translate("MainWindow", "File"))
    (_translate("MainWindow", "Help"))
    (_translate("MainWindow", "Exit."))
    (_translate("MainWindow", "Instructions for the use of the software"))
    (_translate("MainWindow", "About"))

It is mainly responsible for the event handling of the controls on the interface

import time

from  import QMainWindow

from gui.ui_mainwindow import *


class MainWindow(QMainWindow, Ui_MainWindow):

  def __init__(self, parent=None):
    super(MainWindow, self).__init__(parent)
    (self)

    # Bind the click event
    self.button_ok.(self.button_start)

  def button_start(self):

    self.button_ok.setChecked(True)
    self.button_ok.setDisabled(True)

    (20)

Here we use (20) to simulate a time-consuming task. After executing python for a while, the interface will be unresponsive and fake-dead, and after 20 seconds, the interface will be back to normal, which is a very poor user experience.

In fact, it is very simple to solve this problem. We move the (20) in the main UI thread to a sub-thread and we're done. PyQt5 provides a thread class QThread, we inherit it and override its run method, create a new file

# -*- coding: utf-8 -*-
import time

from  import QThread, pyqtSignal

class WorkThread(QThread):

  # Use signals to communicate with the main UI thread, the parameter is the data type of the parameter attached when sending the signal, it can be str, int, list, etc.
  finishSignal = pyqtSignal(str)

  # Examples with parameters
  def __init__(self, ip, port, parent=None):
    super(WorkThread, self).__init__(parent)

     = ip
     = port

  def run(self):
    '''
    Rewrite
    '''

    print('=============sleep======ip: {}, port: {}'.format(, ))
    (20)

    ('This is a test.')
    return

Notice here that we use pyqtSignal, which we use to communicate with the main UI thread, typically for refreshing interface elements, and at the end of the subthread, we send this signal.

Correspondingly, the following changes need to be made

from  import WorkThread

# Other parts omitted
def button_start(self):

  print('button_start clicked.')

  # Setup button not available
  self.button_ok.setChecked(True)
  self.button_ok.setDisabled(True)

   = WorkThread(ip='192.168.1.1', port=4000)

  # Connect the signal finishSignal in thread th to the slot function button_finish in the main UI thread
  (self.button_finish)

  # Starting threads
  ()

def button_finish(self, msg):

  print('msg: {}'.format(msg))

  # Setup button available
  self.button_ok.setChecked(False)
  self.button_ok.setDisabled(False)

After one operation, execute python again, and the interface will no longer show the No Resonding prompt, so you can manipulate other controls on the interface as you wish while the subthread is executing.

For more on this know please check out the related links below