[pyqt5 learning - signal and slot] instance timer (solve the interface jam problem)

Keywords: Python PyQt5

catalogue

1. What are signals and slots

1) GUI controls (signals) and slots

2) Custom signals and slots

2. Actual combat 1: timer (no custom signal slot and no multithreading)

1) Interface design - use QT designer to design, and then compile pyuic into py file

2) Rewrite UI classes and write logical files

  3) Add large cycle in built-in signal slot function - normal operation

4) Add a big loop in the slot function bound to the button control - GUI fake dead Caton

3. Actual combat 2: timer -- using multithreading to solve the problem of GUI jam

1) Common causes of GUI Caton

2) Solution to Caton - multithreading

(1) Create a thread class and override the run function

(2) Initializing functions in GUI classes__ init__ Instantiate thread class in

(3) Start the thread in the corresponding slot function

(4) Complete code

4. Actual combat 3: Timer - parameter transmission using user-defined signal slot

1) Custom signal (signal with parameter) in line class

2) Transmitting signals in line classes

3) Bind the signal and slot function in the GUI class to receive the parameters of signal transmission

4) Complete code

5. Summary

1. What are signals and slots

Press the switch and the light comes on

In the above description, the action of "pressing the switch" is a signal, and the slot refers to what will happen when the action is completed. It can be understood that only when the signal is triggered can the corresponding slot function (event) occur

The common signal and slot in GUI is the callback function bound to button controls and buttons. When you click the \ release \ double click button, different callback functions, namely slot functions, will be run according to the signal. Common signal and slot forms are as follows:

1) GUI controls (signals) and slots

This kind of signal and slot. The signal is mainly aimed at the controls on the GUI. The slot function can be self-contained in the GUI or user-defined

To create such signals and slots:

  • Creating a control is a button
    self.pushButton = QtWidgets.QPushButton(self.centralwidget)
  • Write slot function
    #Custom slot function
    def slotEvent(self):
         for i in range(1000000):
              print(i)
    
    #GUI built-in slot function - Interface exit event
    def slotEvent(self):
        exit()
    
  • Bind the control related actions (signals) with the corresponding slot function according to the requirements

Control name. Action. Connect (slot function)

    self.pushButton.clicked.connect(self.slotEvent)
    Signal: click button slot function: slotEvent() function

2) Custom signals and slots

  • Import corresponding modules
    from PyQt5.Qt import pyqtSignal
  • Custom signal (with or without parameters)

  Signal name = pyqtsignal (type)      

         intSignal = pyqtSignal(int)

  • Define slot function - same as 1)

#Custom slot function
def slotEvent(self):
      for i in range(1000000):
           print(i)

#GUI built-in slot function - Interface exit event
def slotEvent(self):
      exit()

  • Transmit signal
    Signal name.emit (parameter content)
    self.intSignal.emit(val)

Note: the number of parameter types during signal transmission must be consistent with the number of parameter types during definition.

  • Receive signal (slot function corresponding to signal binding)

Signal name. Connect (slot function)

        self.intSignal.connect(self.slotEvent)

2. Actual combat 1: timer (no custom signal slot and no multithreading)

Development environment: pycharm + windows10 + pyqt5

Function:

1. After pressing the start timing button, the counting board starts counting, and the button text is modified to stop detection

2. After pressing the stop timing button, the counting board stops counting and the timing returns to zero. At the same time, the button text is modified to start timing

1) Interface design - use QT designer to design, and then compile pyuic into py file

 

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

# Form implementation generated from reading ui file 'timerWithoutThread.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(307, 165)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.lcdNumber = QtWidgets.QLCDNumber(self.centralwidget)
        self.lcdNumber.setObjectName("lcdNumber")
        self.verticalLayout.addWidget(self.lcdNumber)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setObjectName("pushButton")
        self.verticalLayout.addWidget(self.pushButton)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 307, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Timer - without process"))
        self.pushButton.setText(_translate("MainWindow", "Start timing"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

2) Rewrite UI classes and write logical files

Composition:

Class custom class name (QtWidgets.QMainWindow,Ui_MainWindow):
    def __init__(self):
         Super (MainUI, self). _init_ () # override class
        self.setupUi(self)
        self.run() # is used to bind signals and slots

  def event 1 (self):
        ...

  def event 2 (self):
        ...

  def run(self):
        Control name. Action. Connect (Event 1)
        Control name. Action. Connect (Event 2)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/10/31 19:31
# @Author  : @linlianqin
# @Site    : 
# @File    : timerWithoutThreadLogic.py
# @Software: PyCharm
# @description: do not use threads and signal slots to implement counters

from PyQt5 import QtWidgets
from pyqt_learn.signal_slot.Counters are implemented without threads and signal slots.timerWithoutThread import Ui_MainWindow
from PyQt5.QtCore import QTimer

'''
The relationship between the signal and the slot here is not user-defined, but simply through pyqt There are mainly two corresponding relationships between signals and slots:
1,Signal: end of counter count per second    Slot function: update the information on the counter board
2,Signal: click the button on the counting board    Slot function: counter starts working
3,Signal: click the button on the counting board    Slot function: the counter stops counting and the count returns to zero
 Note: clicking the button here actually starts a cycle, in which the counter per second is continuously called, and then the counter board information is updated

Steps:
1,Counter class instantiation per second, i.e QTimer
2,Write the execution event at the end of the counter per second, that is, update the number on the counter board
3,Write button events and start counting

result:
1,After pressing the start timing button, the counting board starts counting, and the button text is modified to stop detection
2,After pressing the stop timing button, the counting board stops counting and the timing returns to zero. At the same time, the button text is modified to start timing
'''

global sec

class mainUi(QtWidgets.QMainWindow,Ui_MainWindow):
	def __init__(self):
		super(mainUi, self).__init__()
		self.setupUi(self)
		self.timer = QTimer() # Instantiate counter per second
		global sec
		sec = 0
		self.run()

	# Update the number of counters
	def setTime(self):
		global sec
		sec += 1
		self.lcdNumber.display(sec)

	# Start counting
	def startCount(self):
		# Set the counting interval and start it. Send a timeout signal every 1000 milliseconds (1 second) to cycle. If you need to stop, you can call timer.stop() to stop
		self.timer.start(1000)
		# When you click the button to start timing, the button text is changed to stop timing, and the bound slot function is changed
		self.pushButton.setText("Stop timing")
		self.pushButton.clicked.connect(self.stopTime)

	# Stop timing and set the timing to 0
	def stopTime(self):
		global sec
		sec = 0
		self.timer.stop()
		self.lcdNumber.display(sec)
		# When you click the button to stop timing, the button text is changed to start timing, and the button binding slot function is changed
		self.pushButton.setText("Start timing")
		self.pushButton.clicked.connect(self.startCount)

	# Binding signal and slot
	def run(self):
		# Update the count plate number after the counter count per second is completed
		self.timer.timeout.connect(self.setTime)
		# Click the button to start counting
		self.pushButton.clicked.connect(self.startCount)

if __name__ == '__main__':
	import sys
	app = QtWidgets.QApplication(sys.argv)
	main = mainUi()
	main.show()
	sys.exit(app.exec_())

There are actually two signals, one is to click the button, the other is to start timer timing, and timer is a built-in signal,

  3) Add large cycle in built-in signal slot function - normal operation

	# Update the number of counters
	def setTime(self):
		global sec
		sec += 1
		# Adding a large loop in the event of built-in signal binding will not cause the GUI to get stuck
		for i in range(1000000000):
			pass
		self.lcdNumber.display(sec)

Practice has proved that the timer can operate normally. Adding a large loop in the slot function of the built-in signal will not cause the phenomenon of jamming of the GUI interface. This is because another thread is used inside the timer counter to count, which will not affect the operation of the main thread of the GUI, so it will not jam

4) Add a big loop in the slot function bound to the button control - GUI fake dead Caton

	# Start counting
	def startCount(self):
		# Set the counting interval and start it. Send a timeout signal every 1000 milliseconds (1 second) to cycle. If you need to stop, you can call timer.stop() to stop
		self.timer.start(1000)
		# Add a large time-consuming loop in the slot function corresponding to the GUI control
		for i in range(1000000):
			print(i)
			pass
		# When you click the button to start timing, the button text is changed to stop timing, and the bound slot function is changed
		self.pushButton.setText("Stop timing")
		self.pushButton.clicked.connect(self.stopTime)

A large cycle is added to the slot function corresponding to the button. Before the large cycle runs, the GUI will get stuck. This is because the button belongs to the GUI control, and the slot function corresponding to the control is carried out in the main thread of the GUI. Therefore, the main thread will be temporarily suspended, that is, it will cause the GUI to get stuck. After the cycle ends, the GUI will return to normal

3. Actual combat 2: timer -- using multithreading to solve the problem of GUI jam

1) Common causes of GUI Caton

① Contains complex operations

② Contains time-consuming cycles

③time.sleep()

2) Solution to Caton - multithreading

       From the cause of Caton, we can know that Caton mainly encounters time-consuming operations during the running of the GUI main thread, which leads to the pseudo dead Caton state of the GUI during cyclic calculation.

       Therefore, we only need to select the time-consuming operations, and then open a new thread to perform these time-consuming operations, and the main thread is used to trigger the start of the new thread, so as to solve the GUI jam.

(1) Create a thread class and override the run function

The run function runs automatically when the thread calls the start() function

from PyQt5.QtCore import QTimer,QThread

# Open a new thread to operate the loop
class newThread(QThread):
	def __init__(self):
		super(newThread, self).__init__()

	# Large cycle
	def run(self):
		for i in range(1000000):
			print(i)
			pass

(2) Initializing functions in GUI classes__ init__ Instantiate thread class in

self.loopThread = newThread()

(3) Start the thread in the corresponding slot function

		# Start a new thread to execute a large loop
		self.loopThread.start()

(4) Complete code

       After another thread is opened, there will be no jamming. During the large cycle, the numbers on the counting board can still be updated in real time. The GUI code here has not changed

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/10/31 19:31
# @Author  : @linlianqin
# @Site    : 
# @File    : timerWithoutThreadLogic.py
# @Software: PyCharm
# @description:

from PyQt5 import QtWidgets
from pyqt_learn.signal_slot.Counters are implemented without threads and signal slots.timerWithoutThread import Ui_MainWindow
from PyQt5.QtCore import QTimer,QThread
from PyQt5.Qt import pyqtSignal


'''
The relationship between the signal and the slot here is not user-defined, but simply through pyqt There are mainly two corresponding relationships between signals and slots:
1,Signal: end of counter count per second    Slot function: update the information on the counter board
2,Signal: click the button on the counting board    Slot function: Send a signal to inform the counter to start counting
3,Signal: click the button on the counting board    Slot function: the counter stops counting and the count returns to zero
4,Signals: Custom signals           Slot function: counter starts counting
 Note: clicking the button here actually starts a cycle, in which the counter per second is continuously called, and then the counter board information is updated

Steps:
1,Counter class instantiation per second, i.e QTimer
2,Write the execution event at the end of the counter per second, that is, update the number on the counter board
3,Write button events and start counting

Result: a large cycle is added in the slot function corresponding to the button before the large cycle runs GUI There will be a jam because the button belongs to GUI Control. The slot function corresponding to the control is in GUI In the main thread, so
 Make the main thread temporarily suspended, that is, it will cause GUI Caton, when the cycle is over, GUI Return to normal

Method: select the loop part, and then open another thread to run. When you click the button, start the thread and run the loop in the background without affecting the GUI The running of the main thread can be realized GUI No, Caton
'''

global sec

# Open a new thread to operate the loop
class newThread(QThread):
	def __init__(self):
		super(newThread, self).__init__()

	# Large cycle
	def run(self):
		for i in range(1000000):
			print(i)
			pass

class mainUi(QtWidgets.QMainWindow,Ui_MainWindow):

	def __init__(self):
		super(mainUi, self).__init__()
		self.setupUi(self)
		self.timer = QTimer() # Instantiate counter per second
		self.loopThread = newThread()
		global sec
		sec = 0
		self.run()

	# Update the number of counters
	def setTime(self):
		global sec
		sec += 1
		self.lcdNumber.display(sec)

	# Start counting
	def startCount(self):
		# Set the counting interval and start it. Send a timeout signal every 1000 milliseconds (1 second) to cycle. If you need to stop, you can call timer.stop() to stop
		self.timer.start(1000)
		# When you click the button to start timing, the button text is changed to stop timing, and the bound slot function is changed
		self.pushButton.setText("Stop timing")
		self.pushButton.clicked.connect(self.stopTime)
		# Start a new thread to execute a large loop
		self.loopThread.start()

	# Stop timing and set the timing to 0
	def stopTime(self):
		global sec
		sec = 0
		self.timer.stop()
		self.lcdNumber.display(sec)
		# When you click the button to stop timing, the button text is changed to start timing, and the button binding slot function is changed
		self.pushButton.setText("Start timing")
		self.pushButton.clicked.connect(self.startCount)

	# Binding signal and slot
	def run(self):
		# Update the count plate number after the counter count per second is completed
		self.timer.timeout.connect(self.setTime)
		# Click the button to emit a custom signal
		# self.pushButton.clicked.connect(self.startCount)
		# self.pushButton.clicked.connect(lambda:self.signal.emit()) # Here, the statement is functionalized by lambda
		self.pushButton.clicked.connect(self.startCount)

if __name__ == '__main__':
	import sys
	app = QtWidgets.QApplication(sys.argv)
	main = mainUi()
	main.show()
	sys.exit(app.exec_())

4. Actual combat 3: Timer - parameter transmission using user-defined signal slot

A label control is added to the GUI to update and display the number of cycles of a large cycle in real time

1) Custom signal (signal with parameter) in line class

  The type of parameter is determined according to the type of parameter to be passed

	# Customize a signal with integer parameters for use when clicking a button
	intSignal = pyqtSignal(int)

2) Transmitting signals in line classes

This line of code generally appears at the position where the parameter content to be transmitted is obtained. Each time a new parameter content is obtained, the signal is transmitted. Therefore, the signal is transmitted once every cycle. The signal has parameters, and the parameters are accepted by the slot function bound by the signal

self.intSignal.emit(val)

3) Bind the signal and slot function in the GUI class to receive the parameters of signal transmission

Note: here, the number and type of parameters received by the slot function are consistent with the number and type of parameters transmitted by the signal.

		# Connect the custom signal to the slot function for modifying tag events, where the signal transmission will return parameters, and then the slot function will automatically receive the returned parameters
		self.loopThread.intSignal.connect(self.setLabel)
	def setLabel(self,val):
		self.label.setText(str(val))

4) Complete code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/10/31 19:31
# @Author  : @linlianqin
# @Site    : 
# @File    : timerWithoutThreadLogic.py
# @Software: PyCharm
# @description:

from PyQt5 import QtWidgets
from pyqt_learn.signal_slot.Transmitting parameters by signal slot.timerWithoutThread import Ui_MainWindow
from PyQt5.QtCore import QTimer,QThread
from PyQt5.Qt import pyqtSignal

'''
The relationship between the signal and the slot here is not user-defined, but simply through pyqt There are mainly two corresponding relationships between signals and slots:
1,Signal: counter end signal slot function: update the number on the counter board
2,Signals: click the button       Slot function: start counting and trigger a new thread to start running
3,Signal: new thread running
 Note: clicking the button here actually starts a cycle, in which the counter per second is continuously called, and then the counter board information is updated

Expectation: click the button to start counting, and GUI Change the label to the value of the loop

Steps:
1,Create inheritance QThread Class of
2,Rewrite class def __init__()
3,In the class name and__init__Custom signal between
4,In class run Transmitting signal in method emit()

5,stay GUI Class__init__Instantiate thread in
6,stay GUI Bind custom signals in threads to slot functions

be careful:
1,The parameters when transmitting signals need to match the parameter types when customizing signals;
2,stay GUI The number and type of parameters of the slot function bound by the user-defined signal in the class should match the number and type of parameters defined by the user-defined signal
'''

global sec

# Open a new thread to operate the loop
class newThread(QThread):
	# Customize a signal with integer parameters for use when clicking a button
	intSignal = pyqtSignal(int)

	def __init__(self):
		super(newThread, self).__init__()

	# Large loop, which is automatically started when the run function calls the thread start function
	def run(self):
		i = 0
		while True:
			print(i)
			i += 1
			self.emit_(i)

	# Transmit signal
	def emit_(self,val):
		self.intSignal.emit(val)

class mainUi(QtWidgets.QMainWindow,Ui_MainWindow):

	def __init__(self):
		super(mainUi, self).__init__()
		self.setupUi(self)
		self.timer = QTimer() # Instantiate counter per second
		self.loopThread = newThread() # Instantiate thread
		global sec
		sec = 0
		self.run()

	# Update the number of counters
	def setTime(self):
		global sec
		sec += 1
		self.lcdNumber.display(sec)

	# Start counting
	def startCount(self):
		# Set the counting interval and start it. Send a timeout signal every 1000 milliseconds (1 second) to cycle. If you need to stop, you can call timer.stop() to stop
		self.timer.start(1000)
		# When you click the button to start timing, the button text is changed to stop timing, and the bound slot function is changed
		self.pushButton.setText("Stop timing")
		self.pushButton.clicked.connect(self.stopTime)
		# Start a new thread to execute a large loop
		self.loopThread.start()


	# Stop timing and set the timing to 0
	def stopTime(self):
		global sec
		sec = 0
		self.timer.stop()
		self.lcdNumber.display(sec)
		# When you click the button to stop timing, the button text is changed to start timing, and the button binding slot function is changed
		self.pushButton.setText("Start timing")
		self.pushButton.clicked.connect(self.startCount)

	def setLabel(self,val):
		self.label.setText(str(val))


	# Binding signal and slot
	def run(self):
		# Update the count plate number after the counter count per second is completed
		self.timer.timeout.connect(self.setTime)
		self.pushButton.clicked.connect(self.startCount)
		# Connect the custom signal to the slot function for modifying tag events, where the signal transmission will return parameters, and then the slot function will automatically receive the returned parameters
		self.loopThread.intSignal.connect(self.setLabel)

if __name__ == '__main__':
	import sys
	app = QtWidgets.QApplication(sys.argv)
	main = mainUi()
	main.show()
	sys.exit(app.exec_())

Results: the counting plate counted in real time; Number of label real-time scrolling cycles

5. Summary

  • It involves time-consuming computing and separate thread computing to avoid GUI jamming
  • If parameter transmission is required, note that the user-defined signal parameter type is consistent with the transmitted signal parameter type
  • The parameter types of transmitted signal and slot function should be consistent

 

Posted by vikette on Sun, 31 Oct 2021 20:27:12 -0700