PyQt5通過信號實現(xiàn)MVC的示例
眾所周知MVC是個好東西。前陣子網(wǎng)上搜了下,但關(guān)于用PyQt5實現(xiàn)MVC的中文文檔缺少之又少,優(yōu)質(zhì)的文檔只搜到了一篇。既然這樣,來,開個坑,學(xué)習(xí)新知識,吸引流量。話說,關(guān)于PyQt5,布局那里需要好好看看,容器類控件需要好好看看,還有多線程和自動化測試那塊。但要寫出完美GUI需要大量的代碼經(jīng)驗和文檔查詢的能力。然后,嗯,這部分坑就填完了。
扯回正題:假設(shè)此時面臨的場景是,一個軟件涉及好幾個頁面,每個頁面是單獨(dú)的代碼。且每個頁面需要有自己的controller,最終所有的controller匯總到一起,統(tǒng)一管理。
本文中,文字只是輔助理解,務(wù)必讀懂代碼。
信號
眾所周知,GUI中當(dāng)一個控件的狀態(tài)改變時需要通知另一個控件,也就是實現(xiàn)了對象間的通信。當(dāng)事件發(fā)生或狀態(tài)改變時,就會發(fā)出信號,信號會觸發(fā)與這個事件相關(guān)聯(lián)的函數(shù),我們這個函數(shù)為槽。信號與槽可以是多對多的關(guān)系。信號在類創(chuàng)建時定義,即需要在初始化的前面定義。
自定義信號與槽
別問,靜靜感受以下代碼。以下的代碼中,已經(jīng)包含了信號的定義、指定參數(shù)的類型、發(fā)射、綁定槽函數(shù)等一系列過程。
from PyQt5.QtCore import QObject, pyqtSignal # 信號對象 class QSignal(QObject): # 定義信號 # 在類創(chuàng)建時定義,不能在類創(chuàng)建后作為類的屬性而添加 # 指定信號傳遞參數(shù)的數(shù)量,類型等 send_msg = pyqtSignal(str, str) def __init__(self): super(QSignal, self).__init__() def run(self): # 信號發(fā)射 self.send_msg.emit('First arg', 'Second arg') # 槽對象 class QSlot(QObject): def __init__(self): super(QSlot, self).__init__() def get(self, *args): # 信號接收 print("Get message =>" + args[0], args[1], sep=', ') if __name__ == '__main__': send = QSignal() slot = QSlot() # 將信號與槽函數(shù)綁定 send.send_msg.connect(slot.get) # 外部調(diào)用 發(fā)射信號 send.run() # 信號與槽解除關(guān)聯(lián) send.send_msg.disconnect(slot.get) send.run()
內(nèi)置信號綁定自定義槽
這樣,再來看一個和窗口結(jié)合的實例。窗口中有一個按鈕,點(diǎn)擊按鈕就退出窗口。雖然這個例子很簡單,不用信號和槽也能實現(xiàn)。但這里給個例子靜心感受下:信號連接、發(fā)射、接收的全邏輯。
import sys from functools import partial from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QWidget, QHBoxLayout) class MainWindow(QMainWindow): btn_signal = pyqtSignal() def __init__(self): super(MainWindow, self).__init__() a = QPushButton("退出") # 給綁定的槽函數(shù)增加額外信息 a.clicked.connect(partial(self.btn_clicked, 1)) self.btn_signal.connect(self.close) self.setWindowTitle("演示") main_widget = QWidget() layout = QHBoxLayout() layout.addWidget(a) main_widget.setLayout(layout) # QMainWindow 不能設(shè)置布局 self.setCentralWidget(main_widget) def btn_clicked(self, n): print(n) self.btn_signal.emit() def close(self): app = QApplication.instance() app.quit() if __name__ == "__main__": # 在shell中執(zhí)行 app = QApplication(sys.argv) mywin = MainWindow() mywin.show() # 開始主循環(huán),直到退出 sys.exit(app.exec())
這里,想給綁定的槽函數(shù)btn_clicked傳遞額外參數(shù),但信號綁定時不能添加額外參數(shù)。對應(yīng)到上述例子中,close()可以通過指定信號的參數(shù)和類型來增加參數(shù),但btn_clicked()不能。一種解決方案是掏出萬能的partial函數(shù),將函數(shù)和參數(shù)綁定在一起。
至此,應(yīng)該了解了信號的工作方式和原理。而關(guān)于信號更多的內(nèi)容,如重載、裝飾器等,這里不做更多介紹,詳情參考官方文檔。話說,也佩服當(dāng)年的學(xué)習(xí)方式:『把所有代碼敲一遍』。時至今日也忘記了大多控件的含義和各種樣式的代碼,變成了:到時候去查API。
MVC
MVC的大名應(yīng)該都聽說過,model, view 和 control,即數(shù)據(jù)庫、頁面和處理邏輯相分離,這樣寫出來的代碼更加專一化。這里給份代碼感受下,三個內(nèi)容用三個類所實現(xiàn),個人不建議這樣寫,建議將文件放到三個文件夾下,而不是扔進(jìn)一份代碼里:
import sys from PyQt5 import QtCore from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QPushButton, QMessageBox, QLineEdit, QApplication) # View class MainWindow(QWidget): verifySignal = QtCore.pyqtSignal() def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.id_line = QLineEdit() self.id_line.setPlaceholderText("請輸入賬號") self.psd_line = QLineEdit() self.psd_line.setPlaceholderText("請輸入密碼") self.init() def init(self): layout = QHBoxLayout() self.setLayout(layout) self.button = QPushButton("登錄") layout.addWidget(self.button) layout.addWidget(self.id_line) layout.addWidget(self.psd_line) # 連接定義的信號 self.button.clicked.connect(self.verify_emit) def verify_emit(self): self.verifySignal.emit() def verify_ok(self): QMessageBox.about(self, "密碼正確", "已經(jīng)登錄") def verify_no(self): QMessageBox.about(self, "你犯了一個粗誤", "請重新檢查輸入") # model class Student(object): def __init__(self): self.name = "aaa" self.password = "aaa" # control class LoginControll(object): def __init__(self): # 不需要從命令行輸入?yún)?shù) self._app = QApplication([]) self._model = Student() self._view = MainWindow() self.init() def init(self): self._view.verifySignal.connect(self.verify_user) def verify_user(self): id_ = self._view.id_line.text() psd_ = self._view.psd_line.text() if id_ == self._model.name and psd_ == self._model.password: self._view.verify_ok() else: self._view.verify_no() def run(self): self._view.show() # 事件循環(huán),直到應(yīng)用退出 return self._app.exec_() # main.py if __name__ == "__main__": login_control_ = LoginControll() # 退出主程序 sys.exit(login_control_.run())
在這個例子里需要注意的是,將model,view和controller分成了三個類。在view中定義信號以及信號何時發(fā)射,在controller中定義信號發(fā)射后連接的槽函數(shù),即觸發(fā)何種的響應(yīng)。這樣,通過信號的發(fā)射與連接,就將view和controller綁定在了一起。view負(fù)責(zé)頁面展示與信號定義,controller負(fù)責(zé)信號的連接與功能的實現(xiàn),完美。
MVC實現(xiàn)
單頁面
如果讀懂以上內(nèi)容,那么應(yīng)該可以實戰(zhàn)了。首先給出一個demo,就是將上面最簡單的MVC的例子拆分為三個文件。這里不便代碼展示,請移步到我的github進(jìn)行觀看,這是文件結(jié)構(gòu),這是主文件。
多頁面
在實現(xiàn)個復(fù)雜點(diǎn)的邏輯,多個頁面,多個controller,文件結(jié)構(gòu)如下所示,一個主文件,配三個文件夾,完美。這里命名時盡量規(guī)范,文件名、類名、函數(shù)名,不然容易把自己搞暈了。python main.py執(zhí)行。
MVC-demo ├─ main.py ├─ UI │ ├─ leftbtn_ui.py │ ├─ login_ui.py │ ├─ main_window_ui.py │ └─ verify_ui.py ├─ control │ ├─ controller.py │ ├─ leftbtn_control.py │ ├─ login_control.py │ └─ verify_control.py └─ model └─ model.py
調(diào)用關(guān)系如下:
這里需要注意的是變量的生存周期,main調(diào)用controller,controller調(diào)用其它的子controller,很容易在聲明一個類后局部變量消失,導(dǎo)致信號無法連接。如在controller.py中,典型錯誤的寫法:
class Controll(object): def __init__(self): self._app = QApplication([]) self._stu = Student() self._view = MainWindow() self.init() def init(self): # 子 controller 作為局部變量,調(diào)用完后立刻消失,所以無法連接信號和槽 # 這個問題困擾了我三天,可真是滑稽 login_controller = login_control.Controller(self._view, self._stu.name, self._stu.password)
因為代碼文件實在太多且混亂,就不在這里展示了,不然讀者會更容易感到亂。這里只展示一個效果,完整代碼見我的github。其實看一個例子,就啥都懂了。
以上就是PyQt5通過信號實現(xiàn)MVC的示例的詳細(xì)內(nèi)容,更多關(guān)于PyQt5通過信號實現(xiàn)MVC的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Pytorch 如何查看、釋放已關(guān)閉程序占用的GPU資源
這篇文章主要介紹了Pytorch 查看、釋放已關(guān)閉程序占用的GPU資源的操作,具有很好的參考價值,希望對大家有所幫助。2021-05-05Pytorch中Softmax和LogSoftmax的使用詳解
這篇文章主要介紹了Pytorch中Softmax和LogSoftmax的使用詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06scipy稀疏數(shù)組coo_array的實現(xiàn)
本文主要介紹了scipy稀疏數(shù)組coo_array的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Python基礎(chǔ)學(xué)習(xí)之常見的內(nèi)建函數(shù)整理
所謂的內(nèi)建函數(shù),可以直接使用,而不需要import。下面這篇文章主要給大家整理介紹了關(guān)于Python基礎(chǔ)學(xué)習(xí)之常見的一些內(nèi)建函數(shù),文中通過示例代碼為大家介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09pandas如何將DataFrame?轉(zhuǎn)為txt文本去除引號
這篇文章主要介紹了pandas如何將DataFrame?轉(zhuǎn)為txt文本去除引號,文中補(bǔ)充介紹了DataFrame導(dǎo)CSV?txt?||?每行有雙引號的原因及解決辦法,感興趣的朋友跟隨小編一起看看吧2024-01-01