tkinter - Why is global variable not changed after function? - python

window = Tk() #create app window
#custom message box function
def answerMessagebox(returnValue, toplevel, functionName=""):
global answer
answer = returnValue
print("The answer variable is (inside answerMessagebox function): ", answer)
if functionName: #if there is a command do this
functionName()
toplevel.destroy() #close messagebox
def messageboxYesNo(title, text, functionName=""):
toplevel = Toplevel(window)
toplevel.title(title)
l1=Label(toplevel, image=iconQuestion)
l1.grid(row=0, column=0, pady=(7, 0), padx=(10, 30), sticky="e")
l2=Label(toplevel,text=text)
l2.grid(row=0, column=1, columnspan=3, pady=(7, 10), sticky="w")
b1=Button(toplevel,text="Yes",command=lambda: answerMessagebox(True, toplevel, functionName=functionName),width = 10)
b1.grid(row=1, column=1, padx=(2, 35), sticky="e")
b2=Button(toplevel,text="No",command= lambda: answerMessagebox(False, toplevel), width = 10)
b2.grid(row=1, column=2, padx=(2, 35), sticky="e")
def close_window():
driver.quit()
window.destroy()
exit()
exitButton = Button(window, text="Exit", command=lambda: messageboxYesNo("QUIT", "Are you sure you want to Quit?", functionName=close_window), font=breadtextFont, bg=button_color, fg=button_text_color) #add a exit button
exitButton.pack(side="top", anchor=NE, padx=15, pady=10)
window.mainloop()
The print statement inside answerMessagebox prints out correctly False if I press No-button and True if I press Yes-button. I want to use that answer inside another function and do logic based on the answer.
def foo():
global answer
answer = ""
messageboxYesNo("Submit report", "Are you sure you want to submit?") #expect global variable to change to True if press Yes-button. But it actually prints out nothing.
if answer:
#do something
Inside the foo()function the global variable answerdoes not change after I run the messageboxYesNo function and therefore the if answer: statements is not running.
Why is the global variable not changing? I guess it is because foo() continues and is not waiting for messageboxYesNo function to finish. How can I solve this issue?
I use a global variable because the command= in tkinter cannot return anything from a function.

Your code does not show when foo() is being executed, however at the beginning you are already overriding the value of answer with an empty string, which later evaluates to false:
def foo():
global answer
answer = "" # Remove this line
There are many issues with this usage of global variables, and an OOP refactor would be the best approach to address it. However, a quick solution for this specific problem would be passing the result from answerMessagebox to the callback:
def answerMessagebox(returnValue, toplevel, functionName=""):
answer = returnValue
print("The answer variable is (inside answerMessagebox function): ", answer)
if functionName:
functionName(answer)
# ...
def foo(answer):
# ...

The last print statement is executed soon after you press the Exit button. It simply creates the UI for the labels and the Yes/No buttons, prints the last statement and exists the function.
When you press the Yes/No buttons, it executes the answerMessagebox function. If you want something to execute after the changes to answer have been made, either add it to the end of the answerMessagebox function or (maybe in the case that it doesn't make sense to have it on the same function) execute a separate function at the end of the function:
def answerMessagebox(returnValue, toplevel, functionName=""):
answer = returnValue
# The logic you've written already
toplevel.destroy() #close messagebox
everythingAfterYesNoPress(answer)
def everythingAfterYesNoPress(answer = ""):
print("This is answer variable after messageboxYesNo function", answer)
Edit:
I think what you're looking for is wait_window.
Initialize toplevel inside the foo function before the messageboxYesNo function and pass it as a parameter to the function instead of defining it inside (of course, you'd have to change the messageboxYesNo function a bit). Then add the wait_window line after executing the function:
toplevel = Toplevel(window)
messageboxYesNo(toplevel, "Submit report", "Are you sure you want to submit?")
window.wait_window(toplevel)

Related

Running a function in Tkinter [duplicate]

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Make tkinter buttons call the same function with different arguments? [duplicate]

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Why does tkinter (python) create two windows at the same time if command called with additional parameter? [duplicate]

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

python- using delay between tkinter's listbox prints

All i want to do is to make a time delay between each print on the listbox that is included on the GUI .
i havent managed to get a result so far. i tried to use time.sleep () ... and after method.
here is my code :
from Tkinter import *
def func() :
i = int (e.get())
for x in range (0,i):
listbox.insert (END, i)
i-=1
master = Tk()
master.title("hi")
e=Entry (master )
e.pack()
listbox = Listbox(master)
listbox.pack()
b = Button(master, text="get", width=10, command=func)
b.pack()
mainloop()
When you're using a GUI, you should always use after instead of sleep. If you sleep, the GUI will stop updating, so you won't get refreshed displays and nothing will work like you want it to.
To get the desired result in your code, you have several options. One of them would be using after to call the function that inserts into the Listbox and pass a new argument to it for each iteration.
First, you'll have to modify the Button command to pass the initial argument to the function using a lambda expression:
b = Button(master, text="get", width=10, command=lambda: func(int(e.get())))
Next, structure your function like this:
def func(arg):
listbox.insert(END, arg) #insert the arg
arg -= 1 #decrement arg
master.after(1000, func, arg) #call function again after x ms, with modified arg
Note: You'll also need to make the function return if arg is less than 0, or it'll run forever ;)

How to pass arguments to a Button command in Tkinter?

Suppose I have the following Button made with Tkinter in Python:
import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)
The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?
I have tried with the following code:
button = Tk.Button(master=frame, text='press', command=action(someNumber))
This just invokes the method immediately, and pressing the button does nothing.
See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.
If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.
I personally prefer to use lambdas in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
This can also be done by using partial from the standard library functools, like this:
from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Example GUI:
Let's say I have the GUI:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
What Happens When a Button Is Pressed
See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
with:
button_press_handle(btn['command'])
You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.
Calling a Method(Callback) When the Button is Pressed
Without arguments
So if I wanted to print something when the button is pressed I would need to set:
btn['command'] = print # default to print is new line
Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.
With Argument(s)
Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Calling Multiple Methods when the Button Is Pressed
Without Arguments
You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
With Argument(s)
In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
and then set:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Returning Object(s) From the Callback
Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.
Complete Example with global Object Modification(s)
Below example will call a method that changes btn's text each time the button is pressed:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
Mirror
Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:
import tkinter as tk
def callback(text):
print(text)
top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]
for i, z in enumerate(Texts):
Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
Buttons[i].pack(side=tk.LEFT, padx=5)
top.mainloop()
By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.
Python's ability to provide default values for function arguments gives us a way out.
def fce(x=myX, y=myY):
myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)
See: https://tkdocs.com/shipman/extra-args.html
For more buttons you can create a function which returns a function:
def fce(myX, myY):
def wrapper(x=myX, y=myY):
pass
pass
pass
return x+y
return wrapper
button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:
import tkinter as tk
class Callback:
def __init__(self, func, *args, **kwargs):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self):
self.func(*self.args, **self.kwargs)
def default_callback(t):
print("Button '{}' pressed.".format(t))
root = tk.Tk()
buttons = ["A", "B", "C"]
for i, b in enumerate(buttons):
tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)
tk.mainloop()
Use lambda
import tkinter as tk
root = tk.Tk()
def go(text):
print(text)
b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()
output:
hello
One simple way would be to configure button with lambda like the following syntax:
button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I believe should fix this
The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.
To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:
import Tkinter as Tk
frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
global output
global variable
output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()
if __name__ == '__main__':
Tk.mainloop()
What I would do is make a class whose objects would contain every variable required and methods to change those as needed:
import Tkinter as Tk
class Window:
def __init__(self):
self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
self.frame.grid(row=2,column=2)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
self.button = Tk.Button(master=self.frame, text='press', command=self.action)
self.button.pack()
self.variable = Tk.Entry(master=self.frame)
self.variable.pack()
self.output = Tk.Text(master=self.frame)
self.output.pack()
def action(self):
self.output.insert(Tk.END,self.variable.get())
if __name__ == '__main__':
window = Window()
Tk.mainloop()
The best thing to do is use lambda as follows:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
I am extremely late, but here is a very simple way of accomplishing it.
import tkinter as tk
def function1(param1, param2):
print(str(param1) + str(param2))
var1 = "Hello "
var2 = "World!"
def function2():
function1(var1, var2)
root = tk.Tk()
myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()
You simply wrap the function you want to use in another function and call the second function on the button press.
Lambdas are all well and good, but you can also try this (which works in a for loop btw):
root = Tk()
dct = {"1": [*args], "2": [*args]}
def keypress(event):
*args = dct[event.char]
for arg in args:
pass
for i in range(10):
root.bind(str(i), keypress)
This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.
I have encountered this problem before, too.
You can just use lambda:
button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):
event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))
def test_event(event_text):
if not event_text:
print("Nothing entered")
else:
print(str(event_text))
# do stuff
This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.
JasonPy - a few things...
if you stick a button in a loop it will be created over and over and over again... which is probably not what you want. (maybe it is)...
The reason it always gets the last index is lambda events run when you click them - not when the program starts. I'm not sure 100% what you are doing but maybe try storing the value when it's made then call it later with the lambda button.
eg: (don't use this code, just an example)
for entry in stuff_that_is_happening:
value_store[entry] = stuff_that_is_happening
then you can say....
button... command: lambda: value_store[1]
hope this helps!
For posterity: you can also use classes to achieve something similar. For instance:
class Function_Wrapper():
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
def func(self):
return self.x + self.y + self.z # execute function
Button can then be simply created by:
instance1 = Function_Wrapper(x, y, z)
button1 = Button(master, text = "press", command = instance1.func)
This approach also allows you to change the function arguments by i.e. setting instance1.x = 3.
You need to use lambda:
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Categories

Resources