0
votes

I have a small test application using Python tkinter that I got to work. The problem is that it does not exit properly when the "Quit" button is pressed. It is a two-frame tabbed application where I started with the StackOverflow question ttk tkinter multiple frames/windows.

It is now a full example that works, but needs work because it doesn't quit and exit properly. When I press the "Quit" button, it kills the frame for that tab, but the application doesn't quit and exit properly. I have to hit the Window "X" Close icon to close it.

My main question is how (and where?) do I test for the event on either the "Quit" button on the "Feet to Meters" calculator, or the "Cancel/Quit" button on the BMI calculator.

A second question I have is that the design of the application seems inefficient to me, because it creates two widgets "Frame" objects, each with their own set of buttons, including 2 "quit" buttons. How do I put these tabs and frames into a parent window and then add a quit button on that parent window to close the entire application.

I modified the buttons to properly destroy the Frame that the button is in:

Changed button2 "command=self.quit" to "command=self.destroy"

self.button2 = ttk.Button(self, text="Cancel/Quit", command=self.quit).grid(row=3, column=1, sticky=E)

to self.button2 = ttk.Button(self, text="Cancel/Quit", command=self.destroy).grid(row=3, column=1, sticky=E)

""" Created on Thu Jul 11 17:20:22 2019 """

from tkinter import *
from tkinter import ttk
from tkinter import messagebox

class App1(ttk.Frame):
    """ This application calculates BMI and returns a value. """ 

    def __init__(self, master=None):
        ttk.Frame.__init__(self, master)
        self.grid()
        self.createWidgets()

    def createWidgets(self):
        #text variables
        self.i_height = StringVar()
        self.i_weight = StringVar()
        self.o_bmi = StringVar()

        #labels
        self.label1 = ttk.Label(self, text="Enter your weight:").grid(row=0, column=0, sticky=W)
        self.label2 = ttk.Label(self, text="Enter your height:").grid(row=1, column=0, sticky=W)
        self.label3 = ttk.Label(self, text="Your BMI is:").grid(row=2, column=0, sticky=W)

        #text boxes
        self.textbox1 = ttk.Entry(self, textvariable=self.i_weight).grid(row=0, column=1, sticky=E)
        self.textbox2 = ttk.Entry(self, textvariable=self.i_height).grid(row=1, column=1, sticky=E)
        self.textbox3 = ttk.Entry(self, textvariable=self.o_bmi).grid(row=2, column=1, sticky=E)

        #buttons
        self.button1 = ttk.Button(self, text="Ok", command=self.calculateBmi).grid(row=3, column=2, sticky=E)
## Changed button2 "command=self.quit"  to  "command=self.destroy"
#        self.button2 = ttk.Button(self, text="Cancel/Quit", command=self.quit).grid(row=3, column=1, sticky=E)
        self.button2 = ttk.Button(self, text="Cancel/Quit", command=self.destroy).grid(row=3, column=1, sticky=E)

#exitApplication = tk.Button(root, text='Exit Application', command=root.destroy)
#canvas1.create_window(85, 300, window=exitApplication) 


    def calculateBmi(self):
        try:
            self.weight = float(self.i_weight.get())
            self.height = float(self.i_height.get())
            self.bmi = self.weight / self.height ** 2.0
            self.o_bmi.set(self.bmi)
        except ValueError:
            messagebox.showinfo("Error", "You can only use numbers.")
        finally:
            self.i_weight.set("")
            self.i_height.set("")

class App2(ttk.Frame):
    """ Application to convert feet to meters or vice versa. """
    def __init__(self, master=None):
        ttk.Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        """Create the widgets for the GUI"""
        # 1 textbox (stringvar)
        self.entry= StringVar()
        self.textBox1= ttk.Entry(self, textvariable=self.entry).grid(row=0, column=1)

        # 5 labels (3 static, 1 stringvar)
        self.displayLabel1 = ttk.Label(self, text="feet").grid(row=0, column=2, sticky=W)
        self.displayLabel2 = ttk.Label(self, text="is equivalent to:").grid(row=1, column=0)
        self.result= StringVar()
        self.displayLabel3 = ttk.Label(self, textvariable=self.result).grid(row=1, column=1)
        self.displayLabel4 = ttk.Label(self, text="meters").grid(row=1, column=2, sticky=W)

        # 2 buttons
        self.calculateButton = ttk.Button(self, text="Calculate", command=self.convert_feet_to_meters).grid(row=2, column=2, sticky=(S,E))
        self.quitButton = ttk.Button(self, text="Quit", command=self.destroy).grid(row=2, column=1, sticky=(S,E))

#exitApplication = tk.Button(root, text='Exit Application', command=root.destroy)
#canvas1.create_window(85, 300, window=exitApplication) 


    def convert_feet_to_meters(self):
        """Converts feet to meters, uses string vars and converts them to floats"""
        self.measurement = float(self.entry.get())
        self.meters = self.measurement * 0.3048
        self.result.set(self.meters)

###  CODE BELOW COMMENTED OUT WHEN JOINING ORIGINAL POSTER CODE WITH HIS SOLUTION
### It seems no longer relevant since App1 and App2 have their own buttons.

#def button1_click():
#    """ This is for the BMI Calculator Widget """
#    root = Tk()
#    app = App1(master=root)
#    app.mainloop()
#
#def button2_click():
#    """ This is for the Feet to Meters Conversion Widget """
#    root = Tk()
#    app = App2(master=root)
#    app.mainloop()

#def main():
#    window = Tk()
#    button1 = ttk.Button(window, text="bmi calc", command=button1_click).grid(row=0, column=1)
#    button2 = ttk.Button(window, text="feet conv", command=button2_click).grid(row=1, column=1)
#    window.mainloop()


def main():
    #Setup Tk()
    window = Tk()

    #Setup the notebook (tabs)
    notebook = ttk.Notebook(window)
    frame1 = ttk.Frame(notebook)
    frame2 = ttk.Frame(notebook)
    notebook.add(frame1, text="BMI Calc")
    notebook.add(frame2, text="Feet to Meters")
    notebook.grid()

    #Create tab frames
    app1 = App1(master=frame1)
    app1.grid()
    app2 = App2(master=frame2)
    app2.grid()

    #Main loop
    window.mainloop()

if __name__ == '__main__':
    main()

The application doesn't quit when the "Quit" button is pressed. Only the individual frames quit.

1
Have you tried calling quit or destroy on the main window, since that's what you want to destroy?Bryan Oakley
Yes. That worked. Thank you for the hint. In order to get this to work, and be efficient, I declared 'window as a global variable, since it was defined in the name space of the class constructors. Without that, there was an error raised of undefined window. The final working code is posted below.Rich Lysakowski PhD
Not sure why someone gave me a downvote on the question, which removed reputation points. If someone (perhaps the down-voter) could provide clarification, I would appreciate it, so that I don't make the same mistake again. Thank you.Rich Lysakowski PhD

1 Answers

1
votes

Thanks to Martineau for the hint that helped me get this example to work. I declared 'window as a global variable, since it was defined in the name space of the class constructors. Without that, there was an error raised of undefined window. This method breaks the encapsulation and modularity of the classes by passing in the window as a global. If there is a better way to do this, I would like to know.

    # -*- coding: utf-8 -*-
""" Created on Thu Jul 11 17:20:22 2019   
    Filename: tkinter-multiple-frames-windows_v3.py
    From question on StackOverflow question "ttk tkinter multiple frames/windows"
    at https://stackguides.com/questions/6035101/ttk-tkinter-multiple-frames-windows?rq=1
    Now a full example that works but it needed some modification to clarify how it works.
"""

from tkinter import *
from tkinter import ttk
from tkinter import messagebox

class BMICalcApp(ttk.Frame):
    """ This application calculates BMI and returns a value. """ 

    def __init__(self, master=None):
        ttk.Frame.__init__(self, master)
        self.grid()
        self.createWidgets()

    def createWidgets(self):
        #text variables
        self.i_height = StringVar()
        self.i_weight = StringVar()
        self.o_bmi = StringVar()

        #labels
        self.label1 = ttk.Label(self, text="Enter your weight:").grid(row=0, column=0, sticky=W)
        self.label2 = ttk.Label(self, text="Enter your height:").grid(row=1, column=0, sticky=W)
        self.label3 = ttk.Label(self, text="Your BMI is:").grid(row=2, column=0, sticky=W)

        #text boxes
        self.textbox1 = ttk.Entry(self, textvariable=self.i_weight).grid(row=0, column=1, sticky=E)
        self.textbox2 = ttk.Entry(self, textvariable=self.i_height).grid(row=1, column=1, sticky=E)
        self.textbox3 = ttk.Entry(self, textvariable=self.o_bmi).grid(row=2, column=1, sticky=E)

        #buttons
        self.button1 = ttk.Button(self, text="Ok", command=self.calculateBmi).grid(row=3, column=2, sticky=E)
        self.button2 = ttk.Button(self, text="Cancel/Quit", command=window.destroy).grid(row=3, column=1, sticky=E)

    def calculateBmi(self):
        try:
            self.weight = float(self.i_weight.get())
            self.height = float(self.i_height.get())
            self.bmi = self.weight / self.height ** 2.0
            self.o_bmi.set(self.bmi)
        except ValueError:
            messagebox.showinfo("Error", "You can only use numbers.")
        finally:
            self.i_weight.set("")
            self.i_height.set("")

class ConvertFeetMeters(ttk.Frame):
    """ Application to convert feet to meters or vice versa. """
    def __init__(self, master=None):
        ttk.Frame.__init__(self, master)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        """Create the widgets for the GUI"""
        # 1 textbox (stringvar)
        self.entry= StringVar()
        self.textBox1= ttk.Entry(self, textvariable=self.entry).grid(row=0, column=1)

        # 5 labels (3 static, 1 stringvar)
        self.displayLabel1 = ttk.Label(self, text="feet").grid(row=0, column=2, sticky=W)
        self.displayLabel2 = ttk.Label(self, text="is equivalent to:").grid(row=1, column=0)
        self.result= StringVar()
        self.displayLabel3 = ttk.Label(self, textvariable=self.result).grid(row=1, column=1)
        self.displayLabel4 = ttk.Label(self, text="meters").grid(row=1, column=2, sticky=W)

        # 2 buttons
        self.calculateButton = ttk.Button(self, text="Calculate", command=self.convert_feet_to_meters).grid(row=2, column=2, sticky=(S,E))
        self.quitButton = ttk.Button(self, text="Quit", command=window.destroy).grid(row=2, column=1, sticky=(S,E))

    def convert_feet_to_meters(self):
        """Converts feet to meters, uses string vars and converts them to floats"""
        self.measurement = float(self.entry.get())
        self.meters = self.measurement * 0.3048
        self.result.set(self.meters)


def main():
    #Setup Tk()
    global window 
    window = Tk()

    #Setup the notebook (tabs)
    notebook = ttk.Notebook(window)
    frame1 = ttk.Frame(notebook)
    frame2 = ttk.Frame(notebook)
    notebook.add(frame1, text="BMI Calc")
    notebook.add(frame2, text="Feet to Meters")
    notebook.grid()

    #Create tab frames
    bmi_calc = BMICalcApp(master=frame1)
    bmi_calc.grid()
    feet_meters_calc = ConvertFeetMeters(master=frame2)
    feet_meters_calc.grid()

    #Main loop
    window.mainloop()

if __name__ == '__main__':
    main()