What Are the Arguments to Tkinter Variable Trace Method Callbacks

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



Leave a reply



Submit