David Černý David Černý - 11 months ago 99
Python Question

Tkinter Attribute Error when configuring tk Buttons

I'm building an SMTP mail sender and when I started building the login window, I stumbled uppon an Attribute Error when trying to set a button state to 'disabled'.

The first function in this example is the root and the second one is the one causing problems.

class smtp_gui(tk.Tk):
is_logged_in = False
user_email = ''
user_passwd = ''

def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.wm_title("SMTP Mail Sender")
self.resizable(False, False)
self.container = tk.Frame(self)
self.btn_container = tk.Frame(self)
self.bind('<Control-s>', self.send)
self.bind('<Control-h>', self.get_help)

self.container.pack(side="top", padx=1, pady=1)
self.container.columnconfigure(1, weight=1)
self.btn_container.pack(side="bottom", padx=1, pady=1, fill='x')

tk.Label(self.container, text='From:').grid(row=0, column=0, sticky='e')
tk.Label(self.container, text='To:').grid(row=1, column=0, sticky='e')
tk.Label(self.container, text='Subject:').grid(row=2, column=0, sticky='e')


self.To = tk.Entry(self.container)
self.To.grid(row=1, column=1, sticky="we", pady=3, padx=2)

self.Subject = tk.Entry(self.container)
self.Subject.grid(row=2, column=1, sticky="we", pady=2, padx=2)

self.Msg = tk.Text(self.container, width=40, height=5)
self.Msg.grid(columnspan=2, padx=3, pady=3)

send_btn = tk.Button(self.btn_container, text="Send", command=self.send)
send_btn.pack(side='right', padx=2, pady=1)

quit_btn = tk.Button(self.btn_container, text="Quit", command=self.quit)
quit_btn.pack(side='right', padx=2, pady=1)

def send(self, event=None):
print(self.Msg.get("0.0", "end"))

def refresh(self):
if self.logged_in():
except AttributeError:
self.mailabel = tk.Label(self.container, bd=1, text=smtp_gui.user_email, relief='sunken')
self.mailabel.grid(row=0, column=1, pady=3, padx=2, sticky='we')
except AttributeError:
self.login_btn = tk.Button(self.container, text="login before sending", command=self.get_login)
self.login_btn.grid(row=0, column=1, sticky="we", pady=3, padx=2)

def logged_in(self):
return smtp_gui.is_logged_in

def get_login(self):
login = LoginWindow()

def get_help(self, event=None):
get_help = HelpWindow()

class LoginWindow(tk.Toplevel):

def __init__(self, *args, **kwargs):
# Window config
tk.Toplevel.__init__(self, *args, **kwargs)
self.wm_title("Email login")
self.resizable(False, False)
# Containers
self.container = tk.Frame(self)
self.btn_container = tk.Frame(self)

self.container.pack(padx=2, pady=2)
self.btn_container.pack(side="bottom", padx=1, pady=1, fill='x')

# Tracers

self.tracetest = tk.StringVar()
self.tracetest.trace('w', self.tracer)

# Window elements
tk.Label(self.container, text="Gmail address:").grid(row=0, column=0)
tk.Label(self.container, text="Password:").grid(row=1, column=0)

# Entries
self.Address = tk.Entry(self.container, width=44, textvariable=self.tracetest)
self.Address.grid(row=0, column=1, sticky="we", pady=3, padx=2)
self.Address.insert(0, "your address")

self.Passwd = tk.Entry(self.container, show="*", textvariable=None)
self.Passwd.grid(row=1, column=1, sticky="we", pady=3, padx=2)

# Buttons
self.login_btn = tk.Button(self.btn_container, text="Login", command=self.get_credentials)
self.login_btn.pack(side='right', padx=2, pady=3)

storno_btn = tk.Button(self.btn_container, text="Storno", command=self.destroy)
storno_btn.pack(side='right', padx=2, pady=3)

def get_credentials(self):
user_address = self.Address.get()
assert(match(r'[a-zA-Z0-9]+@gmail.com', user_address))
except AssertionError:
messagebox.showwarning("Invalid login", "This is not a valid gmail address!")

def tracer(self, *args):
address = match(r'[a-zA-Z0-9]+@gmail.com', self.Address.get())
passwd = ''
if passwd and address:
self.login_btn.config(state='enabled') # Attribute error here, the program says these buttons don't exist in the functions even tough I defined them above

The problematic function is at the very end (in the tracer function), you should be able to reproduce the problem with this code ( just import tkinter as tk and regex )

I'm guessing there is some kind of a reference problem.


Traceback (most recent call last):
File "C:\Users\david\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1550, in __call__
return self.func(*args)
File "C:\Users\david\Documents\Git_repositories\smtp_client\main_menu.py", line 129, in tracer
AttributeError: 'LoginWindow' object has no attribute 'login_btn'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\Users\david\Documents\Git_repositories\smtp_client\main_menu.py", line 160, in <module>
File "C:\Users\david\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1131, in mainloop
File "C:\Users\david\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1554, in __call__
AttributeError: 'StringVar' object has no attribute '_report_exception'


The traceback occured because the tracer was called before the creation of the button, when I moved the tracer after the initiation of the button, it worked perfectly, thanks guys!

Answer Source

The trace fires when the value changes. The value changes when you do self.address.insert(0, "your address") because that widget is tied to the variable. All of this happens before you define self.login_btn.

You need to define the button before you enable the trace, or before you change the value of the traced variable.