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()