I have a tkinter Entry with a validate command, to be executed when the entry gets focus ("focusin"). This Entry is associated with a StringVar. It seems that whenever the StringVar changes value, the Entry gets focus, triggering the validation command. For example:
import Tkinter as tk
window = tk.Tk()
var = tk.StringVar()
def validate(*args):
print("Validation took place")
return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")
var.set("Text")
print("Value changed")
entry.pack()
tk.mainloop()
This code generates the following output:
Entry created. Associating textvariable
textvariable associated. Changing value
Validation took place
Value changed
Note that the validation command was executed, caused by a call to var.set.
Is there a way for me to change the value of the StringVar without causing its associated Entry to gain focus? I can't temporarily disassociate the StringVar from the Entry, because when re-associating them, the Entry also gains focus.
I think your observation is incorrect: the focus does not change when you set the value of the StringVar. This is likely just an edge case that only happens when your application first starts up. The widget probably gets focus when it is initially created. Once the GUI is up and running, setting the variable won't change the focus.
The official tk documentation discourages the use of both validation and the use of a variable. From the documentation:
In general, the textVariable and validateCommand can be dangerous to mix. Any problems have been overcome so that using the validateCommand will not interfere with the traditional behavior of the entry widget.
There's no reason to use a StringVar in this case. My recommendation is to remove it. The use of a StringVar is generally only useful if you use the same variable for more than one widget, or you're putting a trace on the variable. You are doing neither so just stop using the StringVar. It adds an extra object to manage without providing much extra benefit.
One possible workaround is to disable the validation event before setting the stringvar, and re-enable it afterwards.
import Tkinter as tk
window = tk.Tk()
var = tk.StringVar()
def validate(*args):
print("Validation took place")
return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")
entry.config(validate="none")
var.set("Text")
entry.config(validate="focusin")
print("Value changed")
entry.pack()
tk.mainloop()
If you expect to do this a lot, you could even write a context manager so you don't accidentally forget to re-enable validation after you're finished altering the text.
import Tkinter as tk
from contextlib import contextmanager
#contextmanager
def temp_change(widget, **kargs):
old_values = {key:widget.cget(key) for key in kargs}
widget.config(**kargs)
try:
yield #wait for `with` block to finish executing...
finally:
widget.config(**old_values)
window = tk.Tk()
var = tk.StringVar()
def validate(*args):
print("Validation took place")
return True
entry = tk.Entry(validate="focusin", validatecommand=validate)
print("Entry created. Associating textvariable")
entry.config(textvariable=var)
print("textvariable associated. Changing value")
with temp_change(entry, validate="none"):
var.set("Text")
print("Value changed")
entry.pack()
tk.mainloop()
Related
I bring up here a problem, that's been there for ages, but is obviously still not solved and older workarounds don't work on my Python 3.7.2 (64-bit on Win10).
I have this code:
import tkinter as tk
import tkinter.simpledialog
# message box to enter a value where to set the scale to
class EnterValueBox(tk.simpledialog.Dialog):
def body(self, master):
self.e = tk.Entry(self, width=10)
self.e.pack(pady=5)
return self.e # initial focus
def apply(self):
print(self.e.get())
# callback to open message box
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
# create window with scale widget
root = tk.Tk()
scale = tk.Scale(root, orient=tk.HORIZONTAL, from_=0, to=100)
scale.pack()
# unbind any button-3 events
scale.unbind("<ButtonPress-3>")
scale.unbind("<ButtonRelease-3>")
scale.unbind("<Button-3>")
# bind button-3 press event to open message box
scale.bind("<ButtonPress-3>", enterValue)
tk.mainloop()
It creates a window with a single scale widget. I want to bind ButtonPress-3 to open a little dialog to directly enter a new value. The code only prints that value to the shell, but the example shows, that the unbind is not working, because after printing the value, the dialog box is closed (when the user clicks OK) and then the default binding is executed, which sets the slider, where the user clicked in the trough of the slider widget.
I tried the workaround from Deleting and changing a tkinter event binding with a PatchedScale widget (instead of the PatchedCanvas shown there), but that didn't make any difference.
Any help would be greatly appreciated!
The default bindings are not on the widget, they are on the widget class. Calling unbind on a widget for which there is no widget-specific binding won't have any effect.
If you don't want the default binding to run after your widget-specific binding, the normal technique is to have your bound function return the string break.
def enterValue(event):
EnterValueBox(root, title="Enter Value 0..100")
return "break"
When programming with tkinter I have found a very strange behaviour of the Checkbutton widget. I have re-created the bug with the code below:
import tkinter
from tkinter import *
def displayWelcomeScreen(root):
root2 = Toplevel(root)
root2.geometry('600x380')
root2.focus_set()
Checked = IntVar()
CheckButton1 = Checkbutton(root2, variable=Checked)
CheckButton1.place(relx=0.5, rely=0.5, anchor=CENTER)
CheckButton1.select()
# Create a dummy button that makes the Checkbutton appear checked to the user
#Button(root2, command= lambda event: Checked.get())
root = Tk()
root.geometry('700x400')
displayWelcomeScreen(root)
root.mainloop()
When a new window is created with Toplevel(root) and I put a Checkbutton inside it, it does not appear checked to the user even though I use the .select() method.
However, when I create a dummy button whose command mentions the IntVar associated with my Checkbutton, somehow it is initialised as checked properly. It's almost as if the compiler checks whether the Checkbutton will be useful and decides based on that whether it will display it as selected or not.
EDIT: The Checkbutton is definitely checked under the hood because if I run print(Checked.get()) before and after the CheckButton1.select() command, the value is changed, it just doesn't appear to the user.
Does anyone know why this happens?
EDIT 2: Thanks to jasonharper's explanation, I have added the line CheckButton1.intvar = Checked and it worked without needing the dummy button. When the function went out of scope, the Checked variable got lost so the Checkbutton had nowhere to store its state, therefore we needed to keep a reference to it so it didn't disappear.
I want to print the text of an Entry each time a new character is written.
By doing this with binding and a command to the widget the last character isn't printed.
I guess that the parameter 'textvariable' is getting updated after binding command had been executed. How to fix this?
from tkinter import *
master = Tk()
var = StringVar()
def execute_e(key):
print(var.get())
E = Entry(master, width=30, textvariable=var)
E.pack()
E.bind('<Key>', execute_e)
It's because the bound event function is being executed before the new key has been added.
Here's a simple workaround that uses the ability to add validation to an Entry widget (the validator accepts any key because it always returns True). The trick is that validator function is set-up to receive the value that the text will have if the change is allowed by specifying the %P when it's configuration as part of the Entry construction via the validatecommand=(validator_command, '%P').
Here's some documentation about adding validation to Entry widgets with details about how it works.
from tkinter import *
master = Tk()
var = StringVar()
def validator(new_value):
print(f'new_value: {new_value}')
return True
validator_command = master.register(validator)
E = Entry(master, width=30, textvariable=var,
validate='key',
validatecommand=(validator_command, '%P'))
E.pack()
master.mainloop()
I feel like this is a question of event handler. When a key is typed, your code first register a key press and execute the bound command execute_e. Only after it has processed this and your event handler has return will it update the entry with the character and proceed to update the tkinter variable.
Your print command therefore comes in before your variable have been updated and you print the previous version of your variable. If you try deleting a character from your entry, you'll see that you get the previous string with the character you have just erased.
The easiest way around that problem for you is probably to bind the command to the tkinter variable rather than the keybind. Do so using trace when the variable is writen like so :
var.trace('w', execute_e)
There are also some methods to manipulate the event handler and decide in which order to execute commands. root.after_idle will execute a command when the code has nothing else to do (when it has computed everything else you asked it to do). Try out this version of your code :
from tkinter import *
master = Tk()
var = StringVar()
def execute_e(*key):
def printy():
print(var.get())
master.after_idle(printy)
E = Entry(master, width=30, textvariable=var)
E.pack()
E.bind('<Key>', execute_e)
master.mainloop()
I am trying to set or update the command of an OptionMenu after its instantiation.
The widget.configure(command=foo) statement works for Button and CheckButton, but not for OptionMenu.
The following code raises this error: _tkinter.TclError: unknown option "-command"
from Tkinter import Tk, OptionMenu, StringVar
root = Tk()
var = StringVar()
def foo(val):
print val, var.get()
widget = OptionMenu(root, var, "one", 'two')
widget.configure(command=foo)
widget.pack()
root.mainloop()
Good question! Its a good thing I never had to do this in any one of my projects before because (unless someone proves me wrong here) you can't set/update the command of a OptionMenu widget once its already defined.
If Tkinter wanted you to be able to do that, it definitely would've included it to be edited by .configure()
There is a handy function called .keys() which you can call with a widget object to see all available traits that can be used with .configure().
Button example:
from tkinter import *
master = Tk()
def callback():
print ("click!")
b = Button(master, text="OK", command=callback)
print (b.keys()) #Printing .keys()
b.pack()
mainloop()
Which results in :
Notice how in this huge list of keys, 'command' is on the second line? That is because a button's command CAN be used in .configure()
OptionMenu example:
from tkinter import *
root = Tk()
var = StringVar()
def foo(val):
print ("HI")
widget = OptionMenu(root, var, "one", 'two')
print(widget.keys())
widget.pack()
root.mainloop()
Which results in:
Notice how there is no 'command' on line 2 this time. This is because you cant configure command with an OptionMenu widget.
Hopefully this problem doesn't hinder your program too much and I hope my answer helped you understand better!
I think what you're really asking is how to associate a command to an Optionmenu, rather than update a command (there is no command, so there's nothing to update).
If you want a function to be called every time a value is selected from an Optionmenu, you can add a trace on the related variable. The trace will call a function whenever that variable changes, whether through the Optionmenu or any other means.
For example:
...
var = tk.StringVar()
def foo(*args):
print "the value changed...", var.get()
var.trace("w", foo)
...
When the function is called it will pass three arguments, which you can safely ignore in this case.
For more information on variable traces see http://effbot.org/tkinterbook/variable.htm
You might also want to consider switching to the ttk combobox. It supports binding to <<ComboboxSelected>>, which is every so slightly less clunky than doing a variable trace.
It is possible to change the commands associated with OptionMenu widets if you're careful (as #Bryan Oakley commented). Below is an example of doing it.
The tricky part is you have to reconfigure all the menu items, not just one of them. This requires some extra bookkeeping (and some processing overhead, but it's unnoticeable).
Initially the menu has three items, each with a different function to be called when selected, one of which changes the menu. If the latter is selected the menu is changed to only have two menu items both of which call the same function.
from tkinter import *
root = Tk()
var = StringVar()
var.set('Select')
def foo(value):
var.set(value)
print("foo1" + value)
def foo2(value):
var.set(value)
print("foo2 " + value)
def foo3(value):
var.set(value)
print("foo3 " + value)
def change_menu(value):
var.set('Select')
print('changing optionmenu commands')
populate_menu(optionmenu, one=foo3, two=foo3)
def populate_menu(optionmenu, **cmds):
menu = optionmenu['menu']
menu.delete(0, "end")
for name, func in cmds.items():
menu.add_command(label=name, command=
lambda name=name, func=func: func(name))
optionmenu = OptionMenu(root, var, ()) # no choices supplied here
optionmenu.pack()
Label(root, textvariable=var).pack()
populate_menu(optionmenu, one=foo, two=foo2, change=change_menu)
root.mainloop()
I'm trying to associate a variable with a Tkinter entry widget, in a way that:
Whenever I change the value (the "content") of the entry, mainly by typing something into it, the variable automatically gets assigned the value of what I've typed. Without me having to push a button "Update value " or something like that first.
Whenever the variable gets changed (by some other part of the programm), I want the entry value displayed to be adjusted automatically. I believe that this could work via the textvariable.
I read the example on http://effbot.org/tkinterbook/entry.htm, but it is not exactly helping me for what I have in mind. I have a feeling that there is a way of ensuring the first condition with using entry's "validate". Any ideas?
I think you want something like this. In the example below, I created a variable myvar and assigned it to be textvariable of both a Label and Entry widgets. This way both are coupled and changes in the Entry widget will reflect automatically in Label.
You can also set trace on variables, e.g. to write to stdout.
from tkinter import *
root = Tk()
root.title("MyApp")
myvar = StringVar()
def mywarWritten(*args):
print "mywarWritten",myvar.get()
myvar.trace("w", mywarWritten)
label = Label(root, textvariable=myvar)
label.pack()
text_entry = Entry(root, textvariable=myvar)
text_entry.pack()
root.mainloop()