Python - returning from a Tkinter callback - python

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.

Related

Variable in bind in tkinter python module

I was wondering if there is any way to get around using global variables in the callback functions that are used in bind in tkinter.
What I refer to is:
canvas = Canvas(root, width=500, height=500)
canvas.bind('<B1-Motion>', func)
where func is now some function that is triggered when the mouse is dragged. What I want is something like:
canvas.bind('<B1-Motion>', func(arg))
In combination with:
def func(event, arg):
commands
I can see from https://docs.python.org/3/library/tkinter.html that one argument, which is the event itself, is given to the callback function, but it seems like waste of potential to not give this method any way to modify its callback in a different way.
Maybe I am mistaken and there is some technical reason why that is impossible in general or maybe there is an alternative to bind.
I was basically expecting something like:
buttoname = Button(...,...,..., command = Lambda: func(arg))
If anyone has any pointers, it would be much appreciated.
regards
Use a lambda that receives the event parameter and passes it along.
canvas.bind('<B1-Motion>', lambda e: func(e, arg))

Understanding Python Lambda behavior with Tkinter Button [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I would like to understand how a button is working using lambda.
I have the following Python code:
from tkinter import *
def comando_click(mensagem):
print(mensagem)
menu_inicial = Tk()
menu_inicial.geometry("500x250+200+200")
botao = Button(menu_inicial, text = "Executar", command=comando_click("Nova_Mensagem"))
botao.pack()
menu_inicial.mainloop()
But my button doesn't work when I click on it, it only shows the print once in the console when I run the code, I added some prints here in the question:
Problem Picture one
Well it seems that when I use the Lambda function in the button it works and I really would like to know why.
Lambda working button Picture one
I just added to the button the lambda :
botao = Button(menu_inicial, text = "Executar", command=lambda:comando_click("Nova_Mensagem"))
Lambda working button Picture two
Why with lambda it works?
It shoudn't work without lambda too since lambda is basically a anonymous function?
I am extremely curious to understand why it works, thank you all for the help :)
Edit: I would like to thank you guys, now I finally understand what was going on and how Python was working. Thank you all very much :D
When you use () with a function name(func(args)), then it is immediately calling/invoking the function while python is executing the line, you do not want that. You want to ONLY call the function when the button is clicked. tkinter will internally call the function for you, all you have to do is give the function name.
Why use lambda? Think of it as a function that returns another function, your code can be lengthened to:
func = lambda: comando_click("Nova_Mensagem")
botao = Button(menu_inicial, text = "Executar", command=func)
func is the function name and if you want to call it, you would say func(). And when you say command=comando_click("Nova_Mensagem") then command has the value returned by command click(because you call the function with ()), which is None and if I'm not wrong, if the given value is None, it will not be called by tkinter. Hence your function is executed just once because of () and as a result of calling the function, you are assigning the value of the function call(None) before the event loop starts processing the events.
Some other methods:
Using partial from functools:
from functools import partial
botao = Button(.....,command=partial(comando_click,"Nova_Mensagem"))
Using a helper function:
def helper(args):
def comando_click():
print(args)
return comando_click
botao = Button(...., command=helper("Nova_Mensagem"))
IMO, lambdas are the easiest way to proceed with calling a function with arguments.
In this code:
command=comando_click("Nova_Mensagem")
you have called the comando_click function, once, and assigned the result (None) to the command argument. Nothing will happen when command is called (in fact you should get a TypeError exception because None is not callable).
In this code:
command=lambda:comando_click("Nova_Mensagem")
you have not actually called comando_click yet -- you have created a new function (using lambda) that will in turn call comando_click when it is called. Every time the button is clicked, your new function will get called.
If the lambda is confusing, you can do the exact same thing with a def like this:
def button_command():
comando_click("Nova_Mensagem")
...
command=button_command # no ()! we don't want to actually call it yet!
The lambda expression is just an alternative to using def when you want to create a small single-use function that doesn't need a name (e.g. you want to make a function that calls another function with a specific argument, exactly as you're doing here).
The issue is that with comando_click("Nova_Mensagem") you are executing the function. So command=None.
In the second case lambda:comando_click("Nova_Mensagem") is returning a lambda, that internally calls comando_click("Nova_Mensagem").
Fix: just put command=comando_click.
If you want to personalize the lambda with arguments you could write something like this:
def handler(args):
def custom_handler():
print(args)
return custom_handler
botao = Button(menu_inicial, text = "Executar", command=handler("my custom string"))

How do I pass arguments to a callback for a StringVar?

I have set up an Entry widget using tkinter and am using a StringVar in order to trace any changes to it. However, when I try to use the callback function, I find that I am unable to pass any arguments to it.
Here is what I have tried:
hpstring = StringVar()
hpstring.trace("w", hptrace)
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This fails because hpstring is not defined.
hpstring = StringVar()
hpstring.trace("w", hptrace(hpstring))
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This also somehow fails.
hpstring = StringVar()
hpstring.trace("w", lambda hpstring: hptrace(hpstring))
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This time, it fails because "() takes 1 positional argument but 3 were given".
Is there any way to have my callback function (hptrace) take hpstring as an argument?
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.

Sending a Class' attribute to an outside function in the function call

I'm trying to generalise a function in my script by sending in the Class' attribute through the function call to an outside function, but since I have to call the attribute self.attribute_name inside the class and object_name.attribute.name outside it, I either get an error that no self.attribute_nameexists outside the code or that no object_name.attribute.nameexists inside. The part of my code concerned is as follows(this is just a fragment of the full code with many parts omitted):
class My_Window:
self.get_info_box = Entry(root)
self.entry_button = Button(root, text="Choose", command =lambda: self.retrieve_input(self.get_info_box, solitaire.cards))
def retrieve_input(self, box, entity):
self.user_input = box.get()
entity = input_check(box)
def input_check(which_box): # Function outside class
my_window.which_box.delete(0, "end") # This is want I would like to do if it worked
return 0
my_window = My_Window()
Something in the back of my head tells me it might be possible to use lambda again to accomplish this but I'm still not sure how to use them properly and I couldn't find any active questions covering this specific case.
Anyone have any ideas how to work this out?
I think what you want is
def input_check(which_box):
getattr(my_window,which_box).delete(0, "end")
return 0
input_check("get_info_box")
but its hard to tell for sure
Try it without the my_window.
def input_check(which_box):
which_box.delete(0, "end")
return 0
Incidentally, entity = input_check(box) won't cause solitaire.cards to retain the value returned by input_check, because assignment doesn't propagate upwards like that. If you want to change solitaire.cards, you'll need to do solitaire.cards = input_check(box) instead. If solitaire isn't visible inside retrieve_input, then you'll need to make it an attribute of self.

unable to pass information to callback function

I am using Tkinter,python 2.7 and am new to gui programming. I want to pass a string to a callback function in a button.I have tried both partial from functools and lambda functions. So far no luck. So I must be doing something wrong.
def fetch_urls(name):
print name
root=Tk()
aname=StringVar()
E1 = Entry(root, bd =5,textvariable=aname,justify=CENTER,width=20)
E1.grid(row=0,column=1,columnspan=3)
fetchbutton=Button(root,text ="FETCH",command =fetch_urls)
fetchbutton.grid(row=7,column=0)
what I basically want is to send the string in aname ie the text in the entry widget E1 to the function fetch_urls. I have tried the following with no success
1) using lambda
anime=aname.get()
lambdafetch=lambda: fetch_urls(anime)
fetchbutton=Button(root,text ="FETCH",command =lambdafetch)
2)using partial from functools
anime=aname.get()
parfetch=partial(fetch_urls,anime)
fetchbutton=Button(root,text ="FETCH",command =parfetch)
In both cases the string is not passed to the function. I would like to know what I am doing wrong and how to make it work.
def fetch():
fetch_urls(aname.get())
fetchbutton=Button(root,text ="FETCH",command =fetch)
this works(got the idea 15 mins after posting the question). but surely this can't be the only way?
The problem is that with both lambda and partial you are evaluating aname at the time the callback is created, instead of being evaluated at the time it is actually called.
For partial, you would need to modify the fetch_urls function to actually eval the value:
def fetch_urls(aStringVar):
print aStringVar.get()
parfetch = partial(fetch_urls, aname)
For lambda, just have it evaluate the object:
lambdafetch = lambda: fetch_urls(aname.get())
But I tend to not use lambda for callbacks because you might run into scope issues. You may have to use a trick to "capture" the aname object:
lambdafetch = lambda s=aname: fetch_urls(s.get())

Categories

Resources