Tkinter callback function causing ValueError - python

I am having problem with the callback function of the tkinter trace method. I would like to have 2 entries and the value of each entry depends on the value of another. So if I change the value of one, the value of the other is changed. This is some simple code doing this:
from tkinter import *
class main():
def __init__(self, master):
self.a = DoubleVar(value=2.0)
self.b = DoubleVar()
self.b.trace("w",self.calc_c)
self.c = DoubleVar()
self.c.trace("w",self.calc_b)
Entry(master,textvariable=self.b).grid(row=0,column=0)
Entry(master,textvariable=self.c).grid(row=0,column=1)
def calc_b(self,name,index,mode):
self.b.set(self.c.get()/self.a.get())
def calc_c(self,name,index,mode):
self.c.set(self.b.get()*self.a.get())
root = Tk()
prog = main(root)
root.mainloop()
The program is actually working, returning the right values, but also an error is produced:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib64/python3.3/tkinter/__init__.py", line 1475, in __call__
return self.func(*args)
File "/home/anze/foo.py", line 22, in calc_c
self.c.set(self.b.get()*self.a.get())
File "/usr/lib64/python3.3/tkinter/__init__.py", line 332, in get
return getdouble(self._tk.globalgetvar(self._name))
ValueError: could not convert string to float:
Can someone please explain the meaning of this error?
Thank you!

I think the problem is that the widget is an Entry, which takes a string as input, and your value is a DoubleVar, but you have no validation in place to prevent the user entering an invalid value.
Your code works as long as the input value is a float. When you call get() in calc_c and calc_b after an invalid value was entered, tkinter tries to convert the string in the Entry into a float, fails and throws a ValueError exception.
You can catch the exception with a try-except block. In the try block you put the code that might raise an exception. The except block defines what happens when an exception is caught. You can also have an else block that defines what happens when no exception is caught.
Here's a simple example (with no else block) for calc_c that restores the previous value (calc_b would need to be modified in a similar way):
def calc_c(self,name,index,mode):
try:
self.c.set(self.b.get()*self.a.get())
except ValueError:
# here you can either complain to the user (message-box...)
# or just restore the last value using the other entry like so:
self.b.set(self.c.get()/self.a.get())

I believe your issue is that you are binding self.c to an Entry widget:
Entry(master,textvariable=self.c).grid(row=0,column=1)
But, the textvariable should be set to a Tkinter.StringVar. How about creating a new variable to hold the Entry's string value and then convert in your calc method:
from tkinter import *
class main():
def __init__(self, master):
self.a = DoubleVar(value=2.0)
self.b = DoubleVar()
self.b.trace("w",self.calc_c)
self.c = DoubleVar()
self.c_str = StringVar()
self.c.trace("w",self.calc_b)
Entry(master,textvariable=self.b).grid(row=0,column=0)
Entry(master,textvariable=self.c_str).grid(row=0,column=1)
def calc_b(self,name,index,mode):
self.c.set(float(self.c_str.get()))
self.b.set(self.c.get()/self.a.get())
def calc_c(self,name,index,mode):
self.c.set(self.b.get()*self.a.get())
root = Tk()
prog = main(root)
root.mainloop()
Note you'll probably want to do the same for the other Entry.

I had a similar error using Tk.scale(... resolution=0.1, ...).
When there was matplotlib used in the same script, the decimal number from scale was written with , instead of . and caused an error in getdouble().
I found a workaround by changing the regional settings of the os(kubuntu14.04) from German to US with . as default decimal seperator.
It not satisfying solution but may help to find the problem.

Related

Getting "TypeError: insertstop() takes 0 positional arguments but 1 was given" when there are no arguments in my function calls

I'm very new to Python and I'm trying to put my first application together that takes in trip information and inserts it into a text box for export out to a document. It's gone pretty good until today when I tried to implement multiple ways of inserting text from an entrybox into a text block with tkinter.
I have an entry widget that inserts text into a text widget when a button is pressed. That's simple enough but I wanted to make it to where you could simply hit the enter key to do the same thing.
When I implement this function I get the error:
"Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\Spyder\pkgs\tkinter_init_.py", line 1892, in __ call __
return self.func(*args)
TypeError: insertstop() takes 0 positional arguments but 1 was given"
I've looked the error up and it seems like it would pop up if I put in arguments in my function call but I don't have any arguments in any of my function calls. Also it seems like maybe this error is related to a class or something? I haven't learned about classes yet and only have a basic idea of what they are. Do I need a class to do this?
Coincidentally I also added the argument(self) in my function and that made pressing enter work but it made my insert stop button quit working.
I'm sure I've missed something very basic but I just can't figure out what.
Thanks for any help!
import time
import os
import sys
from tkinter import *
# Creates the Tkinter form named "screen"
screen = Tk()
screen.geometry("550x645")
screen.title("Test")
# Initialize frames
menuframe = Frame(screen,
height=60,width=600,bg="gray",pady=5)
inputframe = Frame(screen,
height=300,width=600,pady=5)
outputframe = Frame(screen,
height=290,width=600,pady=5)
# Packs the frames so they will display
menuframe.pack()
inputframe.pack()
outputframe.pack()
#==STOPBOX==#
stopbox=Text(inputframe,yscrollcommand=1,height= 10,width=20,
padx=3,pady=3,relief=GROOVE,bg="gray79")
stopbox.place(x=345, y=90)
def insertstop():
global stop_vanconv
stop_vanconv=(stop_entry.get())
stopbox.insert(END, stop_vanconv + "\n")
stop_entry.delete("0","end")
stoplist_label = Label(inputframe,
text="Type stop locations and press" + '\n' +
"the Add Stop button to insert a new stop.")
stoplist_label.place(x=100, y=150)
stop_entry = Entry(inputframe,
textvariable = " ")
stop_entry.place(x=150, y=190)
addstopbutton = Button(inputframe,text="Add Stop",padx=20,pady=0,
activebackground="darkslategray4",command=insertstop)
addstopbutton.place(x=160, y=220)
stop_entry.bind('<Return>',insertstop)
screen.mainloop()

Tkinter - StringVar() trace not executing command

I wrote this code to be a version manager, but it doesn't execute the command changeDir(). Why?
https://pastebin.com/VSnhzRzF
You forgot to pass a 'name' argument to changeDir function. And there's no exception because your statement has no effect!
Snippet to represent the problem:
import sys
def exec_smth():
# execution without effect
exec('write_smth')
try:
# execution with exception because of missing argument
exec('write_smth()')
except TypeError as error:
# now we pass an argument
exec('write_smth("I failed because of %s" % error )')
def write_smth(smth):
sys.stdout.write(smth)
exec_smth()
Anyway, outside your __init__ function there're no StringVars at all thanks to garbage collector, so your code would fail anyway!
There're even more problems, because you never bind any of your sv{} to a widget and expect something in return! But ok, let's try to do things with exec:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entries = []
for _ in range(5):
exec('self.sv{} = tk.StringVar()'.format(_))
exec('self.sv{}.trace("w", self.change_sv)'.format(_))
exec('self.entries.append(tk.Entry(self, text="", textvariable=self.sv{}))'.format(_))
for entry in self.entries:
entry.pack()
def change_sv(*args):
# get id of a variable (you can't rely on that (0-9)!)
idx = args[1][-1:]
# get new value
value = getattr(args[0], 'sv{}'.format(idx)).get()
# result
print('Value changed in self.sv%s to %s!' % (idx, value))
app = App()
app.mainloop()
Output:
As you see - we always need a reference to StringVars and I think that option with a list of them is far better!
Note: If you need to pass something to callback function - use a lambda function! All code tested with Python 3.
Links:
The Variable Classes
Tkinter Callbacks
Behavior of exec function in Python 2 and Python 3

Saving exceptions in Tkinter using Python

I have developed several Python programs for others that use Tkinter to receive input from the user. In order to keep things simple and user-friendly, the command line or python console are never brought up (ie. .pyw files are used), so I'm looking into using the logging library to write error text to a file when an exception occurs. However, I'm having difficulty getting it to actually catch the exceptions. For example:
We write a function that will cause an error:
def cause_an_error():
a = 3/0
Now we try to log the error normally:
import logging
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
cause_an_error()
except:
logging.exception('simple-exception')
As expected, the program errors, and logging writes the error to errors.log. Nothing appears in the console. However, there is a different result when we implement a Tkinter interface, like so:
import logging
import Tkinter
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
root = Tkinter.Tk()
Tkinter.Button(root, text='Test', command=cause_an_error).pack()
root.mainloop()
except:
logging.exception('simple-exception')
In this case, pressing the button in the Tkinter window causes the error. However, this time, nothing is written to the file, and the following error appears in the console:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1536, in __call__
return self.func(*args)
File "C:/Users/samk/Documents/GitHub/sandbox/sandbox2.pyw", line 8, in cause_an_error
a = 3/0
ZeroDivisionError: integer division or modulo by zero
Is there a different way to catch and log this error?
It's not very well documented, but tkinter calls a method for exceptions that happen as the result of a callback. You can write your own method to do whatever you want by setting the attribute report_callback_exception on the root window.
For example:
import tkinter as tk
def handle_exception(exception, value, traceback):
print("Caught exception:", exception)
def raise_error():
raise Exception("Sad trombone!")
root = tk.Tk()
# setup custom exception handling
root.report_callback_exception=handle_exception
# create button that causes exception
b = tk.Button(root, text="Generate Exception", command=raise_error)
b.pack(padx=20, pady=20)
root.mainloop()
For reference:
http://epydoc.sourceforge.net/stdlib/Tkinter.Tk-class.html#report_callback_exception
Since this question was about logging and being explicit where the error is occurring, I will expand on Bryan (totally correct) answer.
First, you have to import some additional useful modules.
import logging
import functools # for the logging decorator
import traceback # for printing the traceback to the log
Then, you have to configure the logger and the logging function:
logging.basicConfig(filename='/full/path/to_your/app.log',
filemode='w',
level=logging.INFO,
format='%(levelname)s: %(message)s')
def log(func):
# logging decorator
#functools.wraps(func)
def wrapper_log(*args, **kwargs):
msg = ' '.join(map(str, args))
level = kwargs.get('level', logging.INFO)
if level == logging.INFO:
logging.info(msg)
elif level == logging.ERROR:
logging.exception(msg)
traceback.print_exc()
return wrapper_log
#log
def print_to_log(s):
pass
Note that the function doing the logging is actually log and you can use it for both printing errors and regular info into your log file. How you use it: with the function print_to_log decorated with log. And instead of pass you can put some regular print(), so that you save the message to the log and print it to the console. You can replace pass with whatever commands you prefer.
Note nb. 2 we use the traceback in the log function to track where exactly your code generated an error.
To handle the exception, you do as in the already accepted answer. The addition is that you pass the message (i.e. the traceback) to the print_to_log function:
def handle_exception(exc_type, exc_value, exc_traceback):
# prints traceback in the log
message = ''.join(traceback.format_exception(exc_type,
exc_value,
exc_traceback))
print_to_log(message)
Having configured all this, now you you tell your Tkinter app that it has to use the handle_exception function:
class App(tk.Tk):
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.report_callback_exception = handle_exception
app.mainloop()
Or alternatively, if you work with multiple classes, you can even instruct the class itself to use the andle_exception function:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.report_callback_exception = handle_exception
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.mainloop()
In this way, your app code looks lean, you don't need any try/except method, and you can log at info level whichever event in your app.

Tkinter entry widget get is undefined, not updating to my main function,

i'm following a few different guides to re-learn Tkinter by writing a little application that grabs stock prices. My issue that I am up a wall against is calling the .get() method from my entry widget variable. I've seen a couple other suggestions to put all the widget creating inside a function and then call the function in the init method, however I'm getting the same error, even when using the self argument in front of my entry variables. I know it's an issue with the way i'm passing data from function to function, but I can't wrap my head around it. Here's the code, sorry for the wall of text:
class MyApp:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.createWidgets()
button1 = Button(self.myContainer1, command = self.button1Click)
button1.configure(text = "get quote")
button1.pack()
def createWidgets(self):
root.title("Stock App")
self.symbol = Entry(self.myContainer1)
self.symbol.pack()
self.symbol.focus_set()
def button1Click(self):
stock = symbol.get()
print stock
I've taken it down to simplest form even and just had the button1Click call a callback function-
def button1Click(self):
print callback()
def callback():
print symbol.get()
This returns the exact same error:
NameError: global name 'symbol' is not defined
Is it getting destroyed too early? how do I fix this?
I've referenced multiple documents for tkinter and have seen some great fixes but none are extensible, or um unable to see how they relate to me using it inside of an object.
Thanks in advance for the help.
As far as I can tell inside of your button1Click method you need to add self as in:
def callback():
print self.symbol.get()
You're missing self. to make the callback:
def callback():
print self.symbol.get()
instead.

Python Attribute Error with multiple classes and Tkinter

I'm trying create a gui using Tkinter that grabs a username and password and connects to a remote server and does a function. I slapped together some messy code and it more or less worked, but when I tried to recreate it in a tidy module, it broke. Its probably a newbie python error, but I can't spot it. EDIT: to clarify, when it worked, the only class was setupGui and any methods were under that class. Now that I've separated the gui from the methods, its not working.
class setupGui(object):
def __init__(self, parent):
##omited general frame stuff
self.userIn = ttk.Entry(self.topFrame, width = 20)
self.userIn.grid(row = 1, column = 1)
self.passIn = ttk.Entry(self.topFrame, width = 20, show ="*")
self.passIn.grid(row = 2, column = 1)
#Buttons
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
self.setupbtn.grid(row = 3, column = 0, pady = 10)
class setup(object):
def__init__(self):
self.userName = setupGui.userIn.get()
self.userPass = setupGui.passIn.get()
def startSetup(self):
self.another_related_fucntion # about 4 related functions actually
if __name__ == '__main__':
root = Tk()
gui = setupGui(root)
root.mainloop()
And if I don't have the command attached to the button, everything works fine (but obviously does diddly squat except look pretty). And when I attached the command, I get this error:
Traceback (most recent call last):
File "macSetup.py", line 211, in <module>
gui = setupGui(root)
File "macSetup.py", line 45, in __init__
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
File "macSetup.py", line 69, in __init__
self.userName = setupGui.userIn.get()
AttributeError: type object 'setupGui' has no attribute 'userIn'
In your code, userIn is set up as an instance variable of setupGui objects, not as an attribute of the setupGui class itself.
The simplest solution would be to merge the setupGui and setup classes to move startSetup in as a method of setupGui, then use command=self.startSetup when you initialize setupbtn—this calls startSetup as a bound method, and self should thus refer to the setupGui object, which you can then use e.g. self.userIn.get() and self.passIn.get() on.
If you'd rather keep the logic you have in the setup class out of the setupGui class, you can separate it out like this:
class setup(object):
def __init__(self, username, userpass):
self.userName = username
self.userPass = userpass
def startSetup(self):
# as before
then add this method to the setupGui class:
def dosetup(self):
setup(self.userIn.get(), self.passIn.get()).startSetup()
and instantiate the Button with command=self.dosetup. (I would personally make the setup class a standalone function, but I don't know how complicated your startSetup routine actually is, so I assume you have a good reason for making it a class.)
The command attribute takes a reference to a function, but you're calling the function and giving the result to the command attribute. The net result is that you're calling the setup function at the time that you create the button, not at the time that you click the button. Things aren't fully initialized yet, so you get the error.
You're doing this:
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
... when you should be doing something like this:
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup)
Note the lack of the trailing () on startSetup.
If you don't want to instantiate setup until the button is clicked, you have a couple of choices. The best, arguably, is to create a method:
def _launch_setup(self):
setup().setupGui()
...
self.setupbtn = ttk.Button(..., command=self._launch_setup)
You could also use a lambda, but in this case I recommend a named method.
The class setupGui itself doesn't have the attribute userIn.
In the __init__ method of setupGui you give the attribute to the instance, not the class.

Categories

Resources