問題: PyQt5主界面,如果某些操作比較耗時,比如點擊按鈕執行某個腳本、點擊按鈕從網絡上讀取數據等,則點擊按鈕后,很可能造成整個主窗口卡死,無法執行窗口最大化、最小化、文本輸入、按鈕點擊等其他操作。
例子如下:
程序執行后,有個按鈕和一個QLabel顯示框,點擊按鈕后,會計算1+2+...+50000000,整個計算需要十幾秒,計算過程中,主界面卡死,無法執行窗口最大化等操作。
# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QHBoxLayout, QPushButton)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class MyGui(QWidget):
"""基礎Gui頁面"""
def __init__(self):
super().__init__()
self.init_ui() # 主窗口
def init_ui(self):
# self.setGeometry(300, 300, 600, 200) # 設置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 設置主窗口的標題
self.setWindowIcon(QIcon('pics/icon1.gif')) # 設置主窗口的圖標(左上角)
# 通用樣式。頁面上創建的一些部件,會默認使用設置的通用樣式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 300px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函數
self.show() # 顯示窗口
def main_layout(self):
btn = QPushButton(' 點擊計算1+2+...+50000000 ')
show_label = QLabel(' ')
show_label.setStyleSheet('color: black; background: skyblue; border-radius: 13px;')
hbox = QHBoxLayout()
hbox.addWidget(btn)
hbox.addWidget(show_label)
hbox.addStretch()
self.setLayout(hbox)
btn.clicked.connect(lambda: self.update_text(show_label))
def update_text(self, w):
txt = str(self.cal_sum(50000000))
w.setText(' ' + txt + ' ')
def cal_sum(self, n=100):
sum, k = 0, 0
while k <= n:
sum += k
k += 1
return sum
if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())
幾個截圖如下::
截圖1是程序執行完的情況,主窗口顯示了一個按鈕個一個QLabel; 截圖2是點擊按鈕執行累加操作的情況,此時主頁面卡死,
窗口無法最大化、最小化等; 截圖3是加法操作執行完的情況,此時界面恢復正常。
推測原因: 在PyQt中,GUI界面本身就是一個主線程,當點擊按鈕執行累加操作時,因為這個累加操作直接跑在這個主線程上,GUI需要等待累加操作完成后才會響應,在等待這段時間,整個GUI就處於卡死的狀態。如果這個操作是一個死循環(比如按秒更新時間),在windows下,系統會認為這個程序運行出錯了,會自動顯示未響應,進而關閉程序。
這里可以考慮另開一個線程來執行這個累加操作。(PyQt5的QThread)
# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QHBoxLayout, QPushButton)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class MyGui(QWidget):
"""基礎Gui頁面"""
def __init__(self):
super().__init__()
self.init_ui() # 主窗口
def init_ui(self):
# self.setGeometry(300, 300, 600, 200) # 設置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 設置主窗口的標題
self.setWindowIcon(QIcon('pics/icon1.gif')) # 設置主窗口的圖標(左上角)
# 通用樣式。頁面上創建的一些部件,會默認使用設置的通用樣式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 300px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函數
self.show() # 顯示窗口
def main_layout(self):
self.btn = QPushButton(' 點擊計算1+2+...+50000000 ')
self.show_label = QLabel(' ')
self.show_label.setStyleSheet('color: black; background: skyblue; border-radius: 13px;')
hbox = QHBoxLayout()
hbox.addWidget(self.btn)
hbox.addWidget(self.show_label)
hbox.addStretch()
self.setLayout(hbox)
self.btn.clicked.connect(self.update) # 按鈕點擊后創建一個新的線程
def update(self):
self.btn.setText(' 計算中... ') # 主頁面按鈕點擊后更新按鈕文本
self.btn.setEnabled(False) # 將按鈕設置為不可點擊
self.cal = CalSumTheard() # 創建一個線程
self.cal._sum.connect(self.update_sum) # 線程發過來的信號掛接到槽函數update_sum
self.cal.start() # 線程啟動
def update_sum(self, r):
self.show_label.setText(' ' + r + ' ') # 信號發過來時,更新QLabel內容
self.btn.setText(' 點擊計算1+2+...+50000000 ') # 更新按鈕
self.btn.setEnabled(True) # 讓按鈕恢復可點擊狀態
class CalSumTheard(QThread):
"""該線程用於計算耗時的累加操作"""
_sum = pyqtSignal(str) # 信號類型 str
def __init__(self):
super().__init__()
def run(self):
s, k = 0, 0
while k <= 50000000:
s += k
k += 1
self._sum.emit(str(s)) # 計算結果完成后,發送結果
if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())
效果如下:
截圖1就是程序執行完成后的效果;截圖2就是按鈕點擊后的效果(新加了個點擊按鈕后更新按鈕文字和將按鈕設置為不可點擊),
此時主頁面可以正常拖動、最大、最小化;截圖三累加操作執行完成后的效果(鼠標放在按鈕會變色,此時鼠標是放在按鈕上的)
另寫了一個QThread,按秒更新時間
# coding=utf-8
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QLabel, QFrame,
QSplitter, QHBoxLayout, QVBoxLayout)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal
import time
class MyGui(QWidget):
"""基礎Gui頁面"""
def __init__(self):
super().__init__()
self.update() # 因為沒有按鈕觸發新線程,因此需要在初始化時就先執行線程函數
self.init_ui() # 主窗口
def init_ui(self):
self.setGeometry(300, 300, 800, 400) # 設置主窗口的位置和大小
self.setWindowTitle('Simple Little tools') # 設置主窗口的標題
self.setWindowIcon(QIcon('pics/icon1.gif')) # 設置主窗口的圖標(左上角)
# 通用樣式。頁面上創建的一些部件,會默認使用設置的通用樣式
self.setStyleSheet(
'QPushButton{font-weight: bold; background: skyblue; border-radius: 14px;'
'width: 64px; height: 28px; font-size: 20px; text-align: center;}'
'QPushButton:hover{background: rgb(50, 150, 255);}'
'QLabel{font-weight: bold; font-size: 20px; color: orange}'
)
self.main_layout() # 布局函數
self.show() # 顯示窗口
def main_layout(self):
hbox = QHBoxLayout() # 外層大盒
top_left = QFrame()
top_left.setFrameShape(QFrame.StyledPanel)
splitter_top = QSplitter(Qt.Horizontal) # 橫向QSpli
splitter_top.addWidget(top_left)
# 當前時間顯示
current_time_text_label = QLabel('當前時間:')
self.current_time_show_label = QLabel()
self.current_time_show_label.setStyleSheet('color: black; background: skyblue;'
'border-radius: 13px; font-size: 26px')
current_time_hbox = QHBoxLayout()
current_time_hbox.addWidget(current_time_text_label)
current_time_hbox.addWidget(self.current_time_show_label)
current_time_hbox.addStretch()
current_time_vbox = QVBoxLayout()
current_time_vbox.addLayout(current_time_hbox)
current_time_vbox.addStretch()
top_left.setLayout(current_time_vbox)
hbox.addWidget(splitter_top)
self.setLayout(hbox)
def update(self):
self.current = CurrentTimeThread() # 創建一個線程
self.current._update_time.connect(self.update_time) # 連接到槽函數update_time上
self.current.start()
def update_time(self, curr_time):
self.current_time_show_label.setText(' ' + curr_time + ' ') # 更信QLabel的時間
class CurrentTimeThread(QThread):
_update_time = pyqtSignal(str) # 信號類型 str
def __init__(self):
super().__init__()
def run(self):
while 1:
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
self._update_time.emit(current_time)
time.sleep(1) # 每秒發送一個信號
if __name__ == '__main__':
app = QApplication(sys.argv)
qt = MyGui()
sys.exit(app.exec_())
效果圖:
截圖中看不到效果,實際效果是每秒都會更新時間,這種循環更新的還可以使用QTimer來處理。
總結:
1. 比較耗時的任務最好使用新線程去處理(QThread),主線程僅僅用於GUI顯示
2. 循環任務可以使用新線程去處理,也可以使用QTimer去處理(這里沒有演示QTimer)
3. 新線程要創建了才能正常使用。可以按鈕觸發創建,可以在初始化頁面的時候就創建