I tried making a simple integer sign calculator using tkinter. It has a class with two different functions. The second function is supposed to be initiated when the user presses the "Enter" button. When I run the code the window comes up just as it is supposed to. But when I type and hit "Enter" the second function fails to run and does not update the label. I want for it to update as either "This number is positive.", "This number is 0.", or "This number is negative." Instead it remains blank.
I doubt it is relevant, but I made this program in PyCharm Community Edition 5.0.4, and I am using Python 3.5 (32-bit).
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.statement = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
self.statement = tkinter.StringVar()
if self.number > 0:
self.statement = "This number is positive."
elif self.number == 0:
self.statement = "This number is 0."
else:
self.statement = "This number is negative."
IntegerSign()
The first problem: in your constructor you initialize a variable named self.statement to a StringVar and then initialize again to a Label. After that second initialization, you have no way of accessing the first object. You need to use two different names.
The second problem: in your event handler, calc_answer, you create a new object named self.statement, but instead you need to set a new value into the old one (see docs). Here is a modified version of your program that works as intended:
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.solution_string = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
if self.number > 0:
self.solution_string.set("This number is positive.")
elif self.number == 0:
self.solution_string.set("This number is 0.")
else:
self.solution_string.set("This number is negative.")
IntegerSign()
This code works but contains a bad practice that I recommend you fix. The function tkinter.mainloop() is essentially an infinite loop, and you have placed it inside the constructor. Thus the constructor won't return the way a constructor is normally supposed to. Take that statement out of the __init__ function and put it at the end, after the call to IntegerSign, and make this a pattern to be used in the future.
To set the value of a StringVar you need to use the set method.
Right now all you did was re-assign the variable. You can also set a stringvar's (default) value by giving it a value when you first initialize it. e.g. - var = tk.StringVar(value="some value")
Edit: Didn't see that you also set self.statement to be the label widget... This would work if you used the method all the way at the bottom of this answer, and disregarded (optionally) stringvar's entirely. But, when you do this you can think of it as sticky notes. You stuck a sticky note that says "this variable holds this value", then you re-assigned the variable put another sticky note over the previous one that says "it now holds this value" as a really loose visual analogy.
>>> import tkinter as tk
>>> root = tk.Tk()
>>> statement = tk.StringVar()
>>> type(statement)
>>> <class 'tkinter.StringVar'>
>>> statement = "This number is positive"
>>> type(statement)
>>> <class 'str'>
>>> statement = tk.StringVar()
>>> statement.set("This number is positive")
>>> statement.get()
'This number is positive'
>>> type(statement)
>>> <class 'tkinter.StringVar'>
Alternatively you could just change the labels text by doing label_widget['text'] = 'new_text'
Related
I’m writing a code for an ecosystem simulation in which the user can adjust initial values as e.g. population size, fertility, and so on. Instead of writing a method for each variable I would like to use a general method that can be called for each variable. I also want to exclude non-integers as input and to restrict the acceptable value to a certain range.
However, the last requirement fails in my code as the range limits for each variable affect each other. Basically because I cannot add arguments to the entry.bind method. How to solve this?
Below, you'll find a simplified version of my code in which the problem happens.
import tkinter as tk
class Window():
def __init__(self):
tk.Label(master, text ='Fox number').grid(row=0,column=0)
tk.Label(master, text ='Hare number').grid(row=1,column=0)
self.fox_entry=tk.Entry(master, width=5)
self.fox_entry.grid(row=0, column=1)
self.hare_entry=tk.Entry(master, width=5)
self.hare_entry.grid(row=1, column=1)
class Ecosystem():
def __init__(self):
self.foxnumber = 10
self.harenumber = 100
def initiate(self):
def input_user(entry,value, minval, maxval):
self.value = value
self.inputvalue = value
self.minval = minval
self.maxval = maxval
def get_value(event):
try:
self.inputvalue = int(entry.get())
print(self.inputvalue)
except ValueError:
entry.delete(0,'end')
entry.insert(0,self.value)
self.inputvalue = self.value
if self.inputvalue not in range(self.minval, self.maxval+1):
entry.delete(0,'end')
entry.insert(0,self.value)
self.inputvalue = self.value
print(self.inputvalue)
entry.bind('<Return>', get_value)
value = self.inputvalue
return value
my_win.fox_entry.insert(0,self.foxnumber)
self.foxnumber = input_user(my_win.fox_entry,self.foxnumber, 0, 50)
my_win.hare_entry.insert(0,self.harenumber)
self.harenumber = input_user(my_win.hare_entry,self.harenumber, 0, 200)
# more variables will added later on
master = tk.Tk()
my_win = Window()
my_ecosystem = Ecosystem()
my_ecosystem.initiate()
master.mainloop()
My objective is to create three variables called 'Number of pipes','Anisotropy ratio', 'Filter depth (in cm)' which values are chosen by the user (through a User interface). I achieved to create the scrollbar of Tkinter and to enter values but these are not stored as variables. Could someone give me a hand solving that? I am new in Python and specially I am struggling with Tkinter. The code is developped in Python 3.7:
root = tk.Tk()
root.geometry('1000x1000')
#Description show in window
info=tk.Label(root,anchor='e')
info.pack()
#Parameters
parameters = ('Number of pipes','Anisotropy ratio', 'Filter depth (in cm)')
def ask_parameter(entry):
user_pipes = str (entry['Number of pipes'].get())
user_aniso = str (entry['Anisotropy ratio'].get()) #effective screen length = b
user_depth = str (entry['Filter depth (in cm)'].get())
print(user_pipes,user_aniso,user_depth)
#if parameters
# return True
# else:
# tkinter.messagebox.showwarning ('Only numbers', 'Try again')
# return True
#
def form(parameters):
entry = {}
for parameter in parameters:
print(parameter)
row = tk.Frame(root)
lab = tk.Label(row, width=15, text=parameter+": ", anchor='w')
ent = tk.Entry(row)
row.pack(side=tk.TOP,
fill=tk.X,
padx=2,
pady=2)
lab.pack(side=tk.LEFT)
ent.pack(side=tk.RIGHT, expand=tk.YES,fill=tk.X)
entry[parameter] = ent
return entry
if __name__ == '__main__':
ents = form(parameters)
save_button = tk.Button(root)
save_button.configure(text='Save', command=lambda: ask_parameter(ents))
save_button.pack()
root.mainloop()
Any problem is shown with the code but the parameters are not stored as variables with the entered values.
Thank you very much for your time.
Your code is storing the values inputted through the GUI to variables, your problem is probably that your variables aren't global, so you won't be able to use them outside of the function they are created in. you can either get around this with a return, or make them global by putting global varname above the definition of each variable as well as at the start of every function that will use it.
I want the user to be able to enter an integer value in the Spinbox widget. If the value entered is not an integer or is an integer outside the Spinbox limits, as soon as the Spinbox loses focus, the value in the Spinbox's content must revert to a default value.
In the example code, I use the Entry widget just for the Spinbox can lose focus.
If the user comes back to Spinbox to enter a new value, his entry is not validated.
I confirm Malcolm's remark in Interactively validating Entry widget content in tkinter that the validatecommand=command feature gets cleared as soon as this command updates the widget's value.
Is there a way to get the value entered in the Spinbox repeatedly validated and not just once?
from tkinter import *
class GUI:
def __init__(self):
# root window of the whole program
self.root = Tk()
self.root.title('Validate Spinbox')
# registering validate and invalid commands
validate_cmd = (self.root.register(self.validate), '%P')
invalid_cmd = (self.root.register(self.invalid))
# creating a Label
items_lbl = Label(self.root, text="# of items (5-10):")
items_lbl.grid(row=0, column=0)
# creating a Spinbox widget
self.items_var = StringVar()
self.items_var.set(7)
items_count = Spinbox(self.root, textvariable=self.items_var,
from_=5, to=10, width=4, validate='focusout',
validatecommand=validate_cmd,
invalidcommand=invalid_cmd)
items_count.grid(row=0, column=1)
# creating an Entry widget
self.entry_var = StringVar()
self.entry_var.set("Input some text here")
text_entry = Entry(self.root, textvariable=self.entry_var)
text_entry.grid(row=1, column=0)
def validate(self, entry):
try:
value = int(entry)
valid = value in range(5, 11)
except ValueError:
valid = False
if not valid:
self.root.bell()
return valid
def invalid(self):
self.items_var.set(7)
if __name__ == '__main__':
main_window = GUI()
mainloop()
I found a great explanation here (in the last paragraph of the chapter Validation):
http://stupidpythonideas.blogspot.fr/2013/12/tkinter-validation.html
If your validatecommand (or invalidcommand) modifies the Entry directly or indirectly (e.g., by calling set on its StringVar), the validation will get disabled as soon as your function returns. (This is how Tk prevents an infinite loop of validate triggering another validate.) You have to turn it back on (by calling config). But you can't do that from inside the function, because it gets disabled after your function returns.
But you need to apply some changes to be able to use this trick.
You need to make the Spinbox an instance attribute, with self :
self.items_count = Spinbox(self.root, textvariable=self.items_var,
from_=5, to=10, width=4, validate='focusout',
validatecommand=validate_cmd,
invalidcommand=invalid_cmd)
self.items_count.grid(row=0, column=1)
And then you can call self.items_count.after_idle(...) inside the validate method :
def validate(self, entry):
try:
value = int(entry)
valid = value in range(5, 11)
except ValueError:
valid = False
if not valid:
self.root.bell()
self.items_count.after_idle(lambda: self.items_count.config(validate='focusout'))
return valid
I am having an issue validating spinbox input. I have a workaround below that seems to work; however, it's awkward. Assuming this isn't a bug, is there a correct way to do this? I am using Anaconda Python 3.6 (tk 8.6) on Windows 10.
The issue is that validate is set to None if you return False from the validation function when the value in the spinbox entry is between to and from. This only occurs when clicking the up or down buttons and not when directly editing the text.
import tkinter as tk
class SpinboxGui:
def __init__(self):
self.root = tk.Tk()
vcmd = (self.root.register(self.validate_spin), '%W', '%P')
self.spin = tk.Spinbox(self.root, from_=0, to=50000)
self.spin.config(validate="key", validatecommand=vcmd)
self.spin.pack()
def validate_spin(self, name, nv):
try:
print(nv)
n = int(nv)
except:
return False
if n <= 15000:
return True
return False
if __name__ == "__main__":
SpinboxGui()
tk.mainloop()
To reproduce, highlight 0 and type 149999. Then click up a few times. Note that the validation command stops being called. Output is:
01
014
0149
01499
014999
0149999
15000
15001
Now, according to the docs, using textVariable and validateCommand together is dangerous; indeed, I have crashed Python/Tkinter in more ways than one. However, in this case, it doesn't matter whether you use textVariable or not; the problem is the same.
One possible solution might be to edit the to and from options in the validation function. Even if this works, it's somewhat problematic for me because I'm syncing spinbox values to an embedded Matplotlib plot. I would need to compute to and from and convert units for each Matplotlib Artist and spinbox.
Since you can't edit the textVariable in the validation function, what I came up with is the following. Maybe someone can improve on this.
def __init__(self):
# http://stackoverflow.com/a/4140988/675216
vcmd= (self.root.register(self.validate_spin), '%W', '%P')
# Rest of code left out
self.spin.config(validate="key", validatecommand=vcmd)
self.spin.bind("<<ResetValidate>>", self.on_reset_validate)
def on_reset_validate(self, event):
# Turn validate back on and set textVariable
self.spin.config(validate="key")
def validate_spin(self, name, nv):
# Do validation ...
if not valid:
self.spin.event_generate("<<ResetValidate>>", when="tail")
return valid
After struggling with the validation mechanism in spinbox, I gave up on it. Maybe it works the way it was intended to, but I think it is counter-intuitive that it only gets called once. My application uses spinbox to update a matplotlib graph, and I need the data to be an integer in a specified range. I needed the code to catch non-integer entries as well as out-of-range integers. The solution I came up with was to use key bindings instead of the validation mechanism to achieve the desired result. Here is the relevant part of the code:
class IntSpinbox(ttk.Frame):
def __init__(self, parent, **kwargs):
ttk.Frame.__init__(self,
parent,
borderwidth=kwargs.get('frameborderwidth', 2),
relief=kwargs.get('framerelief', tk.GROOVE))
self.valuestr = tk.StringVar()
self.valuestr2 = tk.StringVar()
self.minvalue = kwargs.get('minvalue', 0)
self.maxvalue = kwargs.get('maxvalue', 99)
self.initval = kwargs.get('initvalue', self.minvalue)
self.valuestr.set(str(self.initval))
self.valuestr2.set(str(self.initval))
self.label = ttk.Label(self,
text=kwargs.get('labeltext', 'No label'),
anchor='w',
width=kwargs.get('labelwidth', 20))
self.spinbox = tk.Spinbox(self,
from_=self.minvalue,
to=self.maxvalue,
increment=1,
textvariable=self.valuestr)
self.spinbox.bind('<Return>', self.updateSpinbox)
self.spinbox.bind('<FocusOut>', self.updateSpinbox)
self.spinbox.bind('<FocusIn>', self.storeSpinbox)
self.spinbox.bind('<Button-1>', self.storeSpinbox)
self.spinbox.bind('<Button-2>', self.storeSpinbox)
self.label.pack(side=tk.TOP, fill=tk.X, expand=True, padx=5)
self.spinbox.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=2, pady=5)
self.onChange = kwargs.get('onchange', self.doNothing)
def storeSpinbox(self, event):
tmpstr = self.valuestr.get()
try:
tmpval = int(tmpstr)
except:
tmpval = -1000
if tmpval < self.minvalue:
tmpval = self.minvalue
elif tmpval > self.maxvalue:
tmpval = self.maxvalue
self.valuestr2.set(str(tmpval))
def updateSpinbox(self, event):
tmpstr = self.valuestr.get()
try:
tmpval = int(tmpstr)
except:
tmpstr = self.valuestr2.get()
self.valuestr.set(tmpstr)
return
if tmpval < self.minvalue:
tmpval = self.minvalue
elif tmpval > self.maxvalue:
tmpval = self.maxvalue
tmpstr = str(tmpval)
self.valuestr.set(tmpstr)
self.valuestr2.set(tmpstr)
self.onChange()
def doNothing(self):
pass
def getValue(self):
tmpstr = self.valuestr.get()
return(int(tmpstr))
def setValue(self, value):
self.valuestr.set(str(value))
I'm probably late to the party, but I'll leave it here in case someone needs it. What I did in a similar situation is to do everything I need in the callback for the writing of the variable I linked to the spinbox. Something like:
import Tkinter as tk
root = tk.Tk()
my_var = tk.IntVar() # or whatever you need
spin = tk.Spinbox(root, from_=0, to=100, textvariable=my_var)
spin.pack()
def do_whatever_I_need(*args):
# here I can access the Spinbox value using spin.get()
# I can do whatever check I
my_var.trace('w', whatever) #'w' for "after writing"
The callback created by the trace method calls the given function whith two arguments: the callback mode ('w', in this case) and the variable name (it's some internal tkinter identifyer I've never used). This is why the signature for do_wahtever_I_need is *args.
I am creating a multiple choice quiz in Tkinter and am using radiobuttons and checkbuttons. For this question I am using radiobuttons. How do I get the value from the radio buttons in order to compare them in the if statement 'Calculate Score'? The computer returns with: 'calculate_score_1() takes exactly 1 argument (0 given)'
Also, how do I pass a variable between classes? I have ten classes for ten questions but want all of them to be able to access the variable 'Score' when adding 1 to the score when the user gets the answer correct.
class Question_1_Window(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Label(self, width=75, height=4, text = "1) Do you have the time to do at least twenty minutes of prefect duty each week?")
self.text.pack(side="top", fill="both", expand=True)
question_1_Var = IntVar() #creating a variable to be assigned to the radiobutton
Yes_1 = Radiobutton(self, text = "Yes", variable = question_1_Var, value=1, height=5, width = 20)
Yes_1.pack() #creating 'yes' option
#Here we are assigning values to each option which will be used in the validation
No_1 = Radiobutton(self, text = "No", variable = question_1_Var, value=2, height=5, width = 20)
No_1.pack() #creating 'no' option
def calculate_score_1(self):
Enter_1.config(state="disabled")
#self.question_1.config(state="disabled")
if (question_1_Var.get() == 1) and not (question_1_Var.get() == 2):
print("calculate score has worked") #test lines
#score = score + 1
else:
print("not worked") #testlines
Enter_1 = Button(self, text= "Enter", width=10, command = calculate_score_1)
Enter_1.pack()
calculate_score_1 is not a method of the instance, but is defined inside the __init__ method. Thus, that method should not have the self parameter. Remove that parameter, then it should work. If you need it (you seem not to) you can still use the self parameter of the outer __init__ method.
If you want to access the score from another class (or in fact from another method of the same class) you have to make it a member of the instance, by defining it as self.score = .... You can then access it like this: your_question_1_window_instance.score.
Finally, if you have "ten classes for ten questions" you should try to find some common ground for all those questions and create either a common super class or even one class that can be parametrized to fit all the questions. You just need the title, the type (select one/select many) and a list of answers, and which ones are correct. Everything else -- creating the check boxes, checking the answer, etc. -- should always be the same.