在tkinter中关闭子窗口并返回父窗口

4

我对tkinter(和面向对象编程)相对较新,正在尝试创建带有第二个窗口的GUI以进行偏好设置。如果以相反的顺序关闭它们,则没有问题,但我正试图使它们能够按任意顺序关闭(这样关闭主窗口就会关闭子窗口)。

我尝试绑定一个简单的函数,在父窗口被销毁时关闭子窗口(如果存在),虽然它似乎不一致。有时它会关闭子窗口,有时它会冻结,我必须关闭内核。我不确定冻结的原因,因为它似乎发生在关闭子窗口之后。请注意,在完整的代码中,我使用tkmacosx在鼠标悬停在按钮上时更改其背景。

这是我代码的一个子集,用于示例,还可能存在其他问题。还有一些来自我的测试的其他内容(例如将销毁子窗口绑定到返回键和在函数内打印)。

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

如果需要的话,这里是全部代码:

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

请不要重复发布相同的代码。我们只需要一个可以用来重现问题的示例即可。 - Bryan Oakley
我不知道你的问题是什么 - 当你销毁主窗口 Tk() 时,它应该自动销毁子窗口 Toplevel()。也许这只是在 Mac 上的问题。 - furas
1个回答

1
请注意,我无法复制您在销毁子窗口时遇到的错误。但是,由于您试图销毁单个窗口,因此可能会出现问题(正如@furas所说)。您可以在Destroy_subwindow函数中调用self.parent.destroy()来销毁主窗口而不是单个窗口。请注意保留HTML标签。
def Destroy_subwindow(self, event):
    self.parent.destroy()

有一种替代方法可以将<Destroy><Return>事件绑定到此函数。您可以使用协议处理程序。这使您能够处理用户明确使用窗口管理器关闭窗口的情况。下面是实现此操作的示例。

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

我复制并粘贴了这段完全相同的代码(在尝试将其实现到自己的代码中后),但仍然出现了卡顿。 "关闭一切"都打印出来了,子窗口关闭了,所有小部件都从主窗口中清除了,但有个颜色轮一直在旋转。你(或任何人)有什么想法可能导致我的机器出错吗?我在Mac上使用jupyterlab运行。我已经尝试重新启动内核很多次(因为我必须杀死它),并重启了anaconda。 - cmay
我刚刚让有人在Windows上尝试了一下,结果运行良好,所以这应该是某种Mac的问题。 - cmay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接