What are the arguments to Tkinter variable trace method callbacks?
The first argument is the internal variable name. You can use this name as an argument to the tkinter getvar
and setvar
methods. If you give your variable a name (eg: StringVar(name='foo')
) this will be the given name, otherwise it will be a name generated for you by tkinter (eg: PYVAR0
)
If the first argument represents a list variable (highly unlikely in tkinter), the second argument will be an index into that list. If it is a scalar variable, the second argument will be the empty string.
The third argument is the operation, useful if you are using the same method for reading, writing and/or deleting the variable. This argument tells you which operation triggered the callback. It will be one of "read", "write", or "unset".
Tkinter is a python wrapper around a tcl/tk interpreter. The definitive documentation for variable traces can be found here: http://tcl.tk/man/tcl8.5/TclCmd/trace.htm#M14. Though, this only documents how the internal trace works, the tkinter wrapper sometimes massages the data.
How do I pass arguments to a callback for a StringVar?
Is there any way to have my callback function (hptrace) take hpstring as an argument?
You can use lambda or functools.partial. The important thing to remember is that the function called by the trace is called with three arguments, so whatever command you give to trace
needs to accept those three arguments.
Using lambda
, your code should look something like this if you want to send only hpstring
to hptrace
:
hpstring.trace("w", lambda var_name, var_index, operation: hptrace(hpstring))
Note: you need to define hptrace
before you attempt to use it, which the code in your question fails to do.
How do you call a trace variable's function in which the variable itself is used
When you put a trace on a variable, tkinter will call the function with three arguments. Your lambda needs to accept those arguments even if you don't use them.
Since you aren't using the arguments, you can define your lambda like this:
mode.trace("w", lambda *args: swapimages(mode.get()))
Another solution is to directly call your function, and have your function use the arguments. For example, the first argument is the name of the variable. You can use the function getvar
on the root window to get the value of a widget by name.
Example:
def swapimages(varname, _, operation):
num = root.getvar(varname)
print("num:", num)
mode.trace("w", swapimages)
For more information about what the arguments are, see What are the arguments to Tkinter variable trace method callbacks?
All of that being said, the checkbutton widget is able to directly call a function when the value changes, without having to rely on tracing variables. This is the more common way of performing a function when a checkbutton is clicked.
Example:
def swapimages():
num = mode.get()
print("num:", num)
checkBox = tk.Checkbutton(root,text=": World Mode",
onvalue=1, offvalue=0, variable=mode,
command=swapimages)
Calling a Tkinter var trace with arguments
Your lambda
is what must accept the arguments and pass them on to your function, since it is the lambda that is being called when the variable changes. The simplest fix to make your code work is to change the lambda to this:
test.trace('w', lambda *args, passed = passarg: checkvar(passed, *args))
You say you're using this for input validation. Are you aware that the entry widget has a built-in feature for entry validation? See Interactively validating Entry widget content in tkinter
How to pass string var in callback function using trace() in tkinter
To being in with, the usage of trace
is wrong, meaning, the positional argument passed is wrong, it has to be 'w'
instead of 'write'
. Then next your calling the function when you use ()
, so you need to use lambda
for that. So you trace
would be like:
my_var1.trace('w', lambda *_,var=my_var1: my_callback(*_,var=var)) #*_ are the other arguments, like varname,value,mode ?
my_var2.trace('w', lambda *_,var=my_var2: my_callback(*_,var=var))
Then your function would be like:
def my_callback(*args,var):
print("Traced variables {}".format(var.get()))
TIP:
Its better for entry1
and entry2
to not be None
, so say:
entry1 = tkinter.Entry(main_wd, textvariable = my_var1)
entry1.pack(padx = 5, pady = 5)
entry2 = tkinter.Entry(main_wd, textvariable = my_var2)
entry2.pack(padx = 5, pady = 5)
When saying pack()
on same line as the declaration, it will return what the last method returns. In this case, pack()
returns None
. So to make those entry widgets reusable, pack()
them on separate line. Same applies to grid()
and place()
.
Final Code:
import tkinter
from tkinter import StringVar
main_wd = tkinter.Tk()
my_var1 = StringVar()
my_var2 = StringVar()
def my_callback(*args,var):
print("Traced variables {}".format(var.get()))
my_var1.trace('w', lambda *_,var=my_var1: my_callback(*_,var=var))
my_var2.trace('w', lambda *_,var=my_var2: my_callback(*_,var=var))
entry1 = tkinter.Entry(main_wd, textvariable = my_var1)
entry1.pack(padx = 5, pady = 5)
entry2 = tkinter.Entry(main_wd, textvariable = my_var2)
entry2.pack(padx = 5, pady = 5)
main_wd.mainloop()
How can I access the particular tkinter StringVar that triggers a trace callback?
You could simply pass the arguments you want by making use of lambda
statement for anonymous functions. Replace:
def validate(self, *args):
print(self)
print(self.get())
...
cb1_var.trace('w', validate)
cb2_var.trace('w', validate)
with:
def validate(var):
print(var)
print(var.get())
...
cb1_var.trace('w', lambda *_, var=cb1_var: validate(var))
cb2_var.trace('w', lambda *_, var=cb2_var: validate(var))
If you use multiple objects that are related, simply use collection types. For the example in the question, I see a list
should be a good fit.
See the example below:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
def upon_var_change(var):
print(var.get())
if __name__ == '__main__':
root = tk.Tk()
cbs = list()
for i in range(3):
cbs.append(ttk.Combobox(root))
cbs[i].var = tk.StringVar()
cbs[i].var.trace_add('write', lambda *_,
var=cbs[i].var:upon_var_change(var))
cbs[i]['textvariable'] = cbs[i].var
cbs[i].grid(row=i // 2, column=i % 2, sticky='nw')
tk.mainloop()
If such is required you could identify the Variable class from its internal reference as well:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
def upon_var_change(var_name):
value = root.tk.globalgetvar(var_name)
print(var_name, value)
if __name__ == '__main__':
root = tk.Tk()
cbs = list()
for i in range(3):
cbs.append(ttk.Combobox(root))
cbs[i].var = tk.StringVar()
cbs[i].var.trace_add('write', lambda var_name,
*_: upon_var_change(var_name))
cbs[i]['textvariable'] = cbs[i].var
cbs[i].grid(row=i // 2, column=i % 2, sticky='nw')
tk.mainloop()
Related Topics
How to Access Command Line Arguments
How to Get a String After a Specific Substring
How to Use Xpath with Beautifulsoup
Perform Commands Over Ssh with Python
How to Initialize a Two-Dimensional Array in Python
Intuition and Idea Behind Reshaping 4D Array to 2D Array in Numpy
I Need to Securely Store a Username and Password in Python, What Are My Options
Where Is Python's Sys.Path Initialized From
How to Detach Matplotlib Plots So That the Computation Can Continue
How to Split a String of Space Separated Numbers into Integers
Generate 'N' Unique Random Numbers Within a Range
Why am I Seeing "Typeerror: String Indices Must Be Integers"
How to Efficiently Parse Fixed Width Files
How to Create a Guid/Uuid in Python
Importerror: No Module Named 'Encodings'
Comparing Two Lists Using the Greater Than or Less Than Operator