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