Java业务逻辑pyqt_PyQt5 UI界面与业务逻辑分离
先說遇到的問題希望遇到和我一樣問題的童鞋也可以成功解決。我在處理邏輯業務時候比較耗時經常造成界面未響應!!!。
但是當使用python 的thread時候會造成主界面數據復原,暫時不知道什么原因。之后開始我的學習之路。
因為qt界面的刷新相當于一直while,當有耗時多的任務時就會造成阻塞無法完成刷新,造成界面未響應。
這時候就需要使用Qthread處理業務邏輯,主線程繼續處理界面。分離ui界面與業務邏輯。
要使用QThread開始一個線程,可以創建它的一個子類,然后覆蓋其QThread.run()函數
class Thread(QThread):
def __init__(self):
super().__init__()
def run(self):
# 線程相關代碼
pass
# 創建一個新的線程
thread = Thread()
thread.start()
在使用線程時可以直接得到Thread實例,調用其start()函數即可啟動線程,線程啟動后,會調用其實現的run方法,該方法就是線程的執行函數,當run()退出之后線程基本就結束了。
QThread類中的常用方法:
start() 啟動線程
wait() 阻止線程
sleep(s) 強制當前線程睡眠s秒
QThread類中的常用信號:
started 在開始執行run()函數之前,從相關線程發射此信號
finished 在程序完成業務邏輯時,從相關線程發射此信號
當在窗口中顯示的數據比較簡單時,可以把讀取數據的業務邏輯放在窗口的初始化代碼中;但如果讀取數據的時間比較長,比如網絡請求數據的時間比較長,則可以把這部分邏輯放在QThread線程中,實現界面的數據顯示和數據讀取的分離.
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class Worker(QThread):
sinOut = pyqtSignal(str) # 自定義信號,執行run()函數時,從相關線程發射此信號
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
self.working = True
self.num = 0
def __del__(self):
self.working = False
self.wait()
def run(self):
while self.working == True:
file_str = 'File index {0}'.format(self.num) # str.format()
self.num += 1
# 發出信號
self.sinOut.emit(file_str)
# 線程休眠2秒
self.sleep(2)
class MainWidget(QWidget):
def __init__(self, parent=None):
super(MainWidget, self).__init__(parent)
self.setWindowTitle("QThread 例子")
# 布局管理
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.slotStart)
# 創建新線程,將自定義信號sinOut連接到slotAdd()槽函數
self.thread = Worker()
self.thread.sinOut.connect(self.slotAdd)
# 開始按鈕按下后使其不可用,啟動線程
def slotStart(self):
self.btnStart.setEnabled(False)
self.thread.start()
# 在列表控件中動態添加字符串條目
def slotAdd(self, file_inf):
self.listFile.addItem(file_inf)
if __name__ == "__main__":
app = QApplication(sys.argv)
demo = MainWidget()
demo.show()
sys.exit(app.exec_())
這個經典例子,雖然解決了界面的數據顯示和數據讀取的分離,但是如果數據的讀取非常消耗時間,則會造成界面卡死,下面是一個需要耗費很長時間讀取數據的例子。
import sys from PyQt5.QtCore
import * from PyQt5.QtGui
import * from PyQt5.QtWidgets
import * global sec sec = 0
def setTime():
global sec sec += 1
# LED顯示數字+1
lcdNumber.display(sec)
def work():
#每秒計數
timer.start(1000)
# 開始一次非常耗時的計算
# 這里用一個2 000 000 000次的循環來模擬
for i in range(200000000):
pass timer.stop()
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()
# 每次計時結束,觸發setTime
timer.timeout.connect(setTime)
# 連接測試按鈕和槽函數
work button.clicked.connect(work)
top.show()
sys.exit(app.exec_())
程序的運行邏輯如下:
這里寫圖片描述
正常情況下,在點擊按鈕之后,LCD上的數字會隨著時間發生變化,但是在實際運行過程中會發現點擊按鈕之后,程序界面直接停止響應,直到循環結束才開始重新更新,于是計時器始終顯示為0。
在上面這個程序中沒有引入新的線程,PyQt中所有的窗口都在UI主線程中(就是執行了QApplication.exec()的線程),在這個線程中執行耗時的操作會阻塞UI線程,從而讓窗口停止響應。
為了避免出現上述問題,要使用QThread開啟一個新的線程,在這個線程中完成耗時的操作:
mport sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
global sec
sec = 0
# 增加了一個繼承自QThread類的類,重新寫了它的run()函數
# run()函數即是新線程需要執行的:執行一個循環;發送計算完成的信號。
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需要等待程序執行完畢才能進行下一步,這個過程表現在界面上就是卡頓;而如果在執行這個耗時程序時不斷地運行QApplication.processEvents(),那么就可以實現一邊執行耗時程序,一邊刷新頁面的功能,會給人一種相對更流暢的感覺,QApplication.processEvents()的使用方法是,在主函數執行耗時操作的地方,加入QApplication.processEvents(),processEvents()函數的使用方法簡單來說就是刷新頁面。(可以在table獲取數據及時顯示等操作使用)
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.setLayout(layout)
self.btnStart.clicked.connect(self.slotAdd)
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_())
如果不添加QApplication.processEvents(),會在卡頓之后全部結果,添加之后,也不能保證每個都是逐行顯示,只是比不加相對流暢一點,效果是不如多線程的。
總結
能用多線程盡量用多線程,不論數據處理還是界面流程性都優于QApplication.processEvents(),但是當數據量小的時候可以使用QApplication.processEvents(),代碼比較簡單。
總結
以上是生活随笔為你收集整理的Java业务逻辑pyqt_PyQt5 UI界面与业务逻辑分离的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 泛型int_Java 泛型
- 下一篇: mysql原生分页语句_mysql原生分