與昨天一樣,不過我們今天要談一個 PyQt 中非常重要的 QThread 概念 !
今天要談一個 PyQt 中非常重要的 QThread 概念!
我們要修改昨天的 QProgressBar 功能,將它潛在問題修改並解決他。
https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day20_qthread
我們今天要來先討論一下在 PyQt 中非常重要的東西!
這個就叫做 「QThread」 !
為什麼「QThread」很重要,其實在昨天的 QTimer 中我們就已經有說過,
我們的程式一定不會是一直線運行的,程式執行的過程中,
勢必要有些「背景處理」的事情!
例如:時間就應該要背景更新,而不是我們主程式隨時切換去給時間+1
這樣光用想的就知道,萬一上一行程式執行慢了一點,時間就會開始有越來越大的誤差了吧!
因為 ProgressBar 正好就符合這樣的需求,
我們可以試著想想,如果一個 ProgressBar 正在跑,我們就不能同時做其他事情,
這樣子的程式,不能說不好 (畢竟還是能動,只是要等他結束才能做其他事XD)
但不覺得很不符合使用者需求嗎XD
我們把運行到一半的程式,強制用右上角的「X」關閉看看,
我們發現居然程式「不但關不掉」,甚至還「沒有回應」了?!
這就是沒有做 QThread 搞得鬼,因為在 Windows 判斷一個視窗「沒有回應」的判斷條件中,
當我們對視窗「進行一個行為(例如:按按鈕、關閉視窗)」,卻沒有得到回應,
沒有回應的原因就是因為他被「卡在單一任務的過程中,無法給予回應」
windows 就會判定這個程式是「沒有回應
」的
(其實不只 windows, ubuntu, mac 在這個邏輯底下都是)
所以,我們必須把這個任務放入 QThread 執行,使得我們的主線任務可以被空出來,
能應付並接收新的其他任務。
修改昨天的 controller.py
)主要可以分為兩個任務:
注意「QThread, pyqtSignal」被宣告在「PyQt5.QtCore」裡面
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtCore import QThread, pyqtSignal
import time
from UI import Ui_MainWindow
我們在 ThreadTask 裡面宣告一個 global 的 pyqtSignal,
並指定給 qthread_signal,(需要宣告類別)
結果就像 「qthread_signal = pyqtSignal(int)」
我們把訊號送回去給主程式,我們透過 emit 這個 function,
可以協助我們把值送回主程式,並不影響主程式的任務。
class ThreadTask(QThread):
qthread_signal = pyqtSignal(int)
def start_progress(self):
max_value = 100
for i in range(max_value):
time.sleep(0.1)
self.qthread_signal.emit(i+1)
我們把按鈕連結 ButtonClick() 這個 function,
我們在 ButtonClick() 這個任務當中,宣告我們的一份新的 ThreadTask 任務,
並把訊號連接至一個 function,
class MainWindow_controller(QtWidgets.QMainWindow):
def __init__(self):
super().__init__() # in python3, super(Class, self).xxx = super().xxx
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setup_control()
def setup_control(self):
self.ui.progressBar.setMaximum(100)
self.ui.pushButton.clicked.connect(self.ButtonClick)
def ButtonClick(self):
self.qthread = ThreadTask()
self.qthread.qthread_signal.connect(self.progress_changed)
self.qthread.start_progress()
def progress_changed(self, value):
self.ui.progressBar.setValue(value)
我們需要把值的變化傳至一個 function 當中,
我們利用「self.qthread.qthread_signal.connect(self.progress_changed) 」
連結「訊號 (qthread_signal)」與 「function - progress_changed()」的關係
而這個 function 會需要保留 value 作為一個欄位,
實際上在連接時,並不是以常見的形式傳入這個值
(正確來說,應該是前面的 qthread_signal 被作為 value 傳入)
def progress_changed(self, value):
self.ui.progressBar.setValue(value)
我們仔細看,value 就是代表 qthread_signal,
所以我們可以直接從 setValue 去改他。
這部分就沒什麼好說的,我們會需要一個 function 幫助我們啟動 Thread。
並用 emit 把訊號送回來。
★ 本文也同步發於我的個人網站(會有內容目錄與顯示各個小節,閱讀起來更流暢):【PyQt5】Day 20 - PyQt 最重要的 QThread 概念 / 為什麼 windows, mac, ubuntu (linux) 程式會「沒有回應」?