Python Forum
[PyQt] MVC implementation issue
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] MVC implementation issue
#1
Good Evening everyone. I am working on an Hotel reservation system, that basically consists of a webscraper that picks rates from an hotel website and present them in a view for further manipulation. Pretty basic but sufficient, as it will be used by the hotel employees themselves. But I want it to be scalable and reusable for future developments, so I opted for PyQt5 and a MVC design pattern.

This is the final result I want to achieve:
   

Each component of the interface must be connected to a model and a controller. main.py is my 'entrypoint'.
No problem for the BSCalendarWidget and OccupationView, but I am not able to connect the BSStaySelectionWidget.
Here follows the code of the classes involved. Please ignore the inconsistent naming conventions, it's a work-in-progress.

BSWidgets.py
class BSStaySelectionView(QWidget):
    """
    A form that includes check-in and check-out date selectors.

    Args:
        mode (str): Selects the appearance of the date selection form.
            - 'default': shows the calendar date picker and text form;
            - 'calendar': shows only the calendar;
            - 'text': shows text fields only
    """

    # Signals
    check_in_changed = pyqtSignal(QDate)
    nights_count_changed = pyqtSignal(int)

    def __init__(self, mode: str = 'default'):  #controller=None
        super().__init__()
        # self.controller = controller
        # if self.controller is not None:
        #     self.connect_signals()

        self.checkin_fld = QDateEdit()  # was: QLineEdit()
        self.checkin_fld.setDisplayFormat('dd.MM.yy')
        self.checkout_fld = QDateEdit()  # was: QLineEdit()
        self.checkout_fld.setDisplayFormat('dd.MM.yy')

        self.nights_fld = QSpinBox()
        self.nights_fld.setMinimum(1)

        self.calendar_wgt = BSCalendarWidget(self)

        # self.checkin_fld.dateChanged.connect(self.controller.update_check_in)
        # self.nights_fld.valueChanged.connect(self.controller.update_nights_count)
        # self.calendar_wgt.date_range_selected.connect(self.controller.update_date_range)

        self.layout = QVBoxLayout()
        self.setup_ui()
        self.set_mode(mode)  # ui must be constructed entirely before customizing the mode

    def setup_ui(self):
        self.layout.addWidget(QLabel('Check-in'))
        self.layout.addWidget(self.checkin_fld)
        self.layout.addWidget(QLabel('Check-out'))
        self.layout.addWidget(self.checkout_fld)
        self.layout.addWidget(QLabel('Nights'))
        self.layout.addWidget(self.nights_fld)
        self.layout.addWidget(self.calendar_wgt)

        # New code
        self.checkin_fld.dateChanged.connect(self.check_in_changed.emit)
        self.nights_fld.valueChanged.connect(self.nights_count_changed.emit)

        self.setLayout(self.layout)

    def set_mode(self, mode: str):
        if mode == 'default':
            pass

        elif mode == 'calendar':
            for i in range(self.layout.count()):
                component = self.layout.itemAt(i).widget()
                if component is not None and isinstance(component, QLabel):
                    component.setHidden(True)
                elif component is not None and isinstance(component, QLineEdit):
                    component.setHidden(True)
                else:
                    pass

        elif mode == 'text':
            self.calendar_wgt.hide()

        else:
            warnings.warn(
                f'Invalid mode for StaySelectorForm. accepted options: "default", "calendar", "text" - entered "{mode}" instead'
            )
            pass

    def update_nights(self):
        # Calculate the number of nights and update the nights QLineEdit
        date_format = 'dd.MM.yy'
        from_date = QDate.fromString(self.checkin_fld.text(), date_format)
        to_date = QDate.fromString(self.checkout_fld.text(), date_format)
        self.nights_fld.setText(str(from_date.daysTo(to_date)))

    def update_check_out_date(self):
        # Add the number of nights to the check-in date and update the check-out date
        date_format = 'dd.MM.yy'
        from_date = QDate.fromString(self.checkin_fld.text(), date_format)

        try:
            num_nights = int(self.nights_fld.text())
        except ValueError:
            num_nights = 1  # When is invalid or, more likely, empty

        to_date = from_date.addDays(num_nights)
        self.checkout_fld.setDate(to_date)

    # def connect_signals(self):
    #     self.checkin_fld.dateChanged.connect(self.controller.update_check_in)
    #     self.nights_fld.valueChanged.connect(self.controller.update_nights_count)
    #     self.calendar_wgt.date_range_selected.connect(self.controller.update_date_range)


# TODO DEBUG
class TestWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Availability Request - ***** *** ******")
        data = [('2 adt, 0 ch', RoomOccupation(2, []))]

        # Central widget
        main_wgt = QWidget()
        lo = QHBoxLayout()

        # Dates form
        input_section = QWidget()
        input_section_layout = QVBoxLayout()

        occupation_view = OccupationView(data)

        begin_btn = QPushButton(QIcon('assets/search_ico.png'), 'Search')
        begin_btn.setMinimumHeight(40)
        begin_btn.setStyleSheet("background-color : #FFF59D")  # TODO We will deal with this with a proper CSS in real world
        begin_btn.clicked.connect(self.load)

        # try to implement the controller
        stay_selector = BSStaySelectionView('default')
        # stay_selector_model = StaySelectorModel()
        # stay_selector_controller = ReservationController(stay_selector_model, stay_selector)
        # stay_selector.set_controller(stay_selector_controller)

        input_section_layout.addWidget(stay_selector)
        input_section_layout.addWidget(occupation_view)
        input_section_layout.addStretch()
        input_section_layout.addWidget(begin_btn)

        input_section.setLayout(input_section_layout)
        input_section.setContentsMargins(0, 0, 0, 0)
        input_section.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

        lo.addWidget(input_section)

        # Output ("Response")
        response_stack_widget = QWidget()
        self.response_stack_layout = QStackedLayout()

        output_view = AvailabilityView()
        dialog_view = DialogView(
            'Welcome!',
            'To check availability, please enter some dates, select occupancy and click "SEARCH"',
            links={'*** Website': 'http://www.***.it',
             'User Guide': 'http://www.google.com'}
        )
        completion_view = CompletionView()

        self.response_stack_layout.addWidget(completion_view)  # 0 = Loading
        self.response_stack_layout.addWidget(dialog_view)  # 1 = Dialog
        self.response_stack_layout.addWidget(output_view)  # 2 = Results selection_view

        response_stack_widget.setLayout(self.response_stack_layout)
        response_stack_widget.setContentsMargins(0, 0, 0, 0)

        lo.addWidget(response_stack_widget)
        main_wgt.setLayout(lo)

        self.response_stack_layout.setCurrentIndex(1)
        self.setCentralWidget(main_wgt)

    def load(self):
        # Completion handler
        self.response_stack_layout.setCurrentIndex(0)
        try:
            self.data_thread = BSNetworking.FetchDataThread()
            self.data_thread.data_fetched.connect(self.on_data_fetched)
            self.data_thread.start()
        except ConnectionError:
            self.response_stack_layout.setCurrentIndex(1)

    def on_data_fetched(self, data):
        print(data)
        self.response_stack_layout.setCurrentIndex(2)
BSModels.py
class StaySelectorModel:
    def __init__(self):
        self.check_in_date = QDate.currentDate()
        self.check_out_date = self.check_in_date.addDays(1)
        self.nights_count = 1

    # def set_check_in(self, date):
    #     self.check_in_date = date
    #     self.check_out_date = self.check_in_date.addDays(self.nights_count)
    #
    # def set_nights_count(self, nights):
    #     self.nights_count = nights
    #     self.check_out_date = self.check_in_date.addDays(self.nights_count)
    #
    # def set_check_in_and_nights(self, check_in_date, nights):
    #     self.set_check_in(check_in_date)
    #     self.set_nights_count(nights)

    def set_check_in(self, date):
        self.check_in_date = date
        self.check_out_date = self.check_in_date.addDays(self.nights_count)

    def set_nights_count(self, nights):
        self.nights_count = nights
        self.check_out_date = self.check_in_date.addDays(self.nights_count)

    def set_check_in_and_nights(self, check_in_date, nights):
        self.set_check_in(check_in_date)
        self.set_nights_count(nights)

class RoomOccupationModel(QAbstractListModel):
    """
    RoomListModel represents a linear model for storing RoomOccupation objects
    and managing the data.
    The model maintains a list of RoomOccupation objects and implements several methods
    to communicate with a RoomOccupationView.

    Attributes:
    -----------
    _data : list
        (Private) A list of RoomOccupation objects that the model stores.

    Methods:
    --------
    rowCount(self, parent):
        Returns the number of rows under the given parent. When the parent is valid,
        this class returns 0.

    data(self, index, role):
        Returns the data stored under the given role for the item referred to by the index.

    addData(self, room):
        Adds a new RoomOccupation object to the list. It begins by signaling the
        selection_view about the upcoming change with beginInsertRows(), then performs the changes
        on its data, and ends by signaling the selection_view with endInsertRows().

    deleteData(self, row):
        Deletes a RoomOccupation object from the list at the specified row index.
        It follows the same pattern of signaling the selection_view about the changes.

    editData(self, row, new_room):
        Edits a RoomOccupation object at the specified row index with a new RoomOccupation object.
        After the change, it emits a dataChanged signal to update the selection_view.

    duplicateData(self, row):
        Duplicates a RoomOccupation object at the specified row index.
    """
    # THIS WORKS FINE SO NO NEED TO COPY THE CODE IN THIS FORUM THREAD
BSControllers.py
class ReservationController(QObject):
    def __init__(self, model, view):
        super().__init__()
        self.model = model
        self.view = view

        # self.selection_view.check_in_changed.connect(self.update_check_in)
        # self.selection_view.nights_count_changed.connect(self.update_nights_count)
        # self.selection_view.calendar_wgt.date_range_selected.connect(self.update_date_range)

        # Calendar specific
        self.click_tracker = 0
        self.from_date = None
        self.to_date = None
        self.view.calendar_wgt.clicked.connect(self.calendar_clicked)

        self.refresh_view()

    def refresh_view(self):
        self.view.checkin_fld.setDate(self.model.check_in_date)
        self.view.checkout_fld.setDate(self.model.check_out_date)
        self.view.nights_fld.setValue(self.model.nights_count)

        self.view.calendar_wgt.select_date_range(
            self.model.check_in_date,
            self.model.check_out_date
        )

    # @pyqtSlot(QDate)
    # def update_check_in(self, date):
    #     self.model.set_check_in(date)
    #     self.refresh_view()
    #
    # @pyqtSlot(int)
    # def update_nights_count(self, nights):
    #     self.model.set_nights_count(nights)
    #     self.refresh_view()
    #
    # @pyqtSlot(QDate, QDate)
    # def update_date_range(self, from_date, to_date):
    #     self.model.set_check_in(from_date)
    #     self.model.set_nights_count(from_date.daysTo(to_date))
    #     self.refresh_view()

    def update_check_in(self, date):
        self.model.set_check_in(date)
        self.refresh_view()

    def update_nights_count(self, nights):
        self.model.set_nights_count(nights)
        self.refresh_view()

    def update_date_range(self, from_date, to_date):
        self.model.set_check_in_and_nights(from_date, from_date.daysTo(to_date))
        self.refresh_view()

    def calendar_clicked(self, clicked_date):
        self.click_tracker += 1

        # Using 1-based count to avoid confusion
        # On the first click of a pair, set the "from_date" as the start; otherwise, to_date
        if self.click_tracker % 2 == 1:
            self.from_date = clicked_date
            self.to_date = None
        else:
            self.to_date = clicked_date

        # Only emit if both from_date and to_date are set (otherwise, it would pass None and raise a TypeError)
        if self.from_date is not None and self.to_date is not None:
            self.view.calendar_wgt.date_range_selected.emit(self.from_date, self.to_date)
main.py (entrypoint)
if __name__ == "__main__":
    app = QApplication(sys.argv)

    test_window = TestWindow()
    app.setStyle('fusion')
    test_window.show()

    sys.exit(app.exec_())
What the problem is
The commented chunks of code are my attempts to achieve the goal.
If I uncomment those, this exception occurs:
Error:
Traceback (most recent call last): File "C:\Users\franc\PycharmProjects\pythonProject\main.py", line 13, in <module> test_window = TestWindow() File "C:\Users\franc\PycharmProjects\pythonProject\BSWidgets.py", line 610, in __init__ stay_selector = BSStaySelectionView('default') File "C:\Users\franc\PycharmProjects\pythonProject\BSWidgets.py", line 515, in __init__ self.checkin_fld.dateChanged.connect(self.controller.update_check_in) AttributeError: 'NoneType' object has no attribute 'update_check_in'
I had to comment those sections in order to be able to do the screenshot.

What I have tried so far
As a preface, I must admit I relied on chatGPT to implement the model and controller class. As it used some constructs I am not familiar with, it is somehow messed up in a way I can't control.
I used MVC also on the other components on my UI and did it myself, and didn't experience all this problems.

Since it's a NoneType exception and controller is passed as default as None, I thought that was the problem. Therefore, I tried to workaround this in various way (initializing a set_controller() method within BSStaySelector, and others) but nothing seems to work.

I also tried to initialize the model and controller from within the class, but this isn't MVC-compliant imho because the view should be responsible only to present the data, no logic (right?). And anyway, it didn't work anyways (circular import).

It seems like there is an approach error in the project, and I can't figure our what.

I will greatly appreciate any help, and also general suggestions on how to implement MVC in a Python w/ PyQt5 project - all examples online are very basic, when it comes to real code it's much different.
Reply
#2
I did a simple example of using mvc and pyqt5. I hope it helps.

from PyQt5.QtWidgets import (QApplication, QWidget, QMainWindow, QLabel, 
                             QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit)
from PyQt5 import QtCore
import sys

class Model:
    '''
    Model class handles all data actions. Creates a empty list
    adds, removes, and returns data for display
    '''
    def __init__(self):
        ''' Create an empty list '''
        self.alist = []

    def add(self, arg):
        ''' Method uses append to add to our list '''
        self.alist.append(arg.strip())
        
    def remove(self, arg):
        ''' Checking if the value is in our list. If it is remove. '''
        if arg.strip() in self.alist:
            self.alist.remove(arg)
    
    def display(self):
        ''' Returns our list for display'''
        if len(self.alist) == 1:
            return self.alist[0]
        elif len(self.alist) > 1:
            return ', '.join(self.alist)
        else:
            return False

class View(QMainWindow):
    '''
    This is the main view window. All widgets are created and added to layout.
    This class is for visual/viewing only
    '''
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle('Test Widget for MVC')

        self.label = QLabel('Hello World!')
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)

        self.add_button = QPushButton('Add')
        self.del_button = QPushButton('Delete')
        self.clear_btn = QPushButton('Clear All')

        self.add_field = QLineEdit()
        self.del_field = QLineEdit()

        layout = QVBoxLayout()
        addbox = QHBoxLayout()
        delbox = QHBoxLayout()
        clearbox = QHBoxLayout()

        addbox.addWidget(self.add_button)
        addbox.addWidget(self.add_field)

        delbox.addWidget(self.del_button)
        delbox.addWidget(self.del_field)

        clearbox.addWidget(self.clear_btn)



        layout.addWidget(self.label)
        layout.addLayout(addbox)
        layout.addLayout(delbox)
        layout.addLayout(clearbox)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        
        self.show()


class Controller:
    '''
    Controller class handles all communications 
    between the Model and View classes.
    '''
    def __init__(self, model, view):
        ''' Set the model and view classes for access '''
        self.model = model
        self.view = view

        # Create actions for the buttons when pressed
        self.view.add_button.clicked.connect(self.add)
        self.view.del_button.clicked.connect(self.remove)
        self.view.clear_btn.clicked.connect(self.clear)

    def add(self):
        ''' 
            Method is called when adding items to the model list.
            arg is getting the values from our add field.
            If there is a values then self.model.add method from the 
            model class is called. We then clear the text field
            and call self.display to update the label with text
        '''
        arg = self.view.add_field.text().strip()
        if arg:
            self.model.add(arg)
            self.view.add_field.setText('')
            self.display()

    def remove(self):
        ''' 
            The remove method is the same as the add method but, removes the item
            from the model list
        '''
        arg = self.view.del_field.text().strip()
        if arg:
            self.model.remove(arg)
            self.view.del_field.setText('')
            self.display()

    def clear(self):
        ''' This method clears the model list '''
        self.model.alist = []
        self.display()

    def display(self):
        ''' Method for displaying text in the label '''
        view = self.model.display()
        self.view.label.setText(view if view else 'Hello World!')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    controller = Controller(Model(), View())
    sys.exit(app.exec_())
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#3
This code should definitely not be in the View.
        stay_selector = BSStaySelectionView('default')
        # stay_selector_model = StaySelectorModel()
        # stay_selector_controller = ReservationController(stay_selector_model, stay_selector)
        # stay_selector.set_controller(stay_selector_controller)
The controller is the glue between the View and the Model. The Model should not be designed to work with the View, and the View should not have to know anything about the Model. Maybe we are lucky and StaySelectorModel has an API that is compatible with BSStaySelectionView, but the View should not know of the existence of StaySelectorModel. Instead, the controller should tell stay_selector that it will be getting its information from some object. And that object might be the controller which has a special method that performs any gymnastics required to make the BBStaySelectionView compatible with the StaySelectorModel.

BSStaySelectionView should know nothing about StaySelectionModel. The controller should create a StaySelectionModel instance and somehow pass along this information to the stay_selector object. Either pass it as an argument when creating the instance or set it as an attribute after the instance is created. The BSStaySelectionView will need some way to limp along until a model is provided. It is Best if BBStaySelectionView has some way to limp along until a model is provided. Sans that, the controller, being the only thing that knows about the models and the view, should be the intermediary and provide a model interface to the view even if there is currently not a model.
Reply
#4
Thank you very much, your example clarified a lot!
(Aug-27-2023, 05:02 AM)menator01 Wrote: I did a simple example of using mvc and pyqt5. I hope it helps.

from PyQt5.QtWidgets import (QApplication, QWidget, QMainWindow, QLabel, 
                             QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit)
from PyQt5 import QtCore
import sys

class Model:
    '''
    Model class handles all data actions. Creates a empty list
    adds, removes, and returns data for display
    '''
    def __init__(self):
        ''' Create an empty list '''
        self.alist = []

    def add(self, arg):
        ''' Method uses append to add to our list '''
        self.alist.append(arg.strip())
        
    def remove(self, arg):
        ''' Checking if the value is in our list. If it is remove. '''
        if arg.strip() in self.alist:
            self.alist.remove(arg)
    
    def display(self):
        ''' Returns our list for display'''
        if len(self.alist) == 1:
            return self.alist[0]
        elif len(self.alist) > 1:
            return ', '.join(self.alist)
        else:
            return False

class View(QMainWindow):
    '''
    This is the main view window. All widgets are created and added to layout.
    This class is for visual/viewing only
    '''
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle('Test Widget for MVC')

        self.label = QLabel('Hello World!')
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter)

        self.add_button = QPushButton('Add')
        self.del_button = QPushButton('Delete')
        self.clear_btn = QPushButton('Clear All')

        self.add_field = QLineEdit()
        self.del_field = QLineEdit()

        layout = QVBoxLayout()
        addbox = QHBoxLayout()
        delbox = QHBoxLayout()
        clearbox = QHBoxLayout()

        addbox.addWidget(self.add_button)
        addbox.addWidget(self.add_field)

        delbox.addWidget(self.del_button)
        delbox.addWidget(self.del_field)

        clearbox.addWidget(self.clear_btn)



        layout.addWidget(self.label)
        layout.addLayout(addbox)
        layout.addLayout(delbox)
        layout.addLayout(clearbox)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        
        self.show()


class Controller:
    '''
    Controller class handles all communications 
    between the Model and View classes.
    '''
    def __init__(self, model, view):
        ''' Set the model and view classes for access '''
        self.model = model
        self.view = view

        # Create actions for the buttons when pressed
        self.view.add_button.clicked.connect(self.add)
        self.view.del_button.clicked.connect(self.remove)
        self.view.clear_btn.clicked.connect(self.clear)

    def add(self):
        ''' 
            Method is called when adding items to the model list.
            arg is getting the values from our add field.
            If there is a values then self.model.add method from the 
            model class is called. We then clear the text field
            and call self.display to update the label with text
        '''
        arg = self.view.add_field.text().strip()
        if arg:
            self.model.add(arg)
            self.view.add_field.setText('')
            self.display()

    def remove(self):
        ''' 
            The remove method is the same as the add method but, removes the item
            from the model list
        '''
        arg = self.view.del_field.text().strip()
        if arg:
            self.model.remove(arg)
            self.view.del_field.setText('')
            self.display()

    def clear(self):
        ''' This method clears the model list '''
        self.model.alist = []
        self.display()

    def display(self):
        ''' Method for displaying text in the label '''
        view = self.model.display()
        self.view.label.setText(view if view else 'Hello World!')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    controller = Controller(Model(), View())
    sys.exit(app.exec_())
Reply
#5
(Aug-28-2023, 04:37 PM)deanhystad Wrote: This code should definitely not be in the View.
        stay_selector = BSStaySelectionView('default')
        # stay_selector_model = StaySelectorModel()
        # stay_selector_controller = ReservationController(stay_selector_model, stay_selector)
        # stay_selector.set_controller(stay_selector_controller)
The controller is the glue between the View and the Model. The Model should not be designed to work with the View, and the View should not have to know anything about the Model. Maybe we are lucky and StaySelectorModel has an API that is compatible with BSStaySelectionView, but the View should not know of the existence of StaySelectorModel. Instead, the controller should tell stay_selector that it will be getting its information from some object. And that object might be the controller which has a special method that performs any gymnastics required to make the BBStaySelectionView compatible with the StaySelectorModel.

BSStaySelectionView should know nothing about StaySelectionModel. The controller should create a StaySelectionModel instance and somehow pass along this information to the stay_selector object. Either pass it as an argument when creating the instance or set it as an attribute after the instance is created. The BSStaySelectionView will need some way to limp along until a model is provided. It is Best if BBStaySelectionView has some way to limp along until a model is provided. Sans that, the controller, being the only thing that knows about the models and the view, should be the intermediary and provide a model interface to the view even if there is currently not a model.

(Aug-28-2023, 04:37 PM)deanhystad Wrote: BSStaySelectionView should know nothing about StaySelectionModel. The controller should create a StaySelectionModel instance and somehow pass along this information to the stay_selector object. Either pass it as an argument when creating the instance or set it as an attribute after the instance is created. The BSStaySelectionView will need some way to limp along until a model is provided. It is Best if BBStaySelectionView has some way to limp along until a model is provided. Sans that, the controller, being the only thing that knows about the models and the view, should be the intermediary and provide a model interface to the view even if there is currently not a model.

Thank you as well for clarifying the theory behind it.
What still thrives me is how to implement this approach on custom QWidgets.

In my case, I have a QMainWindow that includes several custom QWidgets. Each of these QWidget has a model and controller class. There should I initialize those? 1) Within the QMainWindow, when I initialize the custom widgets, 2) or somewhere else outside?

According to MVC I seem to understand that in the view (therefore: where I initialize the QWidget) should be only responsible for presentation, but if I must strictly respect this pattern I should first initialize the QWidget singularly and connect them to model and controller, then pass them as an argument to QMainWindow. This sounds to me like a mess, to be honest.

Perhaps sometimes design patterns have to be taken with a pinch of salt?

Any opinion?
Reply
#6
Quote:According to MVC I seem to understand that in the view (therefore: where I initialize the QWidget) should be only responsible for presentation, but if I must strictly respect this pattern I should first initialize the QWidget singularly and connect them to model and controller, then pass them as an argument to QMainWindow.
This is close to what you do to follow the MVC pattern. You make the view, all the QWidgets and the layouts and putting them in whatever windows you need. You make the model. Sometimes the model already exists, or at least it is defined. If you get to define the model you can skew the design to make it work more easily with the view. And finally, you make the controller. The controller is a mess, and a lot of work has gone into tools to help make the controller less of a mess. The controller tells the view how to send/retrieve information from the model. The controller tells the model how to push information to the view. The controller is a bit of mess, but it also cleans thing up considerably. Instead of having a hodge-podge of connections between views and models made all over the place, you have a view that only creates objects that display information and accept user input, the model that implements all the application logic, and one messy thing that has to know about both.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020