首页 > 解决方案 > 反复打开和销毁窗口时使用全局变量的问题

问题描述

我正在构建一个小型应用程序。

当一个人运行这个最小的程序时,他/她会看到开始屏幕上有一个登录按钮(startS)。当单击它时,该窗口将被销毁并创建一个新窗口(homeP),该窗口具有一个用于打开文本框的按钮和一个注销按钮。单击注销按钮时,会破坏homeP并再次启动startS

重现我的问题的步骤:

点击登录 -> 点击“输入成绩” -> 点击退出 -> 点击登录 -> 点击“输入成绩” -> 错误:( can't invoke "grid" command: application has been destroyed)

发生这种情况是因为,我一直在使用globals. 正如人们所看到的,我想防止框架相互堆叠,因此我检查是否已经制作了eGrade框架(即是否存在globals())。如果是,那么只需放置现有的。但是当页面被销毁时,它仍然在globals(),因此,如果我们在小部件已被销毁时尝试再次放置它会出错。

from tkinter import *

def Start():
    global startS
    startS = Tk()

    loginButton = Button(startS, text='Login', bg='blue', fg='white', command=Login)
    loginButton.grid()

    startS.mainloop()

def Home():
    global homeP
    homeP = Tk()

    enterButton = Button(homeP, text='Enter Grades', bg='blue', fg='white', command=enterG)
    enterButton.grid(row=0, column=0, sticky="w")
    logoutButton = Button(homeP, text='LogOut', bg='brown', fg='white', command=Logout)
    logoutButton.grid(row=0, column=1, sticky="e")

    homeP.mainloop()

def enterG():
    global homeP
    global eGrade

    if 'eGrade' not in globals(): #Prevent Frames from stacking up on one another
        eGrade = Frame(homeP)

        enterGrades = Text(eGrade, width=64, height=10)
        enterGrades.grid(row=0, column=0, sticky="ewns")

        eGrade.grid(row=1, column=0, columnspan=2, sticky="ns")
    else:
        eGrade.grid(row=1, column=0, columnspan=2, sticky="ns")

    # for name, value in globals().copy().items():
    #   print(name, value)

def Logout():
    global homeP
    homeP.destroy()
    Start()

def Login():
    global startS
    startS.destroy()
    Home()

Start()

所以我想知道专家们关于这个话题的一些建议,关于使用全局变量是否是一种好的做法,我该如何规避这个问题?

标签: pythonpython-3.xtkinter

解决方案


您收到此错误的主要原因是当您第一次注销保存文本小部件的容器时。即使小部件(框架)存在于全局命名空间中,您也已经销毁了分配给它的 tkinter 实例。因此不能再应用于它。

这是重新创建Tk()实例的直接结果,而不是仅使用一个实例并管理其中的数据。

我的示例会将您的代码压缩为更简单的代码,并且应该为您提供一个良好的起点。我们将在这里做的是为您的文本框中的文本创建一个全局跟踪变量。这将允许我们在注销时保存数据,然后在重新登录时重新应用数据。从而保留旧文本。

import tkinter as tk

def Home():
    clear_widgets()
    tk.Button(root, text='Enter Grades', bg='blue', fg='white', command=enterG).grid(row=0, column=0, sticky="w")
    tk.Button(root, text='LogOut', bg='brown', fg='white', command=logout).grid(row=0, column=1, sticky="e")

def enterG():
    global txt
    if txt == None:
        txt = tk.Text(root, width=64, height=10)
        txt.grid(row=1, column=0, columnspan=2, sticky="ns")
        txt.insert(1.0, text_data)

def logout():
    global txt, text_data
    text_data = txt.get(1.0, "end-1c")
    clear_widgets()
    txt = None
    tk.Button(root, text='Login', bg='blue', fg='white', command=login).grid(row=0, column=0)

def clear_widgets():
    for widget in root.winfo_children():
        widget.destroy()

def login():
    # some method of checking login credentials.
    Home()

root = tk.Tk()
text_data = ""
tk.Button(root, text='Login', bg='blue', fg='white', command=login).grid(row=0, column=0)
root.mainloop()

但是,您迟早会想要开始使用 OOP 进行编码。这是一个不错的选择,它将使我们能够避免全局的全部。在一个类中,我们可以使用一种称为类属性的东西,它可以从任何方法(类函数)访问,而无需定义全局。

这是您的代码的类示例。

import tkinter as tk


class Example(tk.Tk):
    def __init__(self):
        super().__init__()
        self.text_data = ""
        self.txt = None
        tk.Button(self, text='Login', bg='blue', fg='white', command=self.login).grid(row=0, column=0)

    def home(self):
        self.clear_widgets()
        tk.Button(self, text='Enter Grades', bg='blue', fg='white', command=self.enter_g).grid(row=0, column=0, sticky="w")
        tk.Button(self, text='LogOut', bg='brown', fg='white', command=self.logout).grid(row=0, column=1, sticky="e")

    def enter_g(self):
        if self.txt == None:
            self.txt = tk.Text(self, width=64, height=10)
            self.txt.grid(row=1, column=0, columnspan=2, sticky="ns")
            self.txt.insert(1.0, self.text_data)

    def logout(self):
        self.text_data = self.txt.get(1.0, "end-1c")
        self.clear_widgets()
        self.txt = None
        tk.Button(self, text='Login', bg='blue', fg='white', command=self.login).grid(row=0, column=0)

    def clear_widgets(self):
        for widget in self.winfo_children():
            widget.destroy()

    def login(self):
        # some method of checking login credentials.
        self.home()

Example().mainloop()

推荐阅读