This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I'm trying to dynamically build some buttons in tkinter from a set of data stored in list form. The thing I can't work out is how to put an argument in the callback function. As the code in the callback isn't executed until the time of the callback, the variable used in the callback has changed value by this point.
Here's a (very) simplified version of the code:
from Tkinter import *
from ttk import *
mylist = ['a','b','c','d','e']
class App:
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack()
for i in range (0,len(mylist)):
setattr(self, 'button' + str(i), Button(self.frame, text=mylist[i], command= lambda: self.buttoncall(i)))
getattr(self, 'button' + str(i)).pack(side=LEFT)
def buttoncall(self,input):
print mylist[input]
root = Tk()
app = App(root)
root.mainloop()
Set a default value for i:
command=lambda i=i: self.buttoncall(i)
When the callback is called with no arguments, the default value for i will be bound to the local variable i. Since the default value is fixed at the time the lambda is defined, the value of i will be the desired one.
Without the default value for i, when the callback is called i is not a local variable. Python instead finds the value of i in the enclosing scope. By the time the callback is called, however, the for-loop has completed and i is equal to len(mylist)-1. So all the callbacks use the same value of i.
Related
Tkinter dynamically made variables are not working properly in checkbutton of menu. They are displaying the wrong image as they were supposed to.
Here's my code:
def checkbutton(self,index,var=None):
self.popup_menu.add_checkbutton(label=self.btns[index]['text'], command = lambda : self.menu(index) , variable=IntVar().set(1))
I'm using direct method variable=IntVar().set(1). I aslo tried making variable like :
currentVar=IntVar()
currentVar.set(1)
But I encountered the same problem.
First variable=IntVar().set(1) will assign None, result of set(1) to variable option. Second dynamically created variable will be garbage collected after the function completes.
You need to create an instance variable:
def checkbutton(self,index,var=None):
var1 = IntVar(value=1)
self.popup_menu.add_checkbutton(label=self.btns[index]['text'], command=lambda: self.menu(index), variable=var1)
# self.varlist should be already created in __init__()
self.varlist.append(var1)
I wrote some code using tkinter and some nested functions (see code below) and get the warning in pycharm "Global variable 'value' is undefined at the module level". The program works as intended (the window title is renamed to 'text 0') but i still get the warning... What do i have to change to get rid of this warning? (the program is more complex, i need to start it like it does, with window.after and i also need all the functions)
from tkinter import *
def function_1():
global value
window.title("text " + value)
def function_2():
def function_3():
global value
value = ent.get()
if value == '0':
function_1()
ent = Entry()
ent.pack()
button = Button(text="ok", command=function_3)
button.pack()
window = Tk()
window.after(0, function_2)
window.mainloop()
The global keyword is used inside a function to control assignment to a variable. Normally within a function value = "bar" creates a "value" in the local function namespace. global value in that same function tells python to use the global namespace instead.
In your current code, the module level value variable will not come into existence until function_3 is called. Anything that tries to use value before function_3 is called will get a NameError. function_1 is a case in point; if it is called before function_3, you have an error. It may be that your code can never call function_1 before function_3 but that is difficult for a linter or future maintiainers of the code to know. That's why you get the warning.
The solution is to set a default for value that is sensible for the program. I don't know what works for you, but here is an example that throws up a message. BTW, global value is not needed in function_1. Its job is to control assignment, you can read global variables without any additional annotation.
from tkinter import *
value = None
def function_1():
if value is not None:
window.title("text " + value)
else:
import tkinter.messagebox
tkinter.messagebox.showerror("Error", "Do the thing before the thing")
def function_2():
def function_3():
global value
value = ent.get()
if value == '0':
function_1()
ent = Entry()
ent.pack()
button = Button(text="ok", command=function_3)
button.pack()
window = Tk()
window.after(0, function_2)
window.mainloop()
Basically I have a bunch of checkbuttons, with some on and some off by default. I'm having an issue where if I put the checkbuttons inside the function they aren't on by default like they should be.
Here's the working code:
from tkinter import *
root = Tk()
integer = IntVar(value=1)
Checkbutton(root, text="Should be on by default", variable=integer).grid()
root.mainloop()
Here's the not working code:
from tkinter import *
root = Tk()
def main():
integer = IntVar(value=1)
Checkbutton(root, text="Should be on by default", variable=integer).grid()
main()
root.mainloop()
Can anyone explain to me why this is?
By the time you see the window, integer no longer exists and the checkbox shows as unchecked for lack of a variable to store its state.
Compare:
from tkinter import *
root = Tk()
def main():
global integer
integer = IntVar(value=1)
Checkbutton(root, text="Should be on by default", variable=integer).grid()
main()
root.mainloop()
The global integer tells Python that this integer should be defined at the 'global' level and thus it stays around after the function.
By the way, it's bad practice to name a variable after its type - try picking a name that represents what its value means, instead of describing its type.
You shared some additional code with a similar problem (only repeating the elements that matter):
from tkinter import *
def change_job_skills(name):
top_window = Toplevel(root)
# ..
skill_dictionary = {}
# ..
row_ = 2
column_ = 0
# ..
job_focuses_dictionary = {}
for key in sorted(job_focuses_dictionary.keys()):
Checkbutton(top_window, text=key.strip(""),
variable=job_focuses_dictionary[key]).grid(row=row_, column=column_, sticky=W)
# ..
# no definition was provided of actually_change_job_skills, but it's not important here
Button(top_window, text='Submit',
command=lambda: [actually_change_job_skills(skill_dictionary, name),
top_window.destroy()]).grid(row=0, column=0, sticky=W)
# no reference is made to `job_focuses_dictionary` in a way that survives the function
root = Tk()
change_job_skills("Community Engagement")
root.mainloop()
Although both skill_dictionary and job_focuses_dictionary are used in the code of change_job_skills, skill_dictionary is used in the definition of a lambda function, which is then passed as the command argument for Button. Since the button will need to call that function later, a reference to the lambda is saved inside it and since the function body of the lambda references skill_dictionary, the dictionary survives the function returning.
However, job_focuses_dictionary is only referenced as job_focuses_dictionary[key], retrieving a value from it - the dictionary itself isn't passed to anything that maintains a reference to it outside the function, so when the function returns, the dictionary is garbage-collected.
Same problem, but a bit harder to spot. (#acw1668 pointed it out in the comments as well)
Note that I also renamed your parameter Name to name, you should reserve capitalised names for types and lowercase names for variables, in line with Python standard naming, which helps future you and others more quickly read and understand your code. Nothing to do with the problem though.
This question already has answers here:
How to pass an argument to event handler in tkinter?
(7 answers)
Closed 8 years ago.
In this example, that I used from http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm, how could you pass in more variables to the function callback. Say you wanted to pass in a dict that you created and use that dict, how could you do that without calling global variables.
from Tkinter import *
root = Tk()
def callback(event):
print "clicked at", event.x, event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
Newb here, might have some awkward phrasing, feel free to ask for clarification.
Here are two possible ways.
You can put your 'callback' function in a class, and use self to pass in self-defined variables
You can set all your variables in another module file and import it to this file. Since variables from another file have been imported to this namespace, you can use them without passing into 'callback' function, just the same as global variables.
How can I get a returned object from a function that is executed as a Tkinter callback?
import Tkinter as Tk
from functools import partial
def square(x):
return x*x
root = Tk.Tk()
var = Tk.IntVar(root, value=0) #the variable the gets passed to the class call
menu = Tk.OptionMenu(root, var, *[0,1,2,3,4,5]) #a drop-down list to choose a value for the variable
menu.pack()
button = Tk.Button(root, text='click', command = partial(square,var.get())) #a button that calls the class
button.pack()
root.mainloop()
Obviously this is a simplified example. In reality the function called by the button will return objects, which I wish to append to a list of objects that will be held in the main Python namespace for further operations.
Anyway, here the user is able to choose an argument for the function using a GUI, and press a button that will execute the function. The return value of the function, however, seems doomed to be lost to the aether, since the callback won't accept returns. Can this be overcome without the use of an ugly global in the definition of square(x)?
The notion of "returning" values from callbacks doesn't make sense in the context of an event driven program. Callbacks are called as the result of an event, so there's nowhere to return a value to.
As a general rule of thumb, your callbacks should always call a function, rather than using functools.partial or lambda. Those two are fine when needed, but if you're using an object-oriented style of coding they are often unnecessary, and lead to code that is more difficult to maintain than it needs to be.
For example:
def compute():
value = var.get()
result = square(value)
list_of_results.append(result)
button = Tk.Button(root, text='click', command = compute)
...
This becomes much easier, and you can avoid global variables, if you create your application as a class:
class App(...):
...
def compute():
...
result = self.square(self.var.get())
self.results.append(result)
Sorry for being 6 years late, but recently I figured out a good way to do this without making your code messy and hard to maintain.
This is pretty much what DaveTheScientist has said, but I just want to expand on it a little.
Usually, in Tkinter, if you want to have a button call a function, you do the following:
exampleButton = Button(root, text = 'Example', command = someFunc)
This will simply call someFunc whenever the button is pressed. If this function, however, takes arguments, you need to use lambdas and do something like this:
exampleButton = Button(root, text = 'Example', command = lambda: someFunc(arg1, arg2))
The above line of code will run someFunc and use the variables arg1 and arg2 as arguments for that function. Now, what you could do in a program where, a lot of the times, you would need the functions run by buttons to return values, is create a new function which is called by every button.
This function takes the function you want your button to run as a first argument, and that function's arguments afterwards.
def buttonpress(function, *args):
value = function(*args)
Then when you create the button, you do:
exampleButton = Button(root, text = 'Example', command = lambda: buttonpress( someFunc, arg1, arg2 ))
This will run the given function (in this case, someFunc) and store the returned value in the value variable. It also has the advantage that you can use as many arguments as you want for the function your button runs.
Just create an actual function that is called by your button, instead of putting it all inline like that.
button=Tk.Button(parent, text='click', command=someFxn)
def someFxn(): your code
Then in your function just call the var.get(), do your calculation, and then do something with the value.