David Černý David Černý - 3 months ago 26
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.iconbitmap('at.ico')
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.refresh()

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():
try:
self.login_btn.destroy()
except AttributeError:
pass
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')
else:
try:
self.mailabel.destroy()
except AttributeError:
pass
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()
self.refresh()

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.grab_set()
self.wm_title("Email login")
self.resizable(False, False)
self.iconbitmap('login.ico')
# 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()
try:
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
else:
self.login_btn.config(state='disabled')


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.

EDIT - FULL TRACEBACK



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
self.login_btn.config(state='disabled')
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>
root.mainloop()
File "C:\Users\david\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1131, in mainloop
self.tk.mainloop(n)
File "C:\Users\david\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1554, in __call__
self.widget._report_exception()
AttributeError: 'StringVar' object has no attribute '_report_exception'


FOUND A SOLUTION



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

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.

Comments