简体   繁体   中英

How do you call a trace variable's function in which the variable itself is used

I am trying to call a function upon the user clicking on my Checkbox widget. I'm having difficulty with this as the trace variable itself is passed into the function.(I understand that it isn't the tkinter variable itself being passed, rather its contents)

The Code

#Image swapping code
mode = tk.IntVar()
checkBox = tk.Checkbutton(root,text=": World Mode",variable = mode)
checkBox.grid(row=3,column=3)
def swapimages(num):
    print(num)
    if num==1:
        imageLabel.configure(image = ukImg)
    else:
        imageLabel.configure(image = worldImg)
mode.trace("w", lambda: swapimages(mode.get()))

Error

I receive this error

TypeError: <lambda>() takes 0 positional arguments but 3 were given

This error does not occur when I use lambda for buttons eg

generateButton = tk.Button(root,text="Generate!",command=lambda: getLatLong(inputtedPostcode.get()))

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)

Callback functions for .trace() must accept three arguments, your lambda doesn't accept any.

Change your lambda like this to accept an arbitrary number of arguments:

mode.trace("w", lambda *args: swapimages(mode.get()))

When using a callback in a trace call, the callback should be able to accept 3 arguments that come from the "tkinter" side. You may modify it to accept your own arguments if you wish, but it must accept at least 3 from tkinter.

To see what these arguments are, we can check out the documentation or do a quick inspection on the example below:

import tkinter as tk

def swap_images(*args):
    print(f"I was passed {len(args)} arguments")
    for n, arg in enumerate(args):
        print(f"Arg {n}: {arg}")

root = tk.Tk()
mode = tk.StringVar(master=root, value="Hello", name="I'm the mode var!")
mode_entry = tk.Entry(master=root, textvariable=mode)
mode_entry.pack()
mode.trace("w", lambda *args: swap_images(*args))
root.mainloop()

Output from the swap_images function (when modifying the entry):

I was passed 3 arguments
Arg 0: I'm the mode var!
Arg 1:
Arg 2: w

We easily see the first argument is the internal name of the variable and the third is the operation being performed ( w riting).

This, along with this blurb from the documentation I linked above, is enough to figure out what the 2nd argument is:

When the trace triggers, three arguments are appended to commandPrefix so that the actual command is as follows:

commandPrefix name1 name2 op

name1 and name2 give the name(s) for the variable being accessed: if the variable is a scalar then name1 gives the variable's name and name2 is an empty string; if the variable is an array element then name1 gives the name of the array and name2 gives the index into the array; if an entire array is being deleted and the trace was registered on the overall array, rather than a single element, then name1 gives the array name and name2 is an empty string. name1 and name2 are not necessarily the same as the name used in the trace variable command: the upvar command allows a procedure to reference a variable under a different name.

op indicates what operation is being performed on the variable, and is one of read, write, or unset as defined above.

So what does this mean for you?

The fix is easy -- you need to change how you set your trace so that the lambda accepts the additional arguments. You don't have to use them and you don't even need to pass them to the function you call inside the lambda... But the lambda has to be able to accept them.

Change

mode.trace("w", lambda: swapimages(mode.get()))

to

mode.trace("w", lambda *args: swapimages(mode.get()))

Like I said, you can "throw args away" by not passing it to the function inside your lambda.

As my colleagues have stated, a callback function gets three arguments automatically, which you must handle. So, your lambda: should be lambda *a: and that makes the error go away. This will throw away the three arguments.

But the three arguments can be very helpful, particularly the first one, typically referred to as varname , which is the Tkinter internal name of the variable that caused the trace call. So, to preserve that info, you can change your lambda call to lambda *a: swapimages(*a, mode.get()) and your callback function could be:

import tkinter as tk
def swap_images(varname, varindex, varmode, num):
    # varname is the variable name. you can use it reinstantiate another variable:
    sv = tk.StringVar(name=varname)
    val1 = sv.get()
    # or you can use it to access the variable's value:
    val2 = root.globalgetvar(varname)

For the above, both val1 and val2 should be same as num . We'll skip varindex . But varmode is the callback mode that triggered this call. Here it will always be "w". Mostly the three variables are the most helpful when you have a single callback function handling multiple variables. If your callback only handles a single variable, you can ignore the three variables and tailor the callback to the one variable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM