【问题标题】:Closing subwindow with parent window on tkinter在 tkinter 上关闭带有父窗口的子窗口
【发布时间】:2021-11-10 22:19:13
【问题描述】:

我对 tkinter(和 OOP)比较陌生,我正在尝试制作一个带有第二个窗口的 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()

【问题讨论】:

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

标签: python tkinter


【解决方案1】:

请注意,我无法复制您在销毁子窗口时遇到的错误。但是,当您试图破坏单个窗口时,您可能会遇到问题,而您可以只破坏主窗口(如@furas 所说)。只需在您的 Destroy_subwindow 函数中调用 self.parent.destroy()

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

除了将&lt;Destroy&gt;&lt;Return&gt; 事件绑定到此函数之外,还有另一种方法。你可以使用protocol handlers。这允许您在用户使用窗口管理器显式关闭窗口时进行处理。下面是如何实现此功能的示例。

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。
  • 我刚有人在 Windows 上试用过它,它工作正常,所以这是某种 Mac 问题。
猜你喜欢
  • 1970-01-01
  • 2012-12-15
  • 1970-01-01
  • 2017-02-08
  • 2013-02-14
  • 1970-01-01
  • 1970-01-01
  • 2020-10-17
  • 1970-01-01
相关资源
最近更新 更多