Ho letto molto su questo argomento in passato e ho guardato alcuni interessanti talk come questo da Uncle Bob's . Tuttavia, trovo sempre molto difficile architettare correttamente le mie applicazioni desktop e distinguere quali dovrebbero essere le responsabilità sul lato UI e quali sul lato logic .
Un riassunto molto breve delle buone pratiche è qualcosa del genere. Dovresti progettare la logica disaccoppiata dall'interfaccia utente, in modo da poter usare (teoricamente) la tua libreria, indipendentemente dal tipo di backend / interfaccia utente. Ciò significa che l'interfaccia utente dovrebbe essere il più fittizio possibile e che l'elaborazione intensiva dovrebbe essere eseguita sul lato logico. Detto altrimenti, potrei letteralmente usare la mia bella libreria con un'applicazione di console, un'applicazione web o desktop.
Inoltre, lo zio Bob suggerisce discussioni divergenti su quale tecnologia usare per darti molti vantaggi (buone interfacce), questo concetto di differimento ti permette di avere entità ben collaudate e altamente disaccoppiate, che suona alla grande ma è comunque complicato.
Quindi, so che questa domanda è una domanda piuttosto ampia che è stata discussa molte volte su tutta la rete e anche in tonnellate di buoni libri. Quindi, per ottenere qualcosa di buono, pubblicherò un piccolo esempio fittizio cercando di usare MCV su pyqt:
import sys
import os
import random
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import QtCore
random.seed(1)
class Model(QtCore.QObject):
item_added = QtCore.pyqtSignal(int)
item_removed = QtCore.pyqtSignal(int)
def __init__(self):
super().__init__()
self.items = {}
def add_item(self):
guid = random.randint(0, 10000)
new_item = {
"pos": [random.randint(50, 100), random.randint(50, 100)]
}
self.items[guid] = new_item
self.item_added.emit(guid)
def remove_item(self):
list_keys = list(self.items.keys())
if len(list_keys) == 0:
self.item_removed.emit(-1)
return
guid = random.choice(list_keys)
self.item_removed.emit(guid)
del self.items[guid]
class View1():
def __init__(self, main_window):
self.main_window = main_window
view = QtWidgets.QGraphicsView()
self.scene = QtWidgets.QGraphicsScene(None)
self.scene.addText("Hello, world!")
view.setScene(self.scene)
view.setStyleSheet("background-color: red;")
main_window.setCentralWidget(view)
class View2():
add_item = QtCore.pyqtSignal(int)
remove_item = QtCore.pyqtSignal(int)
def __init__(self, main_window):
self.main_window = main_window
button_add = QtWidgets.QPushButton("Add")
button_remove = QtWidgets.QPushButton("Remove")
vbl = QtWidgets.QVBoxLayout()
vbl.addWidget(button_add)
vbl.addWidget(button_remove)
view = QtWidgets.QWidget()
view.setLayout(vbl)
view_dock = QtWidgets.QDockWidget('View2', main_window)
view_dock.setWidget(view)
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, view_dock)
model = main_window.model
button_add.clicked.connect(model.add_item)
button_remove.clicked.connect(model.remove_item)
class Controller():
def __init__(self, main_window):
self.main_window = main_window
def on_item_added(self, guid):
view1 = self.main_window.view1
model = self.main_window.model
print("item guid={0} added".format(guid))
item = model.items[guid]
x, y = item["pos"]
graphics_item = QtWidgets.QGraphicsEllipseItem(x, y, 60, 40)
item["graphics_item"] = graphics_item
view1.scene.addItem(graphics_item)
def on_item_removed(self, guid):
if guid < 0:
print("global cache of items is empty")
else:
view1 = self.main_window.view1
model = self.main_window.model
item = model.items[guid]
x, y = item["pos"]
graphics_item = item["graphics_item"]
view1.scene.removeItem(graphics_item)
print("item guid={0} removed".format(guid))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
# (M)odel ===> Model/Library containing should be UI agnostic, right now it's not
self.model = Model()
# (V)iew ===> Coupled to UI
self.view1 = View1(self)
self.view2 = View2(self)
# (C)ontroller ==> Coupled to UI
self.controller = Controller(self)
self.attach_views_to_model()
def attach_views_to_model(self):
self.model.item_added.connect(self.controller.on_item_added)
self.model.item_removed.connect(self.controller.on_item_removed)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
form = MainWindow()
form.setMinimumSize(800, 600)
form.show()
sys.exit(app.exec_())
Il frammento di cui sopra contiene molti difetti, il più ovvio è il modello che è accoppiato al framework UI (QObject, segnali pyqt). So che l'esempio è davvero fittizio e potresti codificarlo su poche righe usando una sola QMainWindow ma il mio scopo è capire come progettare correttamente un'applicazione pyqt più grande.
DOMANDA
Come configureresti correttamente una grande applicazione PyQt usando MVC seguendo le buone pratiche generali?
RIFERIMENTI
Ho fatto una domanda simile a questo qui