Code

tiforadacaixa

PySide2 / PyQt - estruturando projeto em mvc



Essa postagem é na verdade a tradução de uma postagem que esta localizada em https://stackoverflow.com/questions/26698628/mvc-design-with-qt-designer-and-pyqt-pyside.
Como achei muito util resolvi traduzir e postar aqui, todos os creditos ao criador da postagem.
Em primeiro lugar, esteja ciente de que o Qt já usa o conceito de Views e Models, que em suma, é uma maneira de vincular automaticamente um widget (por exemplo, um QListView) a uma fonte de dados (por exemplo, um QStringListModel) para que as alterações nos dados do model apareçam automaticamente no widget e vice-versa. Esse é um recurso útil, mas diferente do design do MVC na escala de aplicativos, embora os dois possam ser usados juntos e ofereçam alguns atalhos óbvios. No entanto, o design MVC em escala de aplicativo deve ser programado manualmente.
Aqui está um exemplo de aplicativo MVC que possui uma única view, controller e model. A view possui 3 widgets que cada um escuta independentemente e reage a alterações nos dados no model. A caixa de rotação e o botão podem manipular dados no modelo através do controlador.
mvc_app
A estrutura dos arquivos sera a seguinte:
project/
    mvc_app.py              # Aplicação principal com a classe App
    mvc_app_rc.py           # arquivo de resources gerado automaticamente (usando pyrcc.exe ou algo equivalente)
    controllers/
        main_ctrl.py        # controller principal com a classe MainController
        other_ctrl.py
    model/
        model.py            # model with Model class
    resources/
        mvc_app.qrc         # arquivos de resource do qt
        main_view.ui        # arquivos gerados pelo qt designer
        other_view.ui
        img/
            icon.png
    views/
        main_view.py        # view principal com a classe MainView
        main_view_ui.py     # auto-generated ui file (using pyuic.exe or equivalent)
        other_view.py
        other_view_ui.py

Application

mvc_app.py irá ser responsável por instanciar cada uma das views, contollers e models e passar referências entre eles. Isso pode ser bastante basíco:
import sys
from PyQt5.QtWidgets import QApplication
from model.model import Model
from controllers.main_ctrl import MainController
from views.main_view import MainView


class App(QApplication):
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.model = Model()
        self.main_controller = MainController(self.model)
        self.main_view = MainView(self.model, self.main_controller)
        self.main_view.show()


if __name__ == '__main__':
    app = App(sys.argv)
    sys.exit(app.exec_())

views

Use o Qt designer para criar os arquivos de layout .ui na medida em que designe nomes de variáveis para widgets e ajuste suas propriedades básicas. Não se preocupe em adicionar sinais ou slots, pois geralmente é mais fácil conectá-los a funções da classe view.

Os arquivos de layout .ui são convertidos em arquivos de layout .py quando processados com pyuic ou pyside-uic. Os arquivos de exibição .py podem importar as classes geradas automaticamente relevantes dos arquivos de layout .py.

As classes de exibição devem conter o código mínimo necessário para conectar-se aos sinais provenientes dos widgets em seu layout. Os eventos de exibição podem chamar e passar informações básicas para um método na classe de exibição e para um método em uma classe de controller, onde qualquer lógica deve estar. Seria algo como:

from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import pyqtSlot
from views.main_view_ui import Ui_MainWindow


class MainView(QMainWindow):
    def __init__(self, model, main_controller):
        super().__init__()

        self._model = model
        self._main_controller = main_controller
        self._ui = Ui_MainWindow()
        self._ui.setupUi(self)

        # conecta o widget ao controller
        self._ui.spinBox_amount.valueChanged.connect(self._main_controller.change_amount)
        self._ui.pushButton_reset.clicked.connect(lambda: self._main_controller.change_amount(0))

        # ouve sinais de eventos do model
        self._model.amount_changed.connect(self.on_amount_changed)
        self._model.even_odd_changed.connect(self.on_even_odd_changed)
        self._model.enable_reset_changed.connect(self.on_enable_reset_changed)

        # atriui um valor padrão
        self._main_controller.change_amount(42)

    @pyqtSlot(int)
    def on_amount_changed(self, value):
        self._ui.spinBox_amount.setValue(value)

    @pyqtSlot(str)
    def on_even_odd_changed(self, value):
        self._ui.label_even_odd.setText(value)

    @pyqtSlot(bool)
    def on_enable_reset_changed(self, value):
        self._ui.pushButton_reset.setEnabled(value)
A view não faz muito além dos eventos do widget de link com a função relevante do controlador e escuta as alterações no modelo, que são emitidas como sinais Qt.

Controllers


As classes do controlador executam qualquer lógica e, em seguida, configuram os dados no model. exemplo:

from PyQt5.QtCore import QObject, pyqtSlot


class MainController(QObject):
    def __init__(self, model):
        super().__init__()

        self._model = model

    @pyqtSlot(int)
    def change_amount(self, value):
        self._model.amount = value

        # calcular par ou ímpar
        self._model.even_odd = 'odd' if value % 2 else 'even'

        # verifica o estado de ativação do botão
        self._model.enable_reset = True if value else False
A fuçãochange_amount pega o novo valor do widget, executa a lógica e define atributos no model.

Model

A classe model armazena dados e estado do programa e alguma lógica mínima para anunciar alterações nesses dados. Este modelo não deve ser confundido com o modelo Qt (Veja: http://qt-project.org/doc/qt-4.8/model-view-programming.html) como não é realmente a mesma coisa.
O modelo pode parecer com isso:
from PyQt5.QtCore import QObject, pyqtSignal


class Model(QObject):
    amount_changed = pyqtSignal(int)
    even_odd_changed = pyqtSignal(str)
    enable_reset_changed = pyqtSignal(bool)

    @property
    def amount(self):
        return self._amount

    @amount.setter
    def amount(self, value):
        self._amount = value
        self.amount_changed.emit(value)

    @property
    def even_odd(self):
        return self._even_odd

    @even_odd.setter
    def even_odd(self, value):
        self._even_odd = value
        self.even_odd_changed.emit(value)

    @property
    def enable_reset(self):
        return self._enable_reset

    @enable_reset.setter
    def enable_reset(self, value):
        self._enable_reset = value
        self.enable_reset_changed.emit(value)

    def __init__(self):
        super().__init__()

        self._amount = 0
        self._even_odd = ''
        self._enable_reset = False
As alterações no modelo emitem automaticamente sinais para quaisquer views de escuta via código na função decoradora setter . Como alternativa, o controlador pode disparar manualmente o sinal sempre que ele quiser.
No caso em que os tipos de model padrões do Qt (por exemplo, QStringListModel) foram conectados a um widget, a visualização que contém esse widget não precisa ser atualizada; isso acontece automaticamente através do Qt.

arquivo de codigo UI

Para conclusão, o exemplo o arquivo main_view.ui está incluído aqui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>93</width>
    <height>86</height>
   </rect>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout">
    <item>
     <widget class="QSpinBox" name="spinBox_amount"/>
    </item>
    <item>
     <widget class="QLabel" name="label_even_odd"/>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton_reset">
      <property name="enabled">
       <bool>false</bool>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
Ele deve ser convertido para main_view_ui.py com o comando:
pyuic5 main_view.ui -o ..\views\main_view_ui.py
O arquivo de resource mvc_app.qrc deve ser convertido para mvc_app_rc.py com o comando:
pyrcc5 mvc_app.qrc -o ..\mvc_app_rc.py

Isso é tudo por hoje, espero que esse conteúdo seja útil para vocês também, qualquer novidade eu estarei postando.

Postar um comentário

0 Comentários