📜 ⬆️ ⬇️

Implementing MVC Pattern for PyQt

Good day to all!
The article describes the implementation of the MVC design pattern for applications using PyQt, using the example of an addition program for two numbers. In addition to describing the implementation of the pattern, a description is given of the process of creating an application.

0 Introduction


MVC is a design pattern that allows you to effectively separate the data model, the presentation and processing of actions. The MVC includes three design patterns: an observer , a strategy, and a linker . The article discusses the classic MVC scheme with an active model, described in Eric Freeman's book Design Patterns .

For further work will require:

1 Project Structure


Let's call the project SimpleMVC . So that the task does not seem so trivial, we will add not A and B , but C and D. The project consists of four modules. The Model , Controller, and View modules represent the implementation of the model, controller, and view, respectively. The utility module contains helper classes. The main.pyw file is designed to run the application.
')
  SimpleMVC \
     Controller \
         __init__.py
         CplusDController.py
     Model \
         __init__.py
         CplusDModel.py
     Utility \
         __init__.py
         CplusDMeta.py
         CplusDObserver.py
     View \
         __init__.py
         CplusDView.py
         MainWindow.py
         MainWindow.ui
     main.pyw 


2 Creating a model


The model (first of all!) Is responsible for the application logic. The task of CplusDModel is the addition of two numbers. CplusDModel.py file:

class CplusDModel: """  CplusDModel     .      c, d   .   ,       .    ,    . """ def __init__( self ): self._mC = 0 self._mD = 0 self._mSum = 0 self._mObservers = [] #   @property def c( self ): return self._mC @property def d( self ): return self._mD @property def sum( self ): return self._mSum @c.setter def c( self, value ): self._mC = value self._mSum = self._mC + self._mD self.notifyObservers() @d.setter def d( self, value ): self._mD = value self._mSum = self._mC + self._mD self.notifyObservers() def addObserver( self, inObserver ): self._mObservers.append( inObserver ) def removeObserver( self, inObserver ): self._mObservers.remove( inObserver ) def notifyObservers( self ): for x in self._mObservers: x.modelIsChanged() 


The model implements the observer pattern. This means that the class must support the add, delete, and alert functions of observers. At the same time, the model is completely independent of controllers and views.
It is important that all registered observers implement a certain method that will be called by the model when they are notified (in this case, this is the modelIsChanged () method). To do this, observers must be descendants of an abstract class, inheriting which method modelIsChanged () must be redefined. CplusDObserver.py file:

 from abc import ABCMeta, abstractmethod class CplusDObserver( metaclass = ABCMeta ): """     . """ @abstractmethod def modelIsChanged( self ): """         . """ pass 


Of course, a “very flexible python” allows you to do without an abstract superclass at all or use the cunning exception NotImplementedError () . In my opinion, this may adversely affect the architecture of the application.
I would like to note that using PyQt, one could use the slot-signal model. In this case, when the state changes, the model will send a signal that all attached observers will be able to receive. This approach seems to be less universal — you may want to use another library in the future.
In more complex systems, the functionality that implements the pattern observer (registration, deletion and notification of observers) can be separated into a separate abstract class. Depending on the architecture, the class may not be abstract.

3 Creating a View


A view is a combination of graphic components and implements a linker pattern. To create a view, use the Visual Editor Designer , included in the PyQt package. Create a form based on MainWindow . Form structure:

  MainWindow - QMainWindow
     CentralWidget - QWidget
         lbl_equal - QLabel
         lbl_plus - QLabel
         le_c - QLineEdit
         le_d - QLineEdit
         le_result - QLineEdit 


Appearance form:



To preview the form, use Ctrl + R. Save the file in the View directory as MainWindow.ui . Now we will generate the MainWindow.py file based on it. To do this, use pyuic4.bat (... \ Python32 \ Lib \ site-packages \ PyQt4). Run:

  pyuic4.bat ... \ SimpleMVC \ View \ MainWindow.ui -o ... \ SimpleMVC \ View \ MainWindow.py 


Instead of "..." you need to add the path to the directory with the project. For example, the full path might look like this:

  D: \ Projects \ PythonProjects \ SimpleMVC \ View \ MainWindow.ui 


If there is a resource file in the project, then to convert it, you need to use pyrcc4.exe (... \ Python32 \ Lib \ site-packages \ PyQt4). For Python 3.x, you must pass the -py3 flag:

  pyrcc4.exe -o ... \ SimpleMVC \ View \ MainWindow_rc.py -py3 ... \ SimpleMVC \ View \ MainWindow_rc.qrc 


Pay attention to "_rc". The fact is that if a resource file is connected to our form, pyuic4.bat will automatically add at the end of the file:

 import MainWindow_rc 


Now you need to create a view class. According to the MVC architecture, the view must be an observer. CplusDView.py file:

 from PyQt4.QtGui import QMainWindow, QDoubleValidator from PyQt4.QtCore import SIGNAL from Utility.CplusDObserver import CplusDObserver from Utility.CplusDMeta import CplusDMeta from View.MainWindow import Ui_MainWindow class CplusDView( QMainWindow, CplusDObserver, metaclass = CplusDMeta ): """      CplusDModel. """ def __init__( self, inController, inModel, parent = None ): """       . """ super( QMainWindow, self ).__init__( parent ) self.mController = inController self.mModel = inModel #    self.ui = Ui_MainWindow() self.ui.setupUi( self ) #      self.mModel.addObserver( self ) #      self.ui.le_c.setValidator( QDoubleValidator() ) self.ui.le_d.setValidator( QDoubleValidator() ) #        self.connect( self.ui.le_c, SIGNAL( "editingFinished()" ), self.mController.setC ) self.connect( self.ui.le_d, SIGNAL( "editingFinished()" ), self.mController.setD ) def modelIsChanged( self ): """     .     . """ sum = str( self.mModel.sum ) self.ui.le_result.setText( sum ) 


Notice that the view is a descendant of two classes: QMainWindow and CplusDObserver . Both classes use different metaclasses. Therefore, for the CplusDView class, you need to set a metaclass that will inherit the metaclasses used by QMainWindow and CplusDObserver. In this case, it is CplusDMeta . CplusDMeta.py file:

 """   ,    . pyqtWrapperType -      Qt. ABCMeta -     . CplusDMeta -   . """ from PyQt4.QtCore import pyqtWrapperType from abc import ABCMeta class CplusDMeta( pyqtWrapperType, ABCMeta ): pass 


4 Creating a controller


The controller implements the pattern strategy. The controller connects to the view to control its actions. In this case, the implementation of the controller is very trivial. CplusDController.py file:

 from View.CplusDView import CplusDview class CplusDController(): """  CplusDController   .     . """ def __init__( self, inModel ): """     .     . """ self.mModel = inModel self.mView = CplusDview( self, self.mModel ) self.mView.show() def setC( self ): """        C,    c . """ c = self.mView.ui.le_c.text() self.mModel.c = float( c ) def setD( self ): """        D,    d . """ d = self.mView.ui.le_d.text() self.mModel.d = float( d ) 


5 Compound Components


It remains to combine all the components and test the program. Main.pyw file:

 import sys from PyQt4.QtGui import QApplication from Model.CplusDModel import CplusDModel from Controller.CplusDController import CplusDController def main(): app = QApplication( sys.argv ) #   model = CplusDModel() #         controller = CplusDController( model ) app.exec() if __name__ == '__main__': sys.exit( main() ) 


Thanks for attention!

Source

Source: https://habr.com/ru/post/139454/


All Articles