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