一.簡介
在使用pyqt5編寫gui時遇到兩個問題,會導致介面崩潰,今天就圍繞這兩個問題來簡單說明和改進。
1.在主執行緒中使用while無限迴圈會導致介面崩潰
2.在子執行緒中操作主執行緒gui會導致介面崩潰
二.步驟說明
1.在主執行緒中使用while無限迴圈會導致介面崩潰
1)錯誤程式碼
import sys from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget class FileChooserApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): button = QPushButton("按鈕") self.reviewEdit = QTextEdit() self.reviewEdit.move(100, 100) button.clicked.connect(self.send) hbox1 = QHBoxLayout() # 建立一個水平佈局 hbox1.addWidget(button) # 新增按鈕到水平佈局中 hbox1.addStretch(1) # 設定水平比例間距 hbox1.addWidget(self.reviewEdit) # 新增按鈕到水平佈局中 self.setLayout(hbox1) # 新增到佈局器 self.setWindowTitle('檔案選擇器') self.setGeometry(300, 300, 500, 500) def send(self): """ 事件 :return: """ while True: """ 邏輯程式碼 """ self.reviewEdit.setText("測試") if __name__ == '__main__': app = QApplication(sys.argv) ex = FileChooserApp() ex.show() sys.exit(app.exec_())
2)崩潰原因
我們先來說下while崩潰的問題,這邊我設定的迴圈是一個無限迴圈,不會給 GUI 事件迴圈任何執行的機會。在 PyQt 或其他 GUI 框架中,GUI 的事件迴圈(例如按鈕點選、視窗移動等)必須在單獨的執行緒中執行,以保持 GUI 的響應性
3)改進方法
將迴圈體在一個子執行緒中執行,就可以避免這個問題,程式碼如下。
import sys import threading from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget class FileChooserApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): button = QPushButton("按鈕") self.reviewEdit = QTextEdit() self.reviewEdit.move(100, 100) button.clicked.connect(self.send) hbox1 = QHBoxLayout() # 建立一個水平佈局 hbox1.addWidget(button) # 新增按鈕到水平佈局中 hbox1.addStretch(1) # 設定水平比例間距 hbox1.addWidget(self.reviewEdit) # 新增按鈕到水平佈局中 self.setLayout(hbox1) # 新增到佈局器 self.setWindowTitle('檔案選擇器') self.setGeometry(300, 300, 500, 500) def send(self): """ 事件 :return: """ def send_a(): while True: """ 邏輯程式碼 """ print("123") send_thread = threading.Thread(target=send_a) # 啟動執行緒 send_thread.start() if __name__ == '__main__': app = QApplication(sys.argv) ex = FileChooserApp() ex.show() sys.exit(app.exec_())
2.在子執行緒中操作主執行緒gui會導致介面崩潰
1)錯誤程式碼
import sys import threading import time from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget class FileChooserApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): button = QPushButton("按鈕") self.reviewEdit = QTextEdit() self.reviewEdit.move(100, 100) button.clicked.connect(self.send) hbox1 = QHBoxLayout() # 建立一個水平佈局 hbox1.addWidget(button) # 新增按鈕到水平佈局中 hbox1.addStretch(1) # 設定水平比例間距 hbox1.addWidget(self.reviewEdit) # 新增按鈕到水平佈局中 self.setLayout(hbox1) # 新增到佈局器 self.setWindowTitle('檔案選擇器') self.setGeometry(300, 300, 500, 500) def send(self): """ 事件 :return: """ def send_a(): while True: """ 邏輯程式碼 """ self.reviewEdit.setText("設定文案") send_thread = threading.Thread(target=send_a) # 啟動執行緒 send_thread.start() if __name__ == '__main__': app = QApplication(sys.argv) ex = FileChooserApp() ex.show() sys.exit(app.exec_())
2)崩潰原因
上述中試圖在子執行緒send_a方法中給文字編輯器設定文案。這是不允許的,因為 PyQt 和大多數 GUI 框架一樣,要求所有的 GUI 更新必須在主執行緒(也稱為 GUI 執行緒)中執行。
3)改進方法
既然在子執行緒中無法操作主執行緒gui更新,那麼只能在主執行緒中去執行,這就需要訊號與槽的配合了。我們先來自定義一個訊號
class YourThread(QObject): show_warning_signal = pyqtSignal() def run(self): self.show_warning_signal.emit()
在初始化的時候去例項化YourThread類,連線訊號與槽
class FileChooserApp(QMainWindow): def __init__(self): super().__init__() self.initUI() self.your = YourThread() self.your.show_warning_signal.connect(self.settext)
接著在子執行緒中直接去觸發訊號即可
def send(self): def send_a(): while True: """ 迴圈體 """ self.your.run() time.sleep(2) send_thread = threading.Thread(target=send_a) # 啟動執行緒 send_thread.start()
執行每次迴圈需要等待2s,避免出現無限迴圈導致應用程式凍結、響應緩慢或其他執行緒相關的問題
三.例項
import sys import threading import time from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtWidgets import QPushButton, QTextEdit, QApplication, QHBoxLayout, QWidget class YourThread(QObject): show_warning_signal = pyqtSignal() def run(self): self.show_warning_signal.emit() class FileChooserApp(QWidget): def __init__(self): super().__init__() self.initUI() self.your = YourThread() self.your.show_warning_signal.connect(self.settext) def initUI(self): button = QPushButton("按鈕") self.reviewEdit = QTextEdit() self.reviewEdit.move(100, 100) button.clicked.connect(self.send) hbox1 = QHBoxLayout() # 建立一個水平佈局 hbox1.addWidget(button) # 新增按鈕到水平佈局中 hbox1.addStretch(1) # 設定水平比例間距 hbox1.addWidget(self.reviewEdit) # 新增按鈕到水平佈局中 self.setLayout(hbox1) # 新增到佈局器 self.setWindowTitle('檔案選擇器') self.setGeometry(300, 300, 500, 500) def send(self): """ 事件 :return: """ def send_a(): while True: """ 邏輯程式碼 """ self.your.run() time.sleep(2) send_thread = threading.Thread(target=send_a) # 啟動執行緒 send_thread.start() def settext(self): self.reviewEdit.setText("123") if __name__ == '__main__': app = QApplication(sys.argv) ex = FileChooserApp() ex.show() sys.exit(app.exec_())