PySide6 多執行緒訊號傳遞
本人是一位PySide的初學者,最近剛有點整明白Pyside6的多執行緒間的訊號傳遞,於是想記錄一下。
之前在網上找資料時,感覺很多教程的程式碼都比較複雜,於是想到寫一個簡單版本的,也希望能給其他人一些參考。
由於本人也是初學者,因此可能有很多錯誤,也請大家不吝賜教。
演示
首先寫一個主視窗:
from PySide6.QtCore import Signal,Slot,Qt,QThread from PySide6.QtWidgets import QWidget,QVBoxLayout,QPushButton,QLabel,QApplication import sys,time class mainWindow(QWidget): def __init__(self) -> None: super().__init__() self.label = QLabel("Hello!") self.label.setAlignment(Qt.AlignCenter) self.but = QPushButton("Click!") self.layout = QVBoxLayout() self.layout.addWidget(self.label) self.layout.addWidget(self.but) self.setLayout(self.layout) self.setWindowTitle('Signal Example') self.resize(300,300) self.show() if __name__ == '__main__': app = QApplication([]) widgets = mainWindow() sys.exit(app.exec())
這一步應該不用多講,如果看不懂建議從PySide基礎開始學起。
寫一個執行緒
該執行緒主要就是倒數。我們要讓主程式的視窗上現實剩餘時間,就是Hello!的那個位置。
class Th(QThread): timer = Signal(int) finish = Signal(bool) def __init__(self) -> None: super().__init__() def run(self): # ptvsd.debug_this_thread() print('Start Timer') self.finish.emit(False) for x in range(5): self.timer.emit(5-x) time.sleep(1) self.finish.emit(True)
在這裡,我用Signal初始化兩個訊號,用於傳遞剩餘時間和是否完成倒計時。
當倒計時開始時,設定未完成,將False
給finish訊號:self.finish.emit(False)
隨後每秒,都會把剩餘時間給timer訊號:self.timer.emit(5-x)
當倒計結束時,將True
給finish訊號:self.finish.emit(True)
為主視窗新增槽並繫結
首先,我們點選Click!按鈕應該開始倒計時,即執行我們上一步寫的執行緒。
@Slot() def fun(self): self.th = Th() self.th.timer.connect(self.flushlabel) self.th.finish.connect(self.isFinish) self.th.start()
初始化執行緒,注意這裡要用類的成員變數,不要用區域性變數:self.th = Th()
之後是開始這個執行緒:self.th.start()
訊號
self.th.timer.connect(self.flushlabel)
self.th.finish.connect(self.isFinish)
這兩個是完成訊號與槽的繫結。
timer和finish都是上一步我們在倒計時的類中設定的訊號。
我們希望,在Th類(倒計時的類)中,每次訊號的emit,都會執行一次繫結的槽(self.flushlabel與self.isFinish)
槽函式
上一步繫結的槽,在這裡我們來實現它。實現的程式碼是在主視窗類中的。
class mainWindow(QWidget):
def __init__(self) -> None:
...
...
...
@Slot()
def fun(self):
...
...
...
@Slot(int)
def flushlabel(self,nu):
self.label.setText(str(nu))
@Slot(bool)
def isFinish(self,bo):
if bo is True:
self.but.setEnabled(True)
else:
self.but.setEnabled(False)
@Slot(int)
和@Slot(bool)
為標識槽函式的引數型別,不寫似乎也沒有問題。
def flushlabel(self,nu):
self.label.setText(str(nu))
這裡。該函式用來獲得timer每次emit的倒計時,將這個數字現實到主視窗的label中。
def isFinish(self,bo):
if bo is True:
self.but.setEnabled(True)
else:
self.but.setEnabled(False)
這個函式來獲得每次finish每次emit的狀態,判斷倒計時是否完成,用來控制主視窗的按鈕是否可以點選。
至此,我們的槽函式與訊號的繫結完成了。這樣每次在子執行緒(倒計時)的訊號被emit時,我們的主執行緒(主視窗)都能拿到其emit的內容,並且使用其內容對主視窗進行相應更改。
完整程式碼
import sys,time
from PySide6.QtCore import Signal,Slot,Qt,QThread
from PySide6.QtWidgets import QWidget,QVBoxLayout,QPushButton,QLabel,QApplication
class mainWindow(QWidget):
def __init__(self) -> None:
super().__init__()
self.label = QLabel("Hello!")
self.label.setAlignment(Qt.AlignCenter)
self.but = QPushButton("Click!")
self.but.clicked.connect(self.fun)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label)
self.layout.addWidget(self.but)
self.setLayout(self.layout)
self.setWindowTitle('Signal Example')
self.resize(300,300)
self.show()
@Slot()
def fun(self):
self.th = Th()
self.th.timer.connect(self.flushlabel)
self.th.finish.connect(self.isFinish)
self.th.start()
@Slot(int)
def flushlabel(self,nu):
self.label.setText(str(nu))
@Slot(bool)
def isFinish(self,bo):
if bo is True:
self.but.setEnabled(True)
else:
self.but.setEnabled(False)
class Th(QThread):
timer = Signal(int)
finish = Signal(bool)
def __init__(self) -> None:
super().__init__()
def run(self):
print('Start Timer')
self.finish.emit(False)
for x in range(5):
self.timer.emit(5-x)
time.sleep(1)
self.finish.emit(True)
if __name__ == '__main__':
app = QApplication([])
widgets = mainWindow()
sys.exit(app.exec())