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
Related
I am making a simple tkinter popup where you can type a message.
In the textbox itself, I inserted the text "Type your message here" with a grey colour and when clicked, the inserted text is deleted so the user can type in their own message. In addition, the colour of the text typed by the user is set to black.
However, when I was testing I realised that this will only happen if they click the textbox with a mouse button. My question is, is there a way for tkinter to automatically run a command when a condition is changed? For example, if the textbox is empty, the font colour should be set to black.
I tried putting if-statements in the tk.mainloop, but sadly that didn't work.
Any ideas?
this is my (hopefully) simplified version of the code:
from tkinter import *
def changecolor(event):
if textbox.get("1.0", "end-1c") == "Type your message here":
textbox.delete("1.0", "end")
textbox.config(fg='black')
root = Tk()
canvas = Canvas(root, height=400, width=600)
canvas.pack()
textbox = Text(canvas, font=40, fg="grey")
textbox.insert(1.0, "Type your message here")
textbox.bind("<Button-1>", changecolor)
textbox.pack()
root.mainloop()
~finally found out how to format code here.
Take a look at this class I created that does similar to what your code does, do try it on.
from tkinter import *
class PlaceholderText(Text):
def __init__(self,master,placeholder,placeholdercolor='black',fg='grey',**kwargs):
Text.__init__(self,master,**kwargs) #init the text widget
self.placeholder = placeholder
self.fgcolor = fg
self.placeholdercolor = placeholdercolor
self.has_placeholder = False #make flag
self.add() #run the function to add placeholder
self.bind('<FocusIn>',self.clear) #binding to focusin and not button-1
self.bind('<FocusOut>',self.add) #function wil get triggered when widget loses focus
def clear(self,event=None):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #condition to clear a placeholder
self.delete('1.0','end-1c') #delete the placeholder
self.config(fg=self.fgcolor) #change the color
self.has_placeholder = False #set flag to flase
def add(self,event=None):
if self.get('1.0','end-1c') == '' and not self.has_placeholder: #condition to add placeholder
self.insert('1.0',self.placeholder) #add placeholder
self.has_placeholder = True #set flag to true
self.config(fg=self.placeholdercolor) #change text color to what you specify?
def ret(self,index1,index2):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #gives none if there is nothing in the widget
return 'None'
else:
return self.get(index1,index2) #else gives the text
root = Tk()
pl = PlaceholderText(root,placeholder='Type something here...')
pl.pack()
e = Entry(root) #dummy widget to switch focus and check
e.pack(padx=10,pady=10)
root.mainloop()
I've explained to through the comments. But keep in mind its not the best of classes yet, you have to do add a lot more methods in to make it more efficient.
Just if your wondering on how to do this without classes, then:
from tkinter import *
has_placeholder = False #make flag
placeholder = 'Type Something Here...' #the text to be inserted
def clear(event=None):
global has_placeholder
if a.get('1.0','end-1c') == placeholder and has_placeholder: #condition to clear a placeholder
a.delete('1.0','end-1c') #delete the placeholder
a.config(fg='grey') #change the color
has_placeholder = False #set flag to flase
def add(event=None):
global has_placeholder
if a.get('1.0','end-1c') == '' and not has_placeholder: #condition to add placeholder
a.insert('1.0',placeholder) #add placeholder
has_placeholder = True #set flag to true
a.config(fg='black') #change text color to normal
root = Tk()
a = Text(root)
a.pack()
add() #add the placeholder initially
a.bind('<FocusIn>',clear) #binding to focus and not button-1
a.bind('<FocusOut>',add)
e = Entry(root) #dummy widget to show focus loss
e.pack()
root.mainloop()
Why not to use classes if the latter method is more easier? This is not reusable, say you want to add one more Text widget, that cannot have such property, while using a custom class with custom class you can have as many as text widgets with same properties you like.
Do let me know if any doubts.
You can simply add a <Key> binding to your text widget and use your changecolor function to determine what state your textbox is in.
#Give a hoot. Don't pollute. :D
import tkinter as tk
txtmsg = "Type your message here"
def changecolor(event):
text = textbox.get("1.0", "end-1c")
#customize accordingly
if text:
if text == txtmsg:
print("text is txtmsg")
else:
print("text is user text")
else:
print("text is empty")
#FYI:
#whether this was a button press or key press DOES NOT have string equality
#if you need to create button vs key conditions
#use tk.EventType.ButtonPress and tk.EventType.KeyPress
#or learn the .value and compare that
print(event.type, type(event.type), event.type.value)
root = tk.Tk()
textbox = tk.Text(root, font=40, fg="grey")
textbox.insert(1.0, txtmsg)
textbox.pack()
#add events
for ev in ['<Key>', '<1>']:
textbox.bind(ev, changecolor)
root.mainloop()
i have defined a GUI class that creates a tkinter window with a couple of entries. I would like that every time that the user overwrites the Entries and press Enter, there is some operation done in the background. In addition, i would like that the entries are checking regularly certain values and updating them, so the user could see if they changed; In the example below i use a static dictionary, but normally those parameters are obtained from a camera and could fluctuate.
However, i am not even able to get the correct value printed in the label. I am not a tkinter expert so any idea would be appreciated
from tkinter import *
class GUI():
def __init__(self, window, window_title,input_dict):
self.window = window
self.window.title(window_title)
self.window.geometry('400x200')
top_frame = Frame(self.window)
top_frame.pack(side=TOP, pady=5)
Label(top_frame, text="Frame rate (fps)").grid(row=0)
Label(top_frame, text="Exposure time (ms)").grid(row=2)
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.e1_var = StringVar() # or StringVar(top)
self.e1_var.set(str(round(input_dict['frameRate'])))
self.e2_var = StringVar() # or StringVar(top)
# print(type(self.e2_var))
self.e2_var.set(str(round(input_dict['Exp_time'])))
self.fps_entry = Entry(top_frame,textvariable=self.e1_var)
self.exptime_entry = Entry(top_frame,textvariable=self.e2_var)
self.fps_entry.bind("<Return>",self.my_tracer)
self.exptime_entry.bind("<Return>",self.my_tracer)
self.fps_entry.grid(row=0, column=1)
self.exptime_entry.grid(row=2, column=1)
self.window.mainloop()
def my_tracer(self,event):
val1=int(self.e1_var.get())
val2=int(self.e2_var.get())
self.labeling.configure(text=str(val1*val2))
input_dict = {
'frameRate': 50,
'Exp_time': 5000}
video_object=GUI(Tk(),"Test",input_dict)
The error your code produces is AttributeError: 'NoneType' object has no attribute 'configure', right?
Look at this line:
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.labeling will be None because grid() returns None. It is indeed bad practice to 'chain' a geometry manager to the creation of a widget. Change to:
self.labeling=Label(top_frame, text="Result")
self.labeling.grid(row=3)
Now the labels are updating when the user enters a new value.
I'm trying to use tkinter message boxes in a way so that if a user provides an invalid entry, an error message box appears and, after selecting "okay," they have the opportunity to provide a new entry. However, no subsequent calculations should take place until the entry is valid.
In this simple example, the user is asked to input a positive number, which is then doubled. In the first portion of the script, I've included code to ensure the user cannot enter letters or special symbols. Then I use an if-then to double my number if it is positive and produce an error message box if it isn't. Of course, there's nothing here as of yet that suspends the calculation until the input is valid. This is where I'm struggling.
from tkinter import Tk, Label, Entry, Button, messagebox
import re
def validate(string):
regex = re.compile(r"(\+|\-)?[0-9.]*$")
result = regex.match(string)
return (string == ""
or (string.count('+') <= 1
and string.count('-') <= 1
and string.count('.')<=1
and result is not None
and result.group(0) != ""))
def on_validate(P):
return validate(P)
window = Tk()
window.title("My Window")
window.geometry('800x800')
window.configure(bg='lightgrey')
input_label=Label(window,bg='lightgray',text="Enter positive number:")
input_label.grid(row=0, column=0,padx=10, pady=10)
# Get input and test for valid characters
entry = Entry(window, validate="key",width=20)
vcmd = (entry.register(on_validate), '%P')
entry.config(validatecommand=vcmd)
entry.grid(row=0,column=1,padx=10,pady=10)
output_label=Label(window,width=20)
output_label.grid(row=1, column=0,padx=10, pady=10)
def _compute():
input=float(entry.get())
if input<=0:
messagebox.showerror("Error","Input must be positive!")
# What can I do here to suspend the process until my input is valid?
else:
input=float(entry.get())
output_label.configure(text=str(2*input))
compute_button = Button(master=window, text="Compute",bg='lightgray',command=_compute)
compute_button.grid(row=2, column=0,padx=10, pady=10)
def _quit():
window.quit()
window.destroy()
quit_button = Button(master=window, text="Quit", bg='lightgray',command=_quit)
quit_button.grid(row=2, column=1,padx=10, pady=10)
window.mainloop()
I have a GUI in python that, amongst other things, let's the user choose items from a dropdown menu (I used the combobox feature from tkinter).
I wish to have it so that when the item "Custom" is select, an input box appears to ask the user what their custom number would be. I don't want to have to use a button for the box to appear, but somehow have it so that as soon as custom is selected, the input box appears. I tried first with while loops but I can't get it to work, same with if statements :s
I also tried using the askinteger() method that I found here (http://effbot.org/tkinterbook/tkinter-entry-dialogs.htm) but none of the combinations I came up with worked (I am very new at Python and am still learning so excuse my obvious mistakes).
Here is my code for the GUI :
from tkinter import *
from tkinter.ttk import *
from tkinter import filedialog
from tkinter import StringVar
from tkinter import messagebox
from tkinter import simpledialog
class MyGUI:
def __init__(self, master):
self.master = master
master.title("Intent/Interpretation Check")
self.runlabel = Label(master, text="RunID :")
self.runlabel.grid(row=0, column=0)
self.runentry = Entry(master)
self.runentry.grid(row=1, column=0, padx=25)
self.checklabel = Label(master, text="Check type :")
self.checklabel.grid(row=0, column=1)
self.typeselect = Combobox(master)
self.typeselect['values']=("Intent Score", "Interpretation Score")
self.typeselect.grid(row=1, column=1, padx=25)
self.limitlabel = Label(master, text="Fails if score is below :")
self.limitlabel.grid(row=0, column=2, padx=25)
self.limitselect = Combobox(master)
self.limitselect['values']=(1000, 5000, "Custom")
self.limitselect.grid(row=1, column=2, padx=25)
if self.limitselect.get() != "Custom":
self.limit = self.limitselect.get()
pass
else:
self.askinteger("Custom limit", "Please enter a number from 1 to 10000", minvalue=1, maxvalue=10000)
self.submitbutton = Button(master, text="Submit", command=self.checkstatus)
self.submitbutton.grid(row=1, column=3, padx=25, pady=5)
root = Tk()
root.geometry("+600+300")
my_gui = MyGUI(root)
root.mainloop()
Thank you very much in advance !
You need to have a Bool that tells when to show the new input should be shown.
You also need to be constantly polling the ComboBox to see if it's value is equal to "Custom". This is what I came up with in about 3 minutes.
I didn't try to make the GUI look pretty, just a functional example.
from tkinter import *
from tkinter.ttk import *
class Gui:
def __init__(self):
self.root = Tk()
# Set up the Combobox
self.selections = Combobox(self.root)
self.selections['values'] = ['Apples', 'Oranges', 'Blueberries', 'Bananas', 'Custom']
self.selections.pack()
# The Entry to be shown if "Custom" is selected
self.custom_field = Entry(self.root)
self.show_custom_field = False
# Check the selection in 100 ms
self.root.after(100, self.check_for_selection)
def check_for_selection(self):
'''Checks if the value of the Combobox equals "Custom".'''
# Get the value of the Combobox
value = self.selections.get()
# If the value is equal to "Custom" and show_field is set to False
if value == 'Custom' and not self.show_custom_field:
# Set show_field to True and pack() the custom entry field
self.show_custom_field = True
self.custom_field.pack()
# If the value DOESNT equal "Custom"
elif value != 'Custom':
# Set show_field to False
self.show_custom_field = False
# Destroy the custom input
self.custom_field.destroy()
# Set up a new Entry object to pack() if we need it later.
# Without this line, tkinter was raising an error for me.
# This fixed it, but I don't promise that this is the
# most efficient method to do this.
self.custom_field = Entry(self.root)
# If the value IS "Custom" and we're showing the custom_feild
elif value == 'Custom' and self.show_custom_field:
pass
# Call this method again to keep checking the selection box
self.root.after(100, self.check_for_selection)
app = Gui()
app.root.mainloop()
Hope this helps!
EDIT:
To open a new window instead of packing it inside the same window as the Combobox, replace the function check_for_selection with this:
def check_for_selection(self):
value = self.selections.get()
# If the value is equal to "Custom" and show_field is set to False
if value == 'Custom' and not self.show_custom_field:
# Set show_field to True and pack() the custom entry field
self.show_custom_field = True
# Create a new window how we did when we made self.root
self.new_window = Tk()
# Create the Entry that will go in the window. The previous Entry widget from line 16, can be removed
self.custom_field = Entry(self.new_window)
self.custom_field.pack()
# Run the new window like we did the original
self.new_window.mainloop()
# If the value DOESNT equal "Custom"
elif value != 'Custom':
# Destroy the new window that was created if it exists
if self.show_custom_field:
self.new_window.destroy()
# Set show_field to False
self.show_custom_field = False
# If the value IS "Custom" and we're showing the custom_feild
elif value == 'Custom' and self.show_custom_field:
print('yes')
# Call this method again to keep checking the selection box
self.root.after(100, self.check_for_selection)
I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)