If i want an entry box in Tkinter that only accepts floating point numbers that are greater than or equal to 0.0 and less than or equal to 1.0 how would i do that?
The proper way it to use tkinter's validate capabilities. But it's really a PIA to use.
dsgdfg has a good answer, but I can make that a lot neater, robust, and more dynamic:
import Tkinter as tk
class LimitedFloatEntry(tk.Entry):
'''A new type of Entry widget that allows you to set limits on the entry'''
def __init__(self, master=None, **kwargs):
self.var = tk.StringVar(master, 0)
self.var.trace('w', self.validate)
self.get = self.var.get
self.from_ = kwargs.pop('from_', 0)
self.to = kwargs.pop('to', 1)
self.old_value = 0
tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
def validate(self, *args):
try:
value = self.get()
# special case allows for an empty entry box
if value not in ('', '-') and not self.from_ <= float(value) <= self.to:
raise ValueError
self.old_value = value
except ValueError:
self.set(self.old_value)
def set(self, value):
self.delete(0, tk.END)
self.insert(0, str(value))
You use it just like an Entry widget, except now you have 'from_' and 'to' arguments to set the allowable range:
root = tk.Tk()
e1 = LimitedFloatEntry(root, from_=-2, to=5)
e1.pack()
root.mainloop()
If you want to call a button to check whether, this is a way to do it.
from tkinter import *
class GUI():
def __init__(self, root):
self.Entry_i = Entry(root, bd = 5)
self.test = StringVar()
Label_i = Label(root, textvariable = self.test)
Button_i = Button(root, text = "Go", command = self.floatornot)
self.Entry_i.grid()
Label_i.grid()
Button_i.grid()
mainloop()
def floatornot(self):
test = self.Entry_i.get()
if float(test) < 0 or float(test) > 1:
self.test.set("Good")
else:
self.test.set("")
root = Tk()
GUI(root)
The button will call the floatornot function. This will get the value of the entry and check if it okay or not. Depending on the result the value of the label will be changed.
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()
I've cloned a class called ListBoxChoice found on the web found (adding some needed features) below:
from Tkinter import *
class ListBoxChoice(object):
def __init__(self, master=None, title=None, message=None,\
list=[]):
self.master = master
self.value = None
self.list = list[:]
self.modalPane = Toplevel(self.master)
self.modalPane.transient(self.master)
self.modalPane.grab_set()
self.modalPane.bind("<Return>", self._choose)
self.modalPane.bind("<Escape>", self._cancel)
if title:
self.modalPane.title(title)
if message:
Label(self.modalPane, text=message).pack(padx=5, pady=5)
listFrame = Frame(self.modalPane)
listFrame.pack(side=TOP, padx=5, pady=5)
scrollBar = Scrollbar(listFrame)
scrollBar.pack(side=RIGHT, fill=Y)
# get the largest value of the 'list' to set the width
widthOfList = 0
for k in list:
if len(str(k)) > widthOfList:
widthOfList = len(str(k))
# now pad some space to back of the widthOfList
widthOfList = widthOfList + 2
self.listBox = Listbox(listFrame, selectmode=SINGLE,\
width=widthOfList)
self.listBox.pack(side=LEFT, fill=Y)
scrollBar.config(command=self.listBox.yview)
self.listBox.config(yscrollcommand=scrollBar.set)
self.list.sort()
for item in self.list:
self.listBox.insert(END, item)
buttonFrame = Frame(self.modalPane)
buttonFrame.pack(side=BOTTOM)
chooseButton = Button(buttonFrame, text="Choose",\
command=self._choose)
chooseButton.pack()
cancelButton = Button(buttonFrame, text="Cancel",\
command=self._cancel)
cancelButton.pack(side=RIGHT)
def _choose(self, event=None):
try:
firstIndex = self.listBox.curselection()[0]
self.value = self.list[int(firstIndex)]
except IndexError:
self.value = None
self.modalPane.destroy()
def _cancel(self, event=None):
self.modalPane.destroy()
def returnValue(self):
self.master.wait_window(self.modalPane)
return self.value
if __name__ == '__main__':
import random
root = Tk()
returnValue = True
list = [random.randint(1,100) for x in range(50)]
while returnValue:
returnValue = ListBoxChoice(root, "Number Picking",\
"Pick one of these crazy random numbers",\
list).returnValue()
print returnValue
Now this example says to do something like this:
results = ListBoxChoice(root, list=listOfItems).returnValue().
What I'm trying to do is provide a list of values from which the user selects a single value. The window should close before I use the results from the selected value. Here is that code:
from tkinter import Tk, Label
form ListBoxChoice import ListBoxChoice
...
eventList = ["20190120","20190127","20190203"]
root = Tk()
root.withdraw() # This causes the ListBoxChoice object not to appear
selectValue = ListBoxChoice(root, title="Event",\
message="Pick Event", list=eventList).returnValue()
root.wait_window() # Modal Pane/window closes but not the root
print("selectValue:", selectValue)
A root window is placed behind the modalPane (Toplevel). I have to close that window before the calling process continues. So there is a block in place.
I've tried to put a sleep(1.01) command above but had no impact.
How do I get the ListBoxChoice to close once the selection has been made
before my print statement of the selectValue? For it is at that point I want to use the results to plot data.
If I don't use root.wait_winow(), it is only when the plot is closed (end of the process) that the ListBoxChoice box close as well.
Suggestions?
Slightly updated
Here's a version of the ListBoxChoice class which I think works the way you desire. I've updated my previous answer slightly so the class is now defined in a separate module named listboxchoice.py. This didn't change anything I could see when I tested—it other words it still seems to work—but I wanted to more closely simulate the way you said you're using it the comments.
It still uses wait_window() because doing so is required to give tkinter's mandatory event-processing-loop the opportunity to run (since mainloop() isn't called anywhere). There's some good background material in the article Dialog Windows about programming tkiner dialogs you might find useful. The added root.withdraw() call eliminates the issue of not being able to close it because it's not there. This is fine since there's no need to have the empty window being displayed anyway.
test_lbc.py
import random
try:
import Tkinter as tk # Python 2
except ModuleNotFoundError:
import tkinter as tk # Python 3
from listboxchoice import ListBoxChoice
root = tk.Tk()
root.withdraw() # Hide root window.
values = [random.randint(1, 100) for _ in range(50)]
choice = None
while choice is None:
choice = ListBoxChoice(root, "Number Picking",
"Pick one of these crazy random numbers",
values).returnValue()
print('choice: {}'.format(choice))
listboxchoice.py
""" ListBoxChoice widget to display a list of values and allow user to
choose one of them.
"""
try:
import Tkinter as tk # Python 2
except ModuleNotFoundError:
import tkinter as tk # Python 3
class ListBoxChoice(object):
def __init__(self, master=None, title=None, message=None, values=None):
self.master = master
self.value = None
if values is None: # Avoid use of mutable default argument value.
raise RuntimeError('No values argument provided.')
self.values = values[:] # Create copy.
self.modalPane = tk.Toplevel(self.master, takefocus=True)
self.modalPane.bind("<Return>", self._choose)
self.modalPane.bind("<Escape>", self._cancel)
if title:
self.modalPane.title(title)
if message:
tk.Label(self.modalPane, text=message).pack(padx=5, pady=5)
listFrame = tk.Frame(self.modalPane)
listFrame.pack(side=tk.TOP, padx=5, pady=5)
scrollBar = tk.Scrollbar(listFrame)
scrollBar.pack(side=tk.RIGHT, fill=tk.Y)
# Get length the largest value in 'values'.
widthOfList = max(len(str(value)) for value in values)
widthOfList += 2 # Add some padding.
self.listBox = tk.Listbox(listFrame, selectmode=tk.SINGLE, width=widthOfList)
self.listBox.pack(side=tk.LEFT, fill=tk.Y)
scrollBar.config(command=self.listBox.yview)
self.listBox.config(yscrollcommand=scrollBar.set)
self.values.sort()
for item in self.values:
self.listBox.insert(tk.END, item)
buttonFrame = tk.Frame(self.modalPane)
buttonFrame.pack(side=tk.BOTTOM)
chooseButton = tk.Button(buttonFrame, text="Choose", command=self._choose)
chooseButton.pack()
cancelButton = tk.Button(buttonFrame, text="Cancel", command=self._cancel)
cancelButton.pack(side=tk.RIGHT)
def _choose(self, event=None):
try:
firstIndex = self.listBox.curselection()[0]
self.value = self.values[int(firstIndex)]
except IndexError:
self.value = None
self.modalPane.destroy()
def _cancel(self, event=None):
self.modalPane.destroy()
def returnValue(self):
self.master.wait_window(self.modalPane)
return self.value
What would be the best way to give an error and tell the user to only input numbers if they type letters as an input? Code that doesn't work:
if self.localid_entry.get() == int(self.localid_entry.get():
self.answer_label['text'] = "Use numbers only for I.D."
The variable is obtained in Tkinter with:
self.localid2_entry = ttk.Entry(self, width=5)
self.localid2_entry.grid(column=3, row=2)
The best solution is to use the validation feature to only allow integers so you don't have to worry about validation after the user is done.
See https://stackoverflow.com/a/4140988/7432 for an example that allows only letters. Converting that to allow only integers is trivial.
Bryan has the correct answer, but using tkinter's validation system is pretty bulky. I prefer to use a trace on the variable to check. For instance, I can make a new type of Entry that only accepts digits:
class Prox(ttk.Entry):
'''A Entry widget that only accepts digits'''
def __init__(self, master=None, **kwargs):
self.var = tk.StringVar(master)
self.var.trace('w', self.validate)
ttk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
self.get, self.set = self.var.get, self.var.set
def validate(self, *args):
value = self.get()
if not value.isdigit():
self.set(''.join(x for x in value if x.isdigit()))
You would use it just like an Entry widget:
self.localid2_entry = Prox(self, width=5)
self.localid2_entry.grid(column=3, row=2)
Something like this:
try:
i = int(self.localid_entry.get())
except ValueError:
#Handle the exception
print 'Please enter an integer'
""" Below code restricts the ttk.Entry widget to receive type 'str'. """
import tkinter as tk
from tkinter import ttk
def is_type_int(*args):
item = var.get()
try:
item_type = type(int(item))
if item_type == type(int(1)):
print(item)
print(item_type)
except:
ent.delete(0, tk.END)
root = tk.Tk()
root.geometry("300x300")
var = tk.StringVar()
ent = ttk.Entry(root, textvariable=var)
ent.pack(pady=20)
var.trace("w", is_type_int)
root.mainloop()
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 have the following code:
class Test:
def __init__(self, master): # master is a Tk or Toplevel instance
self.master, self.typeFrame = master, tk.Frame(master)
self.typeVar = tk.StringVar(self.typeFrame)
for column, wordType in enumerate(['Noun', 'Verb', 'Adjective', 'Adverb'], 1):
typeRadioButton = tk.Radiobutton(self.typeFrame, text = wordType, textvariable = self.typeVar, value = wordType, command = self.createLambda(wordType))
typeRadioButton.grid(row = 1, column = column)
self.typeFrame.grid()
def createLambda(self, obj):
return lambda: self.changeInfo(obj)
def changeInfo(self, obj):
pass # this will do something later
However, when I run the code like this, the Radiobuttons have no text associated with them.
root = tk.Tk()
test_instance = Test(root)
test_instance.master.mainloop()
How can I fix this? Thanks in advance!
Change textvariable=... to variable=....
BTW, your example does not contain self.typeFrame.pack() or self.typeFrame.grid(..).