Using MVC design pattern to build GUI (PyQt5 version)

Keywords: Python MATLAB mvc

Today, we introduce an example of using MVC design pattern to build GUI in PyQt5. This case comes from Chapter 7 of MATLAB object-oriented programming - from introduction to design pattern (version 2). For details about the GUI tool design of deposit and withdrawal, please refer to pages 127 ~ 160.

The final interface created by PyQt5 is as follows:

Next, let's talk about the specific implementation details.

Compared with the version designed by Matlab, the only difference is that the implementation of event processing mechanism is different.

PyQt5/Qt is called signal and slot, which is used for communication between objects. When a specified event occurs, an event signal will be transmitted, and the slot can be called by any Python script. When the signal connected to the slot is transmitted, the slot will be called. The custom event is completed by the pyqtSignal method in PyQt5.QtCore.

The handle class in Matlab has also helped us to implement the event handling mechanism. In addition to the control's own attributes such as Callback and ButtonPushedFcn (which can also be understood as built-in events), you can also customize events (defined by events), then register events through the notify method (corresponding to signal transmission in Qt!), and finally listen to events by addlistener (equivalent to the function of the groove).

The source code of the implemented model class is as follows:

# !/usr/bin/env python3
# -*- coding:utf-8 -*-

from PyQt5.QtCore import QObject, pyqtSignal


class BalanceModel(QObject):

    balance_changed = pyqtSignal()

    def __init__(self, balance):
        super(BalanceModel, self).__init__()
        self.balance = balance

    def withdraw(self, val):
        self.balance -= val
        self.balance_changed.emit()

    def deposit(self, val):
        self.balance += val
        self.balance_changed.emit()

The statement balance_changed = pyqtSignal() defines an event signal: balance_changed. In the model class, when withdrawal or deposit occurs, the balance changes, that is, the balance_changed event signal is triggered, and a message will be published (transmitted) (by binding the emit() method) . next, you need to define the so-called slot function in the view class, that is, listen to the balance_changed event signal and respond.

The source code of the view class is as follows:

# !/usr/bin/env python3
# -*- coding:utf-8 -*-

from PyQt5.QtWidgets import (QWidget,
                             QGridLayout,
                             QLabel,
                             QLineEdit,
                             QPushButton)
from PyQt5.QtCore import Qt
from BalanceController import BalanceController


class BalanceView(QWidget):

    def __init__(self, m_obj):
        super(BalanceView, self).__init__()
        self.m_obj = m_obj
        self.c_obj = self.make_controller()  # Controller object
        self.balance_label = None
        self.balance_text = None
        self.rmb_label = None
        self.rmb_text = None
        self.withdraw_btn = None
        self.deposit_btn = None
        self.build_app()
        self.attach2controller(self.c_obj)
        self.m_obj.balance_changed.connect(self.update_balance)

    def build_app(self):
        self.setVisible(False)
        self.resize(300, 120)
        self.setWindowTitle('Deposit and withdrawal interface')

        self.balance_label = QLabel('Balance')
        self.balance_label.setAlignment(Qt.AlignRight)
        self.balance_text = QLineEdit()
        self.balance_text.setAlignment(Qt.AlignRight)
        self.balance_text.setReadOnly(True)
        self.balance_text.setText('0')
        self.rmb_label = QLabel('RMB')
        self.rmb_label.setAlignment(Qt.AlignRight)
        self.rmb_text = QLineEdit()
        self.rmb_text.setAlignment(Qt.AlignRight)
        self.rmb_text.setText('0')
        self.withdraw_btn = QPushButton('withdraw')
        self.withdraw_btn.setAutoFillBackground(True)
        self.deposit_btn = QPushButton('deposit')

        main_layout = QGridLayout(self)
        main_layout.setHorizontalSpacing(15)
        main_layout.setVerticalSpacing(15)

        main_layout.addWidget(self.balance_label, 0, 0, 1, 2)
        main_layout.addWidget(self.balance_text, 0, 2, 1, 2)
        main_layout.addWidget(self.rmb_label, 1, 0, 1, 2)
        main_layout.addWidget(self.rmb_text, 1, 2, 1, 2)
        main_layout.addWidget(self.withdraw_btn, 2, 0, 1, 2)
        main_layout.addWidget(self.deposit_btn, 2, 2, 1, 2)

        self.setLayout(main_layout)
        self.setVisible(True)
        self.update_balance()

    def update_balance(self):
        self.balance_text.setText(str(self.m_obj.balance))

    def make_controller(self):
        controller = BalanceController(self, self.m_obj)
        return controller

    def attach2controller(self, controller):
        self.withdraw_btn.clicked.connect(controller.withdraw_btn_callback)
        self.deposit_btn.clicked.connect(controller.deposit_btn_callback)

For button controls, the connect method is required to listen for the event signal: self.m_obj.balance_changed.connect(self.update_balance)

The source code of the controller class is as follows:

# !/usr/bin/env python3
# -*- coding:utf-8 -*-


class BalanceController:

    def __init__(self, v_obj, m_obj):
        self.v_obj = v_obj
        self.m_obj = m_obj

    def withdraw_btn_callback(self):
        val = float(self.v_obj.rmb_text.displayText())
        self.m_obj.withdraw(val)

    def deposit_btn_callback(self):
        val = float(self.v_obj.rmb_text.displayText())
        self.m_obj.deposit(val)

Finally, we can customize the script balanceApp.py and combine the MVC source code to run this GUI gadget, as shown in the following figure:

So far, we have introduced a relatively complete method of building GUI using MVC design pattern! I hope you like it and can get useful things from it.

Please go back to "pyqt5#u MVC" in gzh to download the complete code of this article.

[recommended in previous periods]

Posted by setaside on Thu, 30 Sep 2021 12:00:36 -0700