Home > Back-end >  Closing subwindow with parent window on tkinter
Closing subwindow with parent window on tkinter

Time:11-11

I am relatively new to tkinter (and OOP), and am trying to make a gui with a second window for preferences. If I close them in reverse order there are no issues, but I am trying to make them able to be closed out of order (so that closing the main window closes the subwindow).

I have tried binding a simple function that closes the window, if it exists, upon destruction of the parent, although it seems inconsistent. Sometimes it will close the subwindow, sometimes it freezes and I have to close the kernel. I am unsure as to the cause of the freezing, as it seems to happen after the closing of the subwindow. As a quick note in the full code I'm using tkmacosx to change the background of the button when the mouse hovers over it.

Here is a subset of my code for a working example, there are likely other issues as well. There are a few additional things from my testing (such as binding destroying the subwindow to the return key and printing within the function)

import tkinter as tk
from tkinter import ttk
import tkinter.filedialog as fd
import os
from tkmacosx import Button
from tkmacosx import Marquee

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.mainframe = tk.Frame(parent)
        self.mainframe.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.mainframe.columnconfigure(0, weight=1)
        self.mainframe.rowconfigure(0, weight=1)
        self.create_widgets()
        self.parent.update()
        self.window = None
        self.parent.geometry(str(self.parent.winfo_width()) "x" str(self.parent.winfo_height()) \
                                " " str((self.parent.winfo_screenwidth()-self.parent.winfo_width())//2)\
                                " " str(int((self.parent.winfo_screenheight()-self.parent.winfo_height())*.4)))
        self.parent.bind("<Destroy>", self.Destroy_subwindow)
        self.parent.bind('<Return>', self.Destroy_subwindow)
    def Destroy_subwindow(self, event):
        if self.window is not None:
            print("closing subwindow")
            self.window.destroy()
    
    def create_widgets(self):
        self.filepath = ttk.Entry(self.mainframe, width=10)
        self.filepath.grid(column = 1, row = 0)
        ttk.Label(self.mainframe, text="Input Directory:").grid(column=0, row=0, sticky=tk.W)
 
        # Preferences        
        self.advOptions = Button(self.mainframe, text = "Preferences", command = self.Preferences,\
                        borderless = 1,highlightthickness = 0, width = 90, \
                        overbackground = '#227afe',activebackground = ('#227afe','#0b60ff'),\
                        activeforeground = "white", overforeground = 'white')
        self.advOptions.bind('<Return>', self.Preferences)
        self.advOptions.grid(column = 3, row = 1, sticky = tk.E)

    def Preferences(self, event = None):
        self.window = tk.Toplevel(root)
        self.window.title("Preferences")
        self.style = ttk.Style(self.window)
        self.style.theme_settings( "aqua",  settings={
                "TNotebook.Tab": {
                    "configure": {"padding": [5, 1], "background": "#e7e7e9","foreground": "black" },
                    "map":       {"background": [("selected", "#cfcfd1")], "foreground":[("selected", "black")],\
                                  "expand": [("selected", [1, 1, 1, 0])] } } } )
        self.tabcontrol = ttk.Notebook(self.window)
        self.tab1 = ttk.Frame(self.window)
        self.tabcontrol.add(self.tab1, text = "Plot")

root = tk.Tk()
root.title("Kinetic Plotting")
MainApplication(root)
root.mainloop()

And here is the full code if needed:

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.mainframe = tk.Frame(parent)
        self.mainframe.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.mainframe.columnconfigure(0, weight=1)
        self.mainframe.rowconfigure(0, weight=1)
        self.create_widgets()
        self.parent.update()
        self.window = None
        self.parent.geometry(str(self.parent.winfo_width()) "x" str(self.parent.winfo_height()) \
                                " " str((self.parent.winfo_screenwidth()-self.parent.winfo_width())//2)\
                                " " str(int((self.parent.winfo_screenheight()-self.parent.winfo_height())*.4)))
        self.parent.bind("<Destroy>", self.Destroy_subwindow)
        self.parent.bind('<Return>', self.Destroy_subwindow)
    def Destroy_subwindow(self, event):
        if self.window is not None:
            print("closing subwindow")
            self.window.destroy()
    
    def create_widgets(self):
        global tempdir, fittingEquation, saveFileType
        # Directory input
        self.filepath = ttk.Entry(self.mainframe, width=10)
        self.filepath.grid(column = 1, row = 0)
        ttk.Label(self.mainframe, text="Input Directory:").grid(column=0, row=0, sticky=tk.W)
        self.browseButton = Button(self.mainframe, text = "Browse", command = self.Browse_files,\
                        borderless = 1,highlightthickness = 0, width = 80, \
                        overbackground = '#227afe',activebackground = ('#227afe','#0b60ff'),\
                        activeforeground = "white", overforeground = 'white')
        self.browseButton.bind('<Return>', self.Browse_files)
        self.browseButton.grid(column = 3, row = 0, sticky = tk.E)
 
        # Preferences        
        self.advOptions = Button(self.mainframe, text = "Preferences", command = self.Preferences,\
                        borderless = 1,highlightthickness = 0, width = 90, \
                        overbackground = '#227afe',activebackground = ('#227afe','#0b60ff'),\
                        activeforeground = "white", overforeground = 'white')
        self.advOptions.bind('<Return>', self.Preferences)
        self.advOptions.grid(column = 3, row = 1, sticky = tk.E)

        # Image type dropdown        
        self.imageOptions = [".jpg", ".png", ".tiff"]
        self.var2 = tk.StringVar(self.mainframe)
        self.var2.set(self.imageOptions[0]) # initial value
        saveFileType = self.imageOptions[0]
        self.imageOption = tk.OptionMenu(self.mainframe, self.var2, *self.imageOptions, command = self.get_savetype)
        self.imageOption.grid(columnspan = 2,row = 1, sticky = tk.W)

        # Fitting Equation dropdown        
        self.Options = ["Single Exponential", "Double Exponential", "Control (Flat)"]
        self.var = tk.StringVar(self.mainframe)
        self.var.set(self.Options[0]) # initial value
        fittingEquation = self.Options[0]
        self.option = tk.OptionMenu(self.mainframe, self.var, *self.Options, command = self.get_variable)
        self.option.grid(columnspan = 2,row = 2, sticky = tk.W)

    def set_text(self, text):
        self.filepath.delete(0,tk.END)
        self.filepath.insert(0,text)

    def Browse_files(self):
        global tempdir
        self.currdir = os.getcwd()
        self.tempdir = fd.askdirectory(parent=root, initialdir=self.currdir, title='Please select a directory')
        self.set_text(self.tempdir)
        tempdir = self.tempdir

    def get_variable(self, var):
        global fittingEquation
        fittingEquation = self.var.get()
        
    def get_savetype(self, var2):
        global saveFileType
        saveFileType = self.var2.get()
    def PlotOptionsWidgets(self):
        self.tab1.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.tab1.columnconfigure(0, weight=1)
        self.tab1.rowconfigure(0, weight=1)
        
        # Errorbar checkbox
        self.errorvar = tk.IntVar(value = 1)
        self.checkerror = tk.Checkbutton(self.tab1, text = "Errorbars (only plotting)", variable = self.errorvar, \
                         onvalue = 1, offvalue = 0,bg = "#e4e4e4")
        self.checkerror.grid(column = 0, row = 0, columnspan = 5)
        
        # Column Labels  
        ttk.Label(self.tab1, text = "Linear Plots (kobs vs conc)").grid(column = 3, row=1, columnspan = 2)
        ttk.Label(self.tab1, text = "Kinetic Traces (F vs time)").grid(column = 0, row=1, columnspan = 2)

        # Figure Sizes
        self.linearFigureSize = tk.StringVar(value = "2.45, 1")
        self.linearFigureSizeEntry = ttk.Entry(self.tab1,textvariable =self.linearFigureSize,  width=10)
        self.linearFigureSizeEntry.grid(column = 4, row = 2, sticky = tk.W)
        ttk.Label(self.tab1, text="Figure Size (in, in)").grid(column=3, row=2, sticky=tk.E)
        self.KineticFigureSize = tk.StringVar(value = "2.49, 2.32")
        self.KineticFigureSizeEntry = ttk.Entry(self.tab1,textvariable =self.KineticFigureSize,  width=10)
        self.KineticFigureSizeEntry.grid(column = 1, row = 2, sticky = tk.W)
        ttk.Label(self.tab1, text="Figure Size (in, in)").grid(column=0, row=2, sticky=tk.E)

        # Axes limits        
        self.leftAxisLimit = tk.StringVar(value = "Auto")
        self.leftAxisLimitentry = ttk.Entry(self.tab1,textvariable =self.leftAxisLimit,  width=10)
        self.leftAxisLimitentry.grid(column = 1, row = 5, sticky = tk.W)
        ttk.Label(self.tab1, text="Left x limit (time)").grid(column=0, row=5, sticky=tk.E)
        self.rightAxisLimit = tk.StringVar(value = "Auto")
        self.rightAxisLimitentry = ttk.Entry(self.tab1,textvariable =self.rightAxisLimit,  width=10)
        self.rightAxisLimitentry.grid(column = 1, row = 6, sticky = tk.W)
        ttk.Label(self.tab1, text="Right x limit (time)").grid(column=0, row=6, sticky=tk.E)
        self.leftAxisLimitConc = tk.StringVar(value = "Auto")
        self.leftAxisLimitConcentry = ttk.Entry(self.tab1,textvariable =self.leftAxisLimitConc,  width=10)
        self.leftAxisLimitConcentry.grid(column = 4, row = 5, sticky = tk.W)
        ttk.Label(self.tab1, text="Left x limit (conc.)").grid(column=3, row=5, sticky=tk.E)
        self.rightAxisLimitConc = tk.StringVar(value = "Auto")
        self.rightAxisLimitConcentry = ttk.Entry(self.tab1,textvariable =self.rightAxisLimitConc,  width=10)
        self.rightAxisLimitConcentry.grid(column = 4, row = 6, sticky = tk.W)
        ttk.Label(self.tab1, text="Right x limit (conc.)").grid(column=3, row=6, sticky=tk.E)
        
        # Axes titles        
        self.xAxisTitle = tk.StringVar(value = "Time (s)")
        self.xAxisTitleEntry = ttk.Entry(self.tab1,textvariable =self.xAxisTitle,  width=10)
        self.xAxisTitleEntry.grid(column = 1, row = 3, sticky = tk.W)
        ttk.Label(self.tab1, text="x-Axis Label").grid(column=0, row=3, sticky=tk.E)
        self.yAxisTitle = tk.StringVar(value = "Rel. Emission Intensity")
        self.yAxisTitleEntry = ttk.Entry(self.tab1,textvariable =self.yAxisTitle,  width=10)
        self.yAxisTitleEntry.grid(column = 1, row = 4, sticky = tk.W)
        ttk.Label(self.tab1, text="y-Axis Label").grid(column=0, row=4, sticky=tk.E)
        self.xAxisTitleLinear = tk.StringVar(value = "[Ion] (Units)")
        self.xAxisTitleLinearEntry = ttk.Entry(self.tab1,textvariable =self.xAxisTitleLinear,  width=10)
        self.xAxisTitleLinearEntry.grid(column = 4, row = 3, sticky = tk.W)
        ttk.Label(self.tab1, text="x-Axis Label").grid(column=3, row=3, sticky=tk.E)
        self.yAxisTitleLinear = tk.StringVar(value = "kobs (Units)")
        self.yAxisTitleLinearEntry = ttk.Entry(self.tab1,textvariable =self.yAxisTitleLinear,  width=10)
        self.yAxisTitleLinearEntry.grid(column = 4, row = 4, sticky = tk.W)
        ttk.Label(self.tab1, text="y-Axis Label").grid(column=3, row=4, sticky=tk.E)
        
        # Font options
        self.LinearFont = tk.StringVar(value = "Arial")
        self.LinearFontEntry = ttk.Entry(self.tab1, textvariable = self.LinearFont, width = 10)
        self.LinearFontEntry.grid(column = 4, row = 7, sticky=tk.W)
        ttk.Label(self.tab1, text = "Font Style").grid(column = 3, row = 7, sticky=tk.E)
        self.LinearFontSize = tk.DoubleVar(value = 10)
        self.LinearFontSizeEntry = ttk.Entry(self.tab1, textvariable = self.LinearFontSize, width = 10)
        self.LinearFontSizeEntry.grid(column = 4, row = 8, sticky=tk.W)
        ttk.Label(self.tab1, text = "Font Size").grid(column = 3, row = 8, sticky=tk.E)
        self.ExponentialFont = tk.StringVar(value = "Arial")
        self.ExponentialFontEntry = ttk.Entry(self.tab1, textvariable = self.ExponentialFont, width = 10)
        self.ExponentialFontEntry.grid(column = 1, row = 7, sticky=tk.W)
        ttk.Label(self.tab1, text = "Font Style").grid(column = 0, row = 7, sticky=tk.E)
        self.ExponentialFontSize = tk.DoubleVar(value = 10)
        self.ExponentialFontSizeEntry = ttk.Entry(self.tab1, textvariable = self.ExponentialFontSize, width = 10)
        self.ExponentialFontSizeEntry.grid(column = 1, row = 8, sticky=tk.W)
        ttk.Label(self.tab1, text = "Font Size").grid(column = 0, row = 8, sticky=tk.E)


    def RegressionOptionsWidgets(self):
        self.tab2.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.tab2.columnconfigure(0, weight=1)
        self.tab2.rowconfigure(0, weight=1)
        
        # Dead time        
        self.deadTime= tk.DoubleVar(value = 0.01)
        self.deadTimeEntry = ttk.Entry(self.tab2, textvariable = self.deadTime, width = 10)
        self.deadTimeEntry.grid(column = 3, row = 4, sticky = tk.W)
        ttk.Label(self.tab2, text = "Dead Time (s)").grid(column = 0,row = 4, sticky = tk.E, columnspan = 3)
        
        # Weighting
        self.weightExp = tk.IntVar(value = 1)
        self.checkWeightExp = tk.Checkbutton(self.tab2, text = "Weight Exponential Fit", variable = self.weightExp, \
                         onvalue = 1, offvalue = 0,bg = "#e4e4e4")
        self.checkWeightExp.grid(column = 0, row = 0, columnspan = 5, sticky = tk.W)
        self.weightLinear = tk.IntVar(value = 1)
        self.checkWeightLinear = tk.Checkbutton(self.tab2, text = "Weight Linear Fit", variable = self.weightLinear, \
                         onvalue = 1, offvalue = 0,bg = "#e4e4e4")
        self.checkWeightLinear.grid(column = 0, row = 1, columnspan = 5, sticky = tk.W)
        
        # Biasing
        self.outliers = tk.IntVar(value = 0)
        self.checkOutliers = tk.Checkbutton(self.tab2, text = "Check for Outliers", variable = self.outliers, \
                         onvalue = 1, offvalue = 0,bg = "#e4e4e4")
        self.checkOutliers.grid(column = 0, row = 2, columnspan = 5, sticky = tk.W)
        self.intialParam = tk.IntVar(value = 1)
        self.checkInitialParam = tk.Checkbutton(self.tab2, text = "Calculate Initial Parameters", variable = self.intialParam, \
                         onvalue = 1, offvalue = 0,bg = "#e4e4e4")
        self.checkInitialParam.grid(column = 0, row = 3, columnspan = 5, sticky = tk.W)

    def OutputOptionsWidgets(self):
        self.tab3.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
        self.tab3.columnconfigure(0, weight=1)
        self.tab3.rowconfigure(0, weight=1)

        # DPI output   
        self.outDpi = tk.StringVar(value = "600")
        self.outDpiEntry = ttk.Entry(self.tab3,textvariable =self.outDpi,  width=10)
        self.outDpiEntry.grid(column = 4, row = 0, sticky = tk.W)
        ttk.Label(self.tab3, text="DPI output").grid(column=2, row=0, columnspan = 2)

    def resizingfunction(self, event = None):
        global tabcontrolw, tabcontrolh
        self.active_tab = self.tabcontrol.index(self.tabcontrol.select())
        if self.active_tab != 1:
            self.window.geometry(str(tabcontrolw) "x" str(tabcontrolh))
        elif self.active_tab == 1:        
            self.window.geometry(str(int(tabcontrolw*.7)) "x" str(int(tabcontrolh*.6)))


    def Preferences(self, event = None):
        self.window = tk.Toplevel(root)
        self.window.title("Preferences")
        self.style = ttk.Style(self.window)
        self.style.theme_settings( "aqua",  settings={
                "TNotebook.Tab": {
                    "configure": {"padding": [5, 1], "background": "#e7e7e9","foreground": "black" },
                    "map":       {"background": [("selected", "#cfcfd1")], "foreground":[("selected", "black")],\
                                  "expand": [("selected", [1, 1, 1, 0])] } } } )
        self.tabcontrol = ttk.Notebook(self.window)
        self.tab1 = ttk.Frame(self.window)
        self.PlotOptionsWidgets()
        self.tab2 = ttk.Frame(self.window)
        self.RegressionOptionsWidgets()
        self.tab3 = ttk.Frame(self.window)
        self.OutputOptionsWidgets()

        self.tabcontrol.add(self.tab1, text = "Plot")
        self.tabcontrol.add(self.tab2, text = "Regression")
        self.tabcontrol.add(self.tab3, text = "Output")
        self.tabcontrol.pack(expand = 1, fill = "both")
        self.window.update()
        global tabcontrolw, tabcontrolh
        tabcontrolw = self.tabcontrol.winfo_width()
        tabcontrolh = self.tabcontrol.winfo_height()
        self.tabcontrol.bind("<<NotebookTabChanged>>", self.resizingfunction)

root = tk.Tk()
root.title("Kinetic Plotting")
MainApplication(root)
root.mainloop()

CodePudding user response:

Note that I was unable to replicate the errors you were having with destroying the subwindows. However, you are potentially having the issue as you are trying to destroy the individual windows, when you could just destroy the main window (as said by @furas). Just call self.parent.destroy() in your Destroy_subwindow function.

def Destroy_subwindow(self, event):
    self.parent.destroy()

There is an alternative to binding the <Destroy> and <Return> events to this function. You could use protocol handlers. This allows you to handle when a user explicitly closes a window using the window manager. An example of how you could implement this is below.

import tkinter as tk 

class MainApplication():
    def __init__(self, parent):
        # Main window
        self.parent = parent
        but1 = tk.Button(self.parent, text="Main window", width = 30, command=self.sub_window)
        but1.pack()
        self.parent.protocol("WM_DELETE_WINDOW", self.on_main_close)

    def sub_window(self):
        # Sub window(s)
        self.window = tk.Toplevel(self.parent)
        but2 = tk.Button(self.window, text="Sub window", width = 30)
        but2.pack()

    def on_main_close(self):
        print("Closing everything")
        self.parent.destroy()

root = tk.Tk()
app = MainApplication(root)
root.mainloop()
  • Related