CSE 111: Programming with Functions

Sample Solution

The following shows one way to solve this problem. It is not the only way this problem can be solved.

code_along_solution.py


# Copyright 2020, Brigham Young University-Idaho. All rights reserved.

import math
import tkinter as tk
from tkinter import Frame, Label, Button
from number_entry import IntEntry, FloatEntry


def main():
    # Create the Tk root object.
    root = tk.Tk()

    # Create the main window. In tkinter,
    # a window is also called a frame.
    frm_main = Frame(root)
    frm_main.master.title("Tire Volume")
    frm_main.pack(padx=4, pady=3, fill=tk.BOTH, expand=1)

    # Call the populate_main_window function, which will add
    # labels, text entry boxes, and buttons to the main window.
    populate_main_window(frm_main)

    # Start the tkinter loop that processes user events
    # such as key presses and mouse button clicks.
    root.mainloop()


# The controls in a graphical user interface (GUI) are called widgets,
# and each widget is an object. Because a GUI has many widgets and
# each widget is an object, the code to make a GUI usually has many
# variables to store the many objects. Because there are so many
# variable names, programmers often adopt a naming convention to help
# a programmer keep track of all the variables. One popular naming
# convention is to type a three letter prefix in front of the names
# of all variables that store GUI widgets, according to this list:
#
# frm: a frame (window) widget
# lbl: a label widget that displays text for the user to see
# ent: an entry widget where a user will type text or numbers
# btn: a button widget that the user will click


def populate_main_window(frm_main):
    """Populate the main window of this program. In other words, put
    the labels, text entry boxes, and buttons into the main window.

    Parameter
        frm_main: the main frame (window)
    Return: nothing
    """
    # Create labels for the number entries and the result.
    lbl_width = Label(frm_main, text="Width (80 - 300):")
    lbl_ratio = Label(frm_main, text="Aspect Ratio (30 - 90):")
    lbl_diam = Label(frm_main, text="Diameter (7 - 30):")
    lbl_volume = Label(frm_main, text="Volume:")

    # Create three number entries.
    ent_width = IntEntry(frm_main, width=5, lower_bound=80, upper_bound=300)
    ent_ratio = IntEntry(frm_main, width=5, lower_bound=30, upper_bound=90)
    ent_diam = FloatEntry(frm_main, width=5, lower_bound=7, upper_bound=30)

    # Create a label to display the result.
    txt_volume = Label(frm_main, width=5, anchor="e")

    # Create labels to display the units.
    lbl_width_units = Label(frm_main, text="millimeters")
    # Ratios don't have units
    lbl_diam_units = Label(frm_main, text="inches")
    lbl_vol_units = Label(frm_main, text="liters")

    # Create the Clear button.
    btn_clear = Button(frm_main, text="Clear")

    # Layout all the labels, number entries, and buttons in a grid.
    lbl_width.grid(      row=0, column=0, padx=3, pady=2, sticky="e")
    ent_width.grid(      row=0, column=1, padx=3, pady=2, sticky="w")
    lbl_width_units.grid(row=0, column=2, padx=0, pady=2, sticky="w")

    lbl_ratio.grid(     row=1, column=0, padx=3, pady=2, sticky="e")
    ent_ratio.grid(     row=1, column=1, padx=3, pady=2, sticky="w")
    # Ratios don't have units.

    lbl_diam.grid(      row=2, column=0, padx=3, pady=2, sticky="e")
    ent_diam.grid(      row=2, column=1, padx=3, pady=2, sticky="w")
    lbl_diam_units.grid(row=2, column=2, padx=0, pady=2, sticky="w")

    lbl_volume.grid(   row=3, column=0, padx=3, pady=2, sticky="e")
    txt_volume.grid(   row=3, column=1, padx=3, pady=2, sticky="w")
    lbl_vol_units.grid(row=3, column=2, padx=0, pady=2, sticky="w")
    btn_clear.grid(    row=3, column=3, padx=3, pady=2)


    # This function is called each time the user releases a key.
    def calculate(event):
        """Compute the approximate volume of a tire in liters."""
        try:
            # Get the user input.
            w = ent_width.get()
            a = ent_ratio.get()
            d = ent_diam.get()

            # Compute the tire volume in liters.
            v = (math.pi * w * w * a * (w * a + 2540 * d)) / 10_000_000_000

            # Display the volume rounded to one digit
            # after the decimal for the user to see.
            txt_volume.config(text=f"{v:.2f}")

        except ValueError:
            # When the user deletes all the digits in one
            # of the number entries, clear the result.
            txt_volume.config(text="")


    # This function is called each time
    # the user clicks the "Clear" button.
    def clear():
        """Clear all the inputs and outputs."""
        btn_clear.focus()
        ent_width.clear()
        ent_ratio.clear()
        ent_diam.clear()
        txt_volume.config(text="")
        ent_width.focus()


    # Bind the calculate function to the three number
    # entries so that the calculate function will be called
    # when the user changes the text in the number entries.
    ent_width.bind("", calculate)
    ent_ratio.bind("", calculate)
    ent_diam.bind("", calculate)

    # Bind the clear function to the clear button so
    # that the clear function will be called when the
    # user clicks the clear button.
    btn_clear.config(command=clear)

    # Give the keyboard focus to the width text field.
    ent_width.focus()


# If this file is executed like this:
# > python teach_solution.py
# then call the main function. However, if this file is simply
# imported (e.g. into a test file), then skip the call to main.
if __name__ == "__main__":
    main()

Download: code_along_solution.py