天天看點

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)

目錄

1、QTimer計時器類

2、QThread多線程類

3、事件處理類

一般情況下,應用程式都是單線程運作的,但是對于GUI程式來說,單線程有時候滿足不了需求。例如,如果需要執行一個特别耗時的操作,在執行過程中整個程式就會卡頓,效果就非常不理想或者Windows系統也認為程式運作出錯,自動關閉了程式。要解決這種問題就涉及多線程的知識。

一般來說,多線程技術涉及三種方法,其中第一種是使用計時器子產品QTimer;第二種是使用多線程子產品QThread;最後是使用事件處理的功能。

如果要在應用程式中周期性地進行某項操作,比如周期性地檢測主機的CPU值,則需要用到QTimer(定時器),QTimer類提供了重複的和單次的定時器。要使用定時器,需要先建立一個QTimer執行個體,将其timeout信号連接配接到相應的槽,并調用start()。然後,定時器會以恒定的間隔發出timeout信号。

當視窗控件收到timeout信号後,它就會停止這個定時器。這是在圖形使用者界面中實作複雜工作的一個典型方法,随着技術的進步,多線程在越來越多的平台上被使用,最終QTimer對象會被線程所替代。

QTimer類中的常用方法如下表所示:

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)
QTimer類中的常用信号如下表所示:
Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)
通過一個示例,了解QTimer計時器類的使用方法,效果如下所示:
Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)

示例中,初始化一個定時器,把定時器的timeout信号與showTime()槽函數連接配接起來。使用連接配接的槽函數顯示目前時間,并在标簽上顯示系統現在的時間。單擊“開始"按鈕,啟動定時器,并使"開始"按鈕失效。單擊“結束"按鈕,停止定時器,并使“結束"按鈕失效。

實作代碼如下所示:

from PyQt5.QtWidgets import QWidget,  QPushButton ,  QApplication ,QListWidget,  QGridLayout , QLabel
from PyQt5.QtCore import QTimer ,QDateTime
import sys 
class WinForm(QWidget):  
    def __init__(self,parent=None): 
  super(WinForm,self).__init__(parent) 
  self.setWindowTitle("QTimer demo")
  self.listFile= QListWidget() 
  self.label = QLabel('顯示目前時間')
  self.startBtn = QPushButton('開始') 
  self.endBtn = QPushButton('結束') 
  layout = QGridLayout(self) 
        # 初始化一個定時器
  self.timer = QTimer(self)
        # showTime()方法
  self.timer.timeout.connect(self.showTime)
 
  layout.addWidget(self.label,0,0,1,2)   
  layout.addWidget(self.startBtn,1,0) 
  layout.addWidget(self.endBtn,1,1)     
 
  self.startBtn.clicked.connect( self.startTimer) 
  self.endBtn.clicked.connect( self.endTimer) 
   
  self.setLayout(layout)   
 
    def showTime(self): 
  # 擷取系統現在的時間
  time = QDateTime.currentDateTime() 
  # 設定系統時間顯示格式
  timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd");
  # 在标簽上顯示時間
  self.label.setText( timeDisplay ) 
    def startTimer(self): 
        # 設定計時間隔并啟動
  self.timer.start(1000)
  self.startBtn.setEnabled(False)
  self.endBtn.setEnabled(True)
    def endTimer(self): 
  self.timer.stop()
  self.startBtn.setEnabled(True)
  self.endBtn.setEnabled(False)
 
if __name__ == "__main__":  
    app = QApplication(sys.argv)  
    form = WinForm()  
    form.show()  
    sys.exit(app.exec_())      

QThread是Qt線程類中最核心的底層類,由于PyQt的跨平台特性,QThread要隐藏所有與平台相關的代碼。

在使用線程時可以直接得到Thread執行個體,調用其start()函數即可啟動線程。線程啟動之後,會自動調用其實作的run方法,該方法就是線程的執行函數。

業務的線程任務就寫在run()函數中,當run()退出之後線程基本就結束了。QThread有started和finished信号,可以為這兩個信号指定槽函數,線上程啟動和結束時執行一段代碼進行資源的初始化和釋放操作。更靈活的使用方法是,在自定義的QThread執行個體中自定義信号,并将信号連接配接到指定的槽函數,當滿足一定的業務條件後發射此信号。

QThread類中的常用方法如下表所示:

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)

QThread類中的常用信号如下表所示:

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)

通過一個示例,了解QThread多線程類的使用方法,效果如下所示:

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)

示例中,在主界面中有一個用于顯示時間的LCD數字面闆,還有一個用于啟動任務的按鈕。使用者單擊"測試"按鈕後,将開始一次非常耗時的計算(在程式中用一個2000 000 000次的循環來模拟這次非常耗時的工作,在真實的程式中可能是一個網絡下載下傳操作,從網絡上下載下傳一個很大的視訊檔案),同時LCD數字面闆開始顯示所用的毫秒數,并通過一個計時器進行更新。但是單擊”測試“按鈕後可見視窗卡死無法操作。此時在PyQt中所有的視窗都在UI主線程中(就是執行了QApplication.exec()的線程),在這個線程中執行耗時的操作會阻塞UI線程,進而讓視窗停止響應。如果視窗長時間沒有響應,則會影響使用者體驗,更嚴重的會導緻程式崩潰。是以,為了避免出現這樣的問題,要使用QThread開啟一個新的線程,在這個線程中完成耗時的操作。

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
   
global sec
sec=0
class WorkThread(QThread):
    trigger = pyqtSignal()
    def __int__(self):
  super(WorkThread,self).__init__()
    def run(self):
  for i in range(2000000000):
    pass
 
  # 循環完畢後發出信号  
  self.trigger.emit()        
def countTime():
    global  sec
    sec += 1
    # LED顯示數字+1
    lcdNumber.display(sec)          
def work():
    # 計時器每秒計數
    timer.start(1000)   
    # 計時開始  
    workThread.start()       
    # 當獲得循環完畢的信号時,停止計數  
    workThread.trigger.connect(timeStop)  
def timeStop():
    timer.stop()
    print("運作結束用時",lcdNumber.value())
    global sec
    sec=0
if __name__ == "__main__":      
    app = QApplication(sys.argv) 
    top = QWidget()
    top.resize(300,120)
   
    # 垂直布局類QVBoxLayout
    layout = QVBoxLayout(top) 
    # 加個顯示屏    
    lcdNumber = QLCDNumber()             
    layout.addWidget(lcdNumber)
    button = QPushButton("測試")
    layout.addWidget(button)
    timer = QTimer()
    workThread = WorkThread()
    button.clicked.connect(work)
    # 每次計時結束,觸發 countTime
    timer.timeout.connect(countTime)      
    top.show()
    sys.exit(app.exec_())      

PyQt為事件處理提供了兩種機制:進階的信号與槽機制以及低級的事件處理程式。本篇文博隻介紹低級的事件處理程式即:processEvents()函數的使用方法,它的作用是處理事件,簡單地說,就是重新整理頁面。

對于執行很耗時的程式來說,由于PyQt需要等待程式執行完畢才能進行下一步,這個過程表現在界面上就是卡頓。而如果在執行這個耗時程式時不斷地運作

QApplication.processEvents(),那麼就可以實作一邊執行耗時程式,一邊重新整理頁面的功能,給人的感覺就是程式運作很流暢。

是以QApplication.processEvents()的使用方法就是,在主函數執行耗時操作的地方,加入QApplication.processEvents()。

通過一個示例,了解事件處理類的使用方法,效果如下所示:

Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)
from PyQt5.QtWidgets import QWidget,  QPushButton ,  QApplication ,QListWidget,  QGridLayout 
import sys 
import time
class WinForm(QWidget):  
    def __init__(self,parent=None): 
  super(WinForm,self).__init__(parent) 
  self.setWindowTitle("實時重新整理界面例子")        
  self.listFile= QListWidget() 
  self.btnStart = QPushButton('開始') 
  layout = QGridLayout(self) 
  layout.addWidget(self.listFile,0,0,1,2) 
  layout.addWidget(self.btnStart,1,1) 
  self.btnStart.clicked.connect( self.slotAdd) 
  self.setLayout(layout)   
 
    def slotAdd(self): 
  for n in range(10): 
    str_n='File index {0}'.format(n) 
    self.listFile.addItem(str_n) 
    QApplication.processEvents() 
    time.sleep(1) 
 
if __name__ == "__main__":  
    app = QApplication(sys.argv)  
    form = WinForm()  
    form.show()  
    sys.exit(app.exec_())      

文章知識點與官方知識檔案比對,可進一步學習相關知識