A Tkinter Entry with boundaries - python

Currently, I am working on a Tkinter GUI. In this GUI, I would like to include an Entry field where users can only enter numbers between two boundaries (for instance, a number between 0.0 and 200.0). I have looked into the validatecommand option, but this still does not provide me a solution. Is there somebody who can help me to create a Tkinter Entry with boundaries?
Thank you in advance.

You can get the value by .get() method and then write an if statement to generate an error or reset the value by .set() method when limits are exceeded.

tkinter Entry validation allows to validate characters typed in the Entry field.
I do not know if the validation process built in tkinter allows to validate the entire entered value for being within a given range; however, you can use a common if-else test for that:
In the following example, the values of each entered character in the entry is validated for being a digit. then, upon retrieving the value entry.get, the value is checked to ascertain it is within the desired range, and printed in the console; if not, a ValueError is raised.
the line entry._get, entry.get = entry.get, _get_only_in_range makes a private copy of entry.get, then monkey patches it to inject the desired behavior
A better way is probably to write a class to encapsulate the behavior specific to your entry.
import tkinter as tk
def only_numbers(char): # validates each character as it is entered in the entry
if char.isdigit():
return True
else:
root.bell()
return False
def _get_only_in_range():
num = int(entry._get()) # uses the private copy of the original entry.get
if 0 <= num < 200:
return num
else:
raise ValueError(f'value {num} must be between 0 and 200')
if __name__ == "__main__":
root = tk.Tk()
validation = root.register(only_numbers)
entry = tk.Entry(root, validate="key", validatecommand=(validation, '%S'))
entry._get = entry.get # make private copy of original get
entry.get = _get_only_in_range # calling entry.get now calls _get_only_in_range to validate the value
entry.pack()
tk.Button(root, text='get value', command=lambda: print(entry.get())).pack() # here get points to _get_only_in_range
root.mainloop()

Related

Getting real time data in Tkinter

Consider the code below:
from tkinter import *
screen = Tk()
e =Entry()
e.pack()
screen.mainloop()
Now how to get to display the length of the characters entered in the e entry widget in real-time? It doesn't matter if the data is displayed in the GUI or Corresponding terminal
There are atleast 3 ways to do this here with one being better than the other:
Using trace from StringVar:
def func(*args):
print(len(var.get()))
var = StringVar()
e = Entry(screen,textvariable=var)
e.pack()
var.trace('w',func)
Every time the value of var is changed, func will be called.
Using bind to each key release:
def func(*args):
print(len(e.get()))
e.bind('<KeyRelease>',func)
Using after(ms,func) to keep repeating the function:
def func():
print(len(e.get()))
screen.after(500,func)
func()
As you can see, the first method is more efficient as it does not unnecessarily prints out values when you select all the items(with Ctrl+A) and so on. Using after() will be the most ridiculous method as it will keep printing the length always as there are no restrictions provided.

How do I retrieve the values from a particular grid location in tkinter?

I am working on my first GUI project, and I have placed my code at the bottom of the post (this is a work in progress, so please bear with any ugliness or inefficiency in the code).
I'm making a GURPS character sheet which will automate character creation for my players, and then (though it isn't implemented yet) spit out a nicely formatted PDF.
The way the program works currently, I have functions which perform cost calculations based on the desired rank in an attribute, derived attribute, or skill. Pressing the "calculate" button then spits out the point cost of taking the attribute or skill at the desired level.
I generate my rows using the while-loops near the end of the class definition. The loops call functions which tell the program to create rows that carry out a certain type of calculation.
By choice, all output values appear in column 4 of each row. I would like to know if there is a way for me to easily find the value of those columns and rows without tracking the values as I go. Perhaps a method, like .grid(column,row).get() or something that would return whatever is in some specific grid location.
class Character_sheet:
#Our default class which will house our character sheet.
def __init__(self):
#Total Point Calculator?
def sum_of_values():
list = self.grid_slaves(column=3)
sum = 0
for each in list:
sum += int(each["text"])
total_cost.set(sum)
#Generators for Rows and Columns.
def attr_widget_10(index):
#The below syntax/structure works.
def attr_10():
cost.set((rank.get()-10)*10)
return None
rank = IntVar()
rank.set(10)
cost = IntVar()
input = ttk.Entry(self.window, textvariable = rank).grid(column=2, row=index)
ttk.Button(self.window, text='Calculate', command=attr_10).grid(column=3,row=index)
ttk.Label(self.window, width=7, textvariable=cost).grid(column=4,row=index)
return None
def attr_widget_20(index):
def attr_20():
cost.set((rank.get()-10)*20)
return None
rank = IntVar()
rank.set(10)
cost = IntVar()
input = ttk.Entry(self.window, textvariable = rank).grid(column=2, row=index)
ttk.Button(self.window, text='Calculate', command=attr_20).grid(column=3,row=index)
ttk.Label(self.window, width=7, textvariable=cost).grid(column=4,row=index)
def derived_attr_widget(dictionary, index):
return None
def skill_widget(dictionary, index):
return None
def total_cost():
return None
#Basic window functions.
self.root = tk.Tk()
self.root.title('GURPS Character Sheet')
self.window = ttk.Frame(self.root)
self.window.grid()
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
"""Core Functionality:
Below are labels for set attributes. Each references an appropriate calculator.
This does not address skills.
For now, inputs start on row 1.
"""
#Labels for attributes and derived attributes.
#ATTRIBUTES
ttk.Label(self.window, width=10, text='Strength').grid(column=1, row=1)
ttk.Label(self.window, width=10, text='Health').grid(column=1, row=2)
ttk.Label(self.window, width=10, text='Intelligence').grid(column=1, row=3)
ttk.Label(self.window, width=10, text='Dexterity').grid(column=1, row=4)
#DERIVED ATTRIBUTES
ttk.Label(self.window, width=10, text='HP').grid(column=1,row=5)
ttk.Label(self.window, width=10, text='FP').grid(column=1,row=6)
ttk.Label(self.window, width=10, text='Will').grid(column=1,row=7)
ttk.Label(self.window, width=10, text='Perception').grid(column=1,row=8)
ttk.Label(self.window, width=10, text='Basic Speed').grid(column=1,row=9)
ttk.Label(self.window, width=10, text='Basic Move').grid(column=1,row=10)
index = 1
while index <= 2:
attr_widget_10(index)
index += 1
while index <= 4:
attr_widget_20(index)
index += 1
total_cost = IntVar()
#ttk.Button(self.window, text='Total Cost', command=sum_of_values).grid(column=2,row=index+1)
#ttk.Label(self.window, width=7, textvariable=total_cost).grid(column=4,row=index+1)
###CREATES WINDOW###
self.window.mainloop()
A couple of things to note right off:
stovfl's comment answers the question as it is written
I agree fully with furas' comment about separating the gui fully from the logic. Your code should be refactored- imo- so that the Sheet GUI should be separate from the Character as an abstract collection of statistics, and also should be separate from the code which executes/manages the GUI (which is currently all handled under the umbrella Character_sheet class).
While I'll leave fully disentangling the Character_sheet to you, we can at least get you started while developing a pattern for gaining access to the values in the GUI.
Each of the first 4 rows represent statistics that the user can change and relate to a label, which you created already. Two of the statistics have a cost modifier of 10, and the other two have a modifier of 20.
## Place in the global space for the time being
BASE_STATISTICS = ["Strength","Health","Intelligence","Will"]
## Note that prior to Python 3.7 dictionary order was not guaranteed, so
## collections.OrderedDict would be preferable for versions before that
STATISTIC_COSTS = {"Strength":10,"Health":10,"Intelligence":20,"Will":20}
(collections.OrderedDict)
Presumably, each given Character Sheet would have its own, independent widgets and values for these statistics. Again, you should rewrite the code to be more detached, but for now we'll preserve as much of your code as possible.
## Place at the top of Character_sheet.__init__
## The value for each stat is a dictionary in order to store arbitrary data until the code is reworked further
self.base_stats = {stat:{} for stat in BASE_STATISTICS}
With these additions we now have a framework for both referring to the widget rows that you are creating and for determining what the cost modifier is for those Statistics.
## This will replace the Label and attr_widget_X loops and functions
## You can place it where the Attributes labels currently are, and delete both attr_widget_x functions
## enumerate pairs each element of an iterable with a sequential integer
for i,stat in enumerate(BASE_STATISTICS):
## These IntVars are useful, so we'll keep them around
rank = IntVar()
rank.set(10)
cost = IntVar()
## We'll set up the gui just like you did, just with a minor tweak
ttk.Label(self.window, width=10, text=stat).grid(column=1, row=i)
ttk.Entry(self.window, textvariable = rank).grid(column=2, row=i)
## I've removed the Generate button for reasons I'll get into below
ttk.Label(self.window, width=7, textvariable=cost).grid(column=3,row=i)
## Here we save all our references so that we can come back to them later
## self.base_stats[stat]['row'] will tell us which row of the grid the widgets are located
## self.base_stats[stat]['rank'] will now give us direct access to the rank IntVar at all times
## self.base_stats[stat]['cost'] likewise gives us easy access to the cost IntVar whenever we need it
self.base_stats[stat].update({'row':i,'rank': rank,'cost':cost})
(enumerate)
Tkinter gives you access to different signal types; specifically for our uses, tkinter Variables can be bound using their trace method. By using the 'w' mode, whenever the Variable changes, the given callback (function) will be called. Using this we can make the GUI more responsive by getting rid of the need to constantly hit the Generate Button.
## This should go right after "cost = IntVar()"
## The lambda statement here is technically the function that is being passed to trace
## The lambda itself is capturing all information it gets passed as e
## stat = stat creates a reference within the lambda definition to the current value of stat
## (as you iterate, the stat value in the local scope will change, so we need to preserve it)
## and then calling self.updatestat and passing that the stat we're updating.
rank.trace('w',lambda *e,stat = stat: self.updatestat(stat))
(lambda)
And now we can add Character_sheet.updatestat so it actually functions:
def updatestat(self,stat):
""" Queries the current value of the stat's rank and then sets the cost appropriately """
## Get the IntVar for the given stat from your stats dict
rankvar = self.base_stats[stat]['rank']
## Since we're using an Entry (instead of e.g.- a spinbox), there's
## no garauntee that it contains a valid integer, so we use try/except
## to catch the mistake
try:
rank = rankvar.get()
rank = int(rank)
except:
## We'll reset the value if it's invalid
rank = 10
rankvar.set(rank)
## Use STATISTIC_COSTS to determine the cost modifier
## Calculate cost
cost = (rank - 10)*STATISTIC_COSTS[stat]
## find our IntVar for the given stat
costvar = self.base_stats[stat]['cost']
## Set it to cost
costvar.set(cost)
## Note that "return None" is the implicit default
And that gets you just a little closer to getting your GUI separated from your programming logic while allowing you to reference those values in the rows and columns like you were trying to do (i.e.- self.stats['Strength']['rank'].get())

How to get value from entry(Tkinter), use it in formula and print the result it in label

from Tkinter import *
top=Tk()
First Value A that user will input
A = Entry(top)
A.grid(row=1, column=1)
Second value B that user also inputs
B = Entry(top)
B.grid(row=1, column=2)
Calculation - Now I want to add those values (Preferably values with decimal points)
A1=float(A.get())
B1=float(B.get())
C1=A1+B1
Result - I want python to calculate result and show it to user when I input the first two values
C = Label(textvariable=C1)
C.grid(row=1, column=3)
top.mainloop()
First off, welcome to StackOverflow and nice job- your code does (mostly) everything you want it to do! The timing is just off- you create your objects and get the value, but the value hasn't been input by the user yet.
To solve that, you need to put your .get()'s in a function, and you should be using an actual text-variable that you set() after each one (if you just use C1=(float), you'll end up making new floats so the Label isn't pointing to the right one).
(setup... )
B.grid(...)
C1 = Tkinter.StringVar()
C = Label(textvariable=C1) # Using a StringVar will allow this to automagically update
def setC():
A1=float(A.get())
B1=float(B.get())
C1.set(str(A1+B1))
Additionally, you need to set this function so it goes off more than just "immediately on running the program". The simple way to do this is to just have the function call itself .after() some time (in milliseconds).
def setC():
# Body above
top.after(1000, setC) # Call setC after 1 second, so it keeps getting called.
setC() # You have to call it manually once, and then it repeats.
The slightly more advanced and efficient way to update involves events and bindings (binding setC() to fire every time A1 or B1 is changed), but the writeup on that is long so I'll give you that tip and send you to some documentation on that. (Effbot is good tkinter documentation regardless)

Entry Tkinter is not allowing to erase data if selected all and pressed delete or backspace button

I have written a code for entry widget which doesnot allow alphabets and limits the number of digits to 7. But i am not able to select all in the entry box and delete them using delete or backspace keys, could somebody help me on this.
My code snippet:
self.DelayLabel = ttk.Label(self)
self.DelayLabel["text"] = "timeout"
vcmd = (root.register(self.IntLength_Delay), '%P', '%S")
self.Delay = ttk.Entry(self, width = '5', validate = 'key', validatecommand = vcmd)
def IntLenght_Delay(self,value,text):
if text in '0123456789':
if len(value)<7:
return True
else:
return False
else:
return False
Answering an older question here, but I was having an extremely similar problem and wound up finding a decent solution. #Bryan Oakley's answer is useful, but doesn't provide an example, which is what I aim to do here.
After importing Tkinter or tkinter (depending on your Python version) as tk, first define the initial validation command:
val_cmd = (master.register(self.validate), '%P') # master is root in thie case.
Next, whenever you have an entry box needing validation, I found this to be a nice way to write it out:
self.entry = tk.Entry(your_frame_of_choice)
self.entry.insert('end', 100) # Inserts 100 as an initial value.
self.entry.config(validate='key', validatecommand=val_cmd)
This inserts a value before the validation begins. I originally had something like self.entry = tk.Entry(your_frame_of_choice, validate='key', validatecommand=val_cmd), but various iterations of the validation function that I toyed with rejected the insert code if the validation came before the insert. The one listed below doesn't care, but I've kept the entry code this way as I find it more aesthetically pleasing.
Finally, the actual function:
def validate(self, input_text):
if not input_text:
return True
try:
float(input_text)
return True
except ValueError:
return False
This checks for floats, but you could easily change it to ints, as you prefer. And, most importantly, it allows you to delete and write over any highlighted text in the entry box.
I'm sure the initial problem is long gone, but I hope someone in the future finds this to be helpful!
Follow the logic. let's say you've entered "987". You now select it and try to delete it. In your validation function text (the current value) will be "987". Your code isn't prepared for that so it will fail the first if statement. Since it fails, validation returns False, disallowing the edit.
You need to be prepared for what Tkinter passes to your function (a long string in the case of a deletion), and you need to explicitly allow an empty value after the edit.
Working in python 3.8.
%d == '0' -> delete text | %d == '-1' -> select text
import tkinter as tk
window = tk.Tk()
window.title('tkinter Entry')
def IntLength_Delay(action, key, content, value):
return True if not action == '1' or len(value) < 7 and (content != '' and key == '0' or key in '123456789') else False
vcmd=window.register(IntLength_Delay)
txt = tk.Entry(window,width=10,validate='key',validatecommand=(vcmd,'%d','%S','%s', '%P'))
txt.grid(column=1,row=1,sticky='news')
window.mainloop()

Limiting entry on a tk widget

I have trouble finding a way to limit the entry length of entry widgets, I would like to limit it to 20 characters, i.e. when I click on a sequence or the other I would like to be able to edit it but stay in the 20 characters limit. In or order to keep the code light , should I use a regex , a loop or check the entry with an event ?
Here is my code:
import Tkinter
from Tkinter import *
import tkFileDialog
root = Tkinter.Tk()
edit1 =StringVar()
edit2 =StringVar()
s = StringVar()
s = "GATACACGCGCGCGTATATATTACGCGCGCGATACA"
lb01=Label(root,text="sequence1")
lb01v=Entry(root,textvariable=edit1,width=20)
lb01v.delete(0, END)
lb01v.insert(0, s[6:20])
lb01.grid(sticky=W,row=1,column=1)
lb01v.grid(row=1,column=2)
lb02=Label(root,text="sequence2")
lb02v=Entry(root,textvariable=edit2,width=20)
lb02v.delete(0, END)
lb02v.insert(0, s[0:6])
lb02.grid(sticky=W,row=2,column=1)
lb02v.grid(row=2,column=2)
root.mainloop()
Ok I did try with the trace variable, on a short piece of test code , this is excactly what I was searching for !! I like the fact you can prototype so easily in Python ;)
def main():
pass
if __name__ == '__main__':
main()
from Tkinter import *
def callback(sv):
c = sv.get()[0:9]
print "c=" , c
sv.set(c)
root = Tk()
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(root, textvariable=sv)
e.pack()
root.mainloop()
I know its too late to add any answers to this, just found a simpler way to represent what Wabara had answered. This will help if you need multiple entry limits and each to a user-defined length limit. Here's a code working on Python 3.6.5:
def main():
pass
if __name__ == '__main__':
main()
from tkinter import *
def limit_entry(str_var,length):
def callback(str_var):
c = str_var.get()[0:length]
str_var.set(c)
str_var.trace("w", lambda name, index, mode, str_var=str_var: callback(str_var))
root = Tk()
abc = StringVar()
xyz = StringVar()
limit_entry(abc,3)
limit_entry(xyz,5)
e1 = Entry(root, textvariable=abc)
e2 = Entry(root, textvariable=xyz)
e1.pack()
e2.pack()
root.mainloop()
The simplest solution is to put a trace on the variable. When the trace fires, check the length of the value and then delete any characters that exceed the limit.
If you don't like that solution, Tkinter also has built-in facilities to do input validation on entry widgets. This is a somewhat under-documented feature of Tkinter. For an example, see my answer to the question Python/Tkinter: Interactively validating Entry widget content
I will start off by making an alphabet to measure from. The alphabet is a string and has 26 letters meaning its too long for our use. we want 20 letters only, so our output should be "A" thru "T" only.
I would define a function to make it happen and dump each string thru it that I would want cut to 20 characters or less.
I am making the below code in such a way that it takes as an input any string that is called it takes that input in and processes it to 20 characters in length only...
def twenty(z):
a = z[0:20]
return a
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
so to execute our newly made code, we need only call the print twenty command with the variable we want cut to 20 characters in the parenthesis.
print twenty(alphabet)
-----------------------------------------------------------------
OUTPUT:
ABCDEFGHIJKLMNOPQRST
So you see, it worked, we input the entire alphabet into the program and it cut the string down to 20 letters only. now every time in your code you want to cut text down to 20 letters, just run the command
twenty(variable)
and it will make sure you have no more letters than that.
Explanation:
def twenty is to define a function with one input that you can call on over and over simply by typing twenty(variable)
the next line is a = z[0:20] Meaning call variable "a" to equal the input from position 0 to position 20 and dont worry about anything past that.
return command is how you get an output from the def function. anytime you create a def function, you should end it with a line.

Categories