Tkinter登录时调用destroy()关闭主窗口报错排查
Tkinter登录功能中调用destroy()触发AttributeError的问题解答
错误信息
Exception in Tkinter callback Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/tkinter/__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "/Users/tomassanchezgarcia/Desktop/python-test/iniciar_sesion.py", line 84, in validar self.root.destroy() ^^^^^^^^^ AttributeError: 'Login' object has no attribute 'root'
相关代码
main.py
from tkinter import * from tkinter import ttk import tkinter as tk import pymysql from conexion import * from tkinter import messagebox from PIL import Image, ImageTk from iniciar_sesion import Login class AplicacionInventario: def __init__(self): self.root = Tk() self.root.title("StockMaster") self.root.geometry("400x500") # Bloquear agrandar ventana self.root.resizable(0,0) self.wtotal = self.root.winfo_screenwidth() self.htotal = self.root.winfo_screenheight() # Guardamos el largo y alto de la ventana self.wventana = 400 self.hventana = 500 # Aplicamos la siguiente formula para calcular donde debería posicionarse self.pwidth = round(self.wtotal/2-self.wventana/2) self.pheight = round(self.htotal/2-self.hventana/2) # Se lo aplicamos a la geometría de la ventana self.root.geometry(str(self.wventana)+"x"+str(self.hventana)+"+"+str(self.pwidth)+"+"+str(self.pheight)) self.imagen = Image.open("src/logo.png") self.imagen = self.imagen.resize((350, 100)) # Opcional: Redimensionar la imagen self.imagen = ImageTk.PhotoImage(self.imagen) # Crear un widget Label con la imagen y colocarlo en la ventana self.label_imagen = tk.Label(self.root, image=self.imagen) self.label_imagen.pack(pady=20) self.login1 = Login(self) self.root.mainloop() AplicacionInventario()
iniciar_sesion.py
from tkinter import * from tkinter import ttk import tkinter as tk import pymysql from conexion import * from tkinter import messagebox from PIL import Image, ImageTk from test2 import Menus class Login: def __init__(self, app): self.app = app self.t_Email = "Email:" self.label_email = tk.Label(text=self.t_Email) self.label_email.pack(pady=5, padx=20) self.label_email.place(x=150, y=150, width=100, height=20) self.entry_email = tk.Entry() self.entry_email.pack(pady=5) self.entry_email.place(x=75, y=170, width=250, height=50) self.t_Password = "Contraseña:" self.label_password = tk.Label(text=self.t_Password) self.label_password.pack(pady=5, padx=20) self.label_password.place(x=150, y=220, width=100, height=20) self.entry_password = tk.Entry(show="*") self.entry_password.pack(pady=5) self.entry_password.place(x=75, y=240, width=250, height=50) self.login_email = self.entry_email.get() self.login_password = self.entry_password.get() self.boton_login = tk.Button(text="Iniciar Sesión", command=self.validar) self.boton_login.pack(pady=5) self.boton_login.place(x=125, y=300, width=150, height=50) def validar(self): self.connection2 = connect_to_database() self.cursor2 = self.connection2.cursor() self.cursor2.execute("SELECT nombre,password FROM usuarios") self.data2 = self.cursor2.fetchall() for self.fila in self.data2: self.v_email = self.fila[0] self.v_password = self.fila[1] self.cursor2.close() self.connection2.close() if self.entry_email.get() == "" or self.entry_password.get() == "": self.dialogo = tk.Toplevel() self.dialogo.title("Diálogo Personalizado") self.dialogo.geometry("300x50") self.dialogo.resizable(0,0) self.wtotal2 = self.dialogo.winfo_screenwidth() self.htotal2 = self.dialogo.winfo_screenheight() self.wventana2 = 300 self.hventana2 = 50 self.pwidth2 = round(self.wtotal2/2-self.wventana2/2) self.pheight2 = round(self.htotal2/2-self.hventana2/2) self.dialogo.geometry(str(self.wventana2)+"x"+str(self.hventana2)+"+"+str(self.pwidth2)+"+"+str(self.pheight2)) self.etiqueta = tk.Label(self.dialogo, text="Este es un cuadro de diálogo personalizado.", padx=10, pady=10) self.etiqueta.pack() elif self.entry_email.get() == self.v_email and self.entry_password.get() == self.v_password: self.ventana_menu = Menus(self) else: self.root.destroy() messagebox.showwarning("Advertencia", "Los datos introducidos son incorrectos.")
错误原因
Login类实例本身没有root属性,主窗口的root是属于AplicacionInventario类实例的。你在validar方法中直接调用self.root.destroy(),相当于在Login对象里寻找不存在的root属性,因此触发AttributeError。
操作失误点
- 没有通过传入的
app参数(即AplicacionInventario实例)访问主窗口的root,错误地直接使用self.root。 Login类中创建的Label、Entry等控件未指定父容器,会默认绑定到系统自动创建的根窗口,而非你定义的app.root,可能引发布局异常。- 在
Login的__init__方法中提前获取输入框内容,此时输入框刚创建,内容为空,后续验证逻辑会拿到无效值。
解决方法
1. 修正主窗口销毁的调用方式
在iniciar_sesion.py的validar方法最后,将self.root.destroy()改为通过self.app访问主窗口的root:
else: self.app.root.destroy() messagebox.showwarning("Advertencia", "Los datos introducidos son incorrectos.")
2. 为控件指定正确的父容器
修改Login类中创建控件的代码,将self.app.root作为父容器传入:
# 示例:Label控件修改 self.label_email = tk.Label(self.app.root, text=self.t_Email) # Entry控件修改 self.entry_email = tk.Entry(self.app.root) # 其余Label、Entry、Button控件均需添加self.app.root作为第一个参数
3. 修正输入内容的获取时机
将__init__中获取输入框内容的代码移到validar方法内,确保拿到用户输入的最新内容:
def validar(self): # 移到此处获取输入内容 self.login_email = self.entry_email.get() self.login_password = self.entry_password.get() # 后续数据库验证逻辑...
内容的提问来源于stack exchange,提问作者Tomas
相关产品推荐
相关产品推荐

