Skip to content
Michael W Warren, Sr edited this page Mar 21, 2023 · 32 revisions

As noted in the README.md, most of the GUI (reservationsView.py) code was initially written while following a YouTube tutorial provided by The CS Classroom (Intro To PySimpleGUI: An 8-Part Tutorial Series (with databases)). After completing tutorials 1-6, I decided to rewrite the tutorial code as an MVC (Model-View-Controller) design using Python classes to implement the reservations View, and using the peewee ORM to implement the Model.

The architecture for the Reservations Demo System in shown in Figure 1. The MVC architecture is implemented via three python modules:

  • reservationsView.py,
  • reservationsController.py, and
  • reservationsModel.py.

ReservationsDemoMVC png-1 Figure 1: Reservations Demo MVC Architecture


Figure 2 shows the UML diagrams for the Python modules and their associated classes. Python modules are presented as module stereotypes that show their imports, attributes, and methods. If a Python module contains classes, they are shown above the module via an association link (dashed line without arrows). The relationship between Python module classes is shown via standard UML connectors. Note that the reservationsView.py module imports all of reservationsController.py methods, so that they become available to all of the reservationsView.py classes. Likewise, the reservationsController.py module imports all of the reservationsModel.py methods.


Screenshot from 2023-03-18 15-58-22

Figure 2: UML Diagram for Reservations System Python Modules and Their Associated Classes, Attributes, and Methods


The reservations View (GUI) is implemented in the reservationsView.py module via three Python classes that utilize the PySimpleGUI package. These GUI windows display forms and tables for entering, displaying, and editing reservations data. The Python classes and corresponding windows are as follows:

  • class ReservationsSystem -- The window for creating reservations (Figure 3),
  • class ReservationsWindow -- The modal window for displaying and deleting reservations (Figure 4), and
  • class ReservationsEditWindow -- The modal window for changing reservations (Figure 5).

CreateReservationWindow

Figure 3: Create Reservation Window


DeleteEditReservation

Figure 4: Reservations Window


EditReservationWindow

Figure 5: Edit Reservations Window


Another major role of the View is to validate user input whenever a user creates or changes (updates) a reservation. If one or more errors are detected when a user clicks the Reserve Ticket button on the Flight Reservations System window or clicks the Save Reservation button on the Edit Reservation window, a pop-up error message lists all of the errors. When the message is dismissed, focus returns to the window so that the user can correct the error.

The Model is a Python module that uses the peewee ORM connected to a Sqlite database. It uses the peewee ORM classes/methods to create and manage Sqlite tables for the reservations database. It also implements low level CRUD methods for the reservations database.

The Controller is a Python module that connects the View and Model. The Controller provides high-level CRUD methods connecting the View to the Model. It converts the data retrieved from the Model to a format that is usable by the View. For example, the PySimpleGUI Tree widget, which is used to implement the table on the Reservations Window, requires data formatted as TreeData.

The Controller also updates the View whenever the Model has changed (i.e., an update or deletion). When the View sends a request to the Controller to delete or update a reservation, it passes a reference to the Reservations Window as one of the method parameters. Note the Controller deleteReservation and updateReservation methods used by the View use viewRef to update treedata and to refresh the Reservations Window contents:

* def deleteReservation(idval, viewRef=None) -> None:
    rm.deleteReservation(idval)
    # Update Reservations Window Tree
    if viewRef != None:
        viewRef.treedata = _retrieveReservationsTreeData()
        viewRef.refreshTree()
    return()
* def updateReservation(idVal, name, gender, passport_num, destination, departure_dt, arrival_dt, viewRef=None) -> None:
    rm.updateReservation(idVal, name, gender, passport_num, destination, departure_dt, arrival_dt)
    # Update Reservations Window Tree
    if viewRef != None:
        viewRef.treedata = _retrieveReservationsTreeData()
        viewRef.refreshTree()
    return()

This approach enables the Controller to update and refresh the the Reservations Window, and the View only exposes what the Controller needs to update the appropriate View window.

As noted in the README.md, I have not seen other examples on the Web of using PySimpleGUI within Python classes, but I found it a convenient way to develop python PySimpleGUI applications.

The code listed below provides a template Python class for a PySimpleGUI window.

PySimpleGUI uses nested lists to lay out its window elements. The layout code may include code that populates window elements (e.g., a listbox, table widget, or tree widget). Then you create the window passing in your custom layout as a parameter. Notice that a reference to sg.Window is captured the instance variable self._window, thereby making it available to all class methods. All layout and initialization code is placed in the class constructor or __init__() method.

The PySimpleGUI event loop processes window input (e.g., button presses, keyboard entry, table row selection, etc.). The event loop is typically an infinite while loop. One of the events often processed in this loop is pressing a SUBMIT or SAVE button. These events will often execute code to validate user input and to persist user input in a database. Users typically press an Exit button to break out of the event loop and close the window when they are finished using the window. The event loop code is placed in the public run() method.

Of course, any method can be called from the __init()__ constructor or from the public run() method. For example, note the use of the _clearEntries() method at the end of the 'Reserve Ticket' event in the public run() method. The _clearEntries() method demonstrates the advantage of capturing a reference to sg.Window in the self._window instance variable.

import PySimpleGUI as sg

# PySimpleGUI Python class template
class stubWindow:
    def __init__(self, theme='lightGreen'):
        
        self._themeStr = theme
        self._gender = ""
        self._destinations = []
        
        # Set GUI theme           
        sg.theme(self._themeStr)

        self._layout = [self._getFormLayout(), self._getButtonLayout()]

        self._window = sg.Window('Stub Window', self._layout, keep_on_top=True, finalize=False)


    def _getFormLayout(self) -> list:
		# Retrieval of screen values goes here
        self._destinations = ['New York', 'London', 'Tokyo', 'Madrid', 'Paris', 'Brussels']
        
        layout = [
            [sg.Text("Full name:"), sg.Input(key='-Name-', do_not_clear=True, size=(20,1))],
            [sg.Text("Passport number:"), sg.Input(key='-Passport_Number-', do_not_clear=True, size=(10,1))],
            [sg.Radio("Male", "RADIO", key='-Male-'), sg.Radio("Female", 'RADIO', key='-Female-')],
            [sg.Input(key='-Departure-', size=(20,1)), sg.CalendarButton("Date of Departure", close_when_date_chosen=True, target='-Departure-', no_titlebar=False)],
            [sg.Input(key='-Arrival-', size=(20,1)), sg.CalendarButton("Date of Arrival", close_when_date_chosen=True, target='-Arrival-', no_titlebar=False)],
            [sg.Text("Select A Destination:")],
            [sg.Listbox(values=self._destinations, key='-Destination-', size=(40,5), select_mode="single")]
        ]
        return(layout)
    
    
    def _getButtonLayout(self) -> list:
        layout = [
            [sg.Button("Reserve Ticket"), sg.Button('See Reservations'), sg.Exit()]
        ]
        return(layout)
    
    def _clearEntries(self) -> None:
        self._window['-Name-'].update(value="")
        self._window['-Passport_Number-'].update(value="")
        self._window['-Male-'].update(value=False)
        self._window['-Female-'].update(value=False)
        self._window['-Departure-'].update(value="")
        self._window['-Arrival-'].update(value="")
        self._window['-Destination-'].update(set_to_index=[])
        self._window['-Name-'].set_focus(force=True)
        
        
    def run(self) -> None:
        # Process events
        while True:      
            event, values = self._window.Read() 
            print(event, values)
            if event in ('Exit', sg.WIN_CLOSED):
                break
            elif event == 'Reserve Ticket':
		# Validation processing goes here
		# Database persistence goes here
                self._clearEntries()  # Clear screen after successful db CREATE
                continue
            elif event == 'Display Screen':
		# Display new screen here
                continue
            else:
                continue

        self._window.close()
        
        
def main():
    
    App = stubWindow('DarkAmber')
    App.run()


if __name__ == '__main__':
	main()

A clear advantage of using PySimpleGUI within Python classes is that any GUI window that you develop this way can be reused via inheritance. If you look at the classes included in the reservationsView.py module, you will see that the class ReservationsEditWindow(ReservationsSystem) is inherited from class ReservationsSystem, and that much less code is required to implement the edit window because it inherits all the layout and validation code from the parent class.

REFERENCES:

  • Everything you need to know about PySimpleGUI can be found here PySimpleGUI.

  • Everything you need to know about peewee can be found here peewee.

  • The UML diagrams shown here were generated with the Pro Version of Pynsource.

Clone this wiki locally