Creating a Presenter class¶
In the Add Button section we had the response to a button press within the view. In practice this is not a good implementation. If the response was more complicated then it would be difficult to maintain the view as it would become extremely long. Furthermore creating the look of the GUI is fairly simple and any logic/responses should be contained within the presenter.
In this section we will make a simple presenter for when a button is pressed.
The View¶
First we will start with the view:
from qtpy.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
from typing import Union
class View(QWidget):
def __init__(self, parent: Union[QWidget, None]=None):
super().__init__(parent)
self.setWindowTitle("view tutorial")
# A presenter will be subscribed to the view later
self._presenter = None
self._button = QPushButton("Hi", self)
self._button.setStyleSheet("background-color:lightgrey")
# connect button to signal
self._button.clicked.connect(self._button_clicked)
self._label = QLabel()
self._label.setText("Button")
# add widgets to layout
self._sub_layout = QHBoxLayout()
self._sub_layout.addWidget(self._label)
self._sub_layout.addWidget(self._button)
grid = QVBoxLayout(self)
grid.addLayout(self._sub_layout)
# set the layout for the view widget
self.setLayout(grid)
def subscribe_presenter(self, presenter) -> None:
# Subscribe the presenter to the view so we do not need to
# make a Qt connection between the presenter and view
self._presenter = presenter
def _button_clicked(self) -> None:
print("hello from view")
self._presenter.handle_button_clicked()
The above code has two new additions. The first is a function which can
be used to subscribe the presenter to the view. The second addition is
that _button_clicked
now calls a member function of the presenter to
handle the response to the button being clicked.
Tip
Instead of connecting signals between the view and presenter, we have subscribed the presenter to the view.
The key advantage of this approach is that the presenter does not need to be a QObject. This helps us follow a core principle of the MVP pattern: keeping Qt-specific code confined to the view for better separation of concerns.
Why is this useful?
It makes the presenter easier to test and maintain.
The view and presenter are more decoupled, improving flexibility.
You can reuse the presenter in non-Qt environments.
This compartmentalization keeps the codebase cleaner and more modular.
The Presenter¶
The presenter is initialized with the view provided as a parameter. This flexibility is a key advantage of the MVP pattern: the view can be swapped out by passing a different one to the presenter. As long as the new view adheres to the same interface as the previous one, the functionality remains the same, but will have a different appearance.
A practical example of this is adapting the view based on device resolution. You could switch to a different layout or design depending on the user’s screen size, ensuring an optimized experience across devices.
class Presenter:
# pass the view into the presenter
def __init__(self, view):
self._view = view
# subscribe the presenter to the view
self._view.subscribe_presenter(self)
# handle events from the view
def handle_button_clicked(self) -> None:
print("hello world, from the presenter")
Notice that the presenter is subscribed to the view in the constructor. This is important if you want your callback from the view to work when a button is clicked.
The Main¶
The main is now:
import sys
from qtpy.QtWidgets import QApplication
from view import View
from presenter import Presenter
def _get_qapplication_instance() -> QApplication:
if app := QApplication.instance():
return app
return QApplication(sys.argv)
if __name__ == "__main__" :
app = _get_qapplication_instance()
view = View()
presenter = Presenter(view)
view.show()
app.exec_()
The view and presenter are both created, and the view is passed into the presenter.