Info
python 3
tkinter
Goal
I have a piece of code, that generates a custom menu for me. Now I want to change the color of the single buttons within the menu when the mouse hovers over them.
Code
for i in cmd:
button = Button([..set some stuff here..])
button.bind("<Enter>", lambda name: button.config(bg=hovercolor))
button.bind("<Leave>", lambda name: button.config(bg=color))
button.pack()
My idea was to bind ENTER and LEAVE events to every button. To get the lambda function to work it needs a name(in this case name, but I don't care about the name).
Problem
If the mouse hovers over any button in my generated menu the color of the last button is changed.
I think that is caused by the name of the lambda function. every button generates a new lambda function with the same name, overwriting the previous lambda function in the python environment.
I'm searching for:
a way to generate the names for the lambda functions
or
another way to change the color if the mouse hovers over the button.
Solved
for i in cmd:
button = Button([..set some stuff here..])
button.bind("<Enter>", lambda name, button=button: button.config(bg=hovercolor))
button.bind("<Leave>", lambda name, button=button: button.config(bg=color))
button.pack()
The issue is the binding of button. Your lambda expression doesn't define it, so it needs a closure to be found in the outer scope (the loop). Since the value changes each time through the loop, by the time the lambda functions are run the button variable points at the last button you created, not the one it pointed to when the lambda function was defined.
You can work around this by saving each button object as a default argument:
button.bind("<Enter>", lambda name, button=button: button.config(bg=hovercolor))
The button=button argument makes button a local variable within the lambda, so the changing outer definition doesn't matter. No closures necessary!
The reason it's happening is as you said, the name gets overwritten and the lambdas all run on the same variables.
What you could do is create a factory of callbacks, so you'd pass the names to the function, and the function would return the callback that'd get called.
def create_callback(button, bg_color):
lambda x: button.config(bg=bg_color)
button.bind("<Enter>", create_callback(button, hovercolor))
Related
Is it possible to have arguments and an event listener on a function? I have two entries that I want to clear on <FocusIn>. I thought it would be simple and bind delete like self.minutes.bind("<FocusIn>", self.minutes.delete(0, "end)), but no that would be too easy. So I created a function to wipe any entry I want whenever focused. My function is simply:
def entry_clear(entry, e):
entry.delete(0, "end")
This results in entry_clear() missing 1 required positional argument: 'e' but if I use it with self it works fine like:
def entry_clear(self, e):
self.minutes.delete(0, "end")
But of course now I have to specify the exact entry I want in the function, rather than being used for any entry. Thanks for any help.
You do not really need to pass the widget itself because tkinter passes an Event object implicitly with bind. This Event object has an attribute called widget which will be the widget that originally triggered the event. So you can just delete the items of that widget directly:
def entry_clear(self, e): # `e` is `Event` object
e.widget.delete(0, "end")
Now to answer your original question:
Is it possible to have arguments and an event listener on a function?
Yes it is possible, but it depends on how you use bind, a fairly common way is like:
ent.bind('<1>', lambda event: callback(event, ent))
Instead of this, I would always use the first method.
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))
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"))
Inside of a Python 3.5 app written using PyQt5 I have mouseReleaseEvents like so
self.scene1.mouseReleaseEvent = self.setScene("1")
When they have variables to pass, they all trigger when the application starts, and they don't trigger when actually clicked.
However, if I remove the variable, they work when pressed, and they don't trigger at application start:
self.scene1.mouseReleaseEvent = self.setScene
But I have to get a variable over to this function
def setScene(self, scene):
print(scene)
Any suggestions as to why the variable causes this behavior and how to fix it?
Probably in every GUI when you bind function to event you have to use function name - it means without () and arguments.
You can have two method
"boring": create function without arguments and assign to event
def new_fun(self):
self.setScene("1")
self.scene1.mouseReleaseEvent = self.new_fun
"popular": use lambda to create anonymouse function
self.scene1.mouseReleaseEvent = lambda:self.setScene("1")
I don't know but mouseReleaseEvent may run function with event object which has information about event so you have to grab it
self.scene1.mouseReleaseEvent = lambda event:self.setScene("1")
If you have to use lambda in for x loop and use x in lambda function then you need
for x in some_list:
self.scene1.mouseReleaseEvent = lambda event, arg=x :self.setScene(arg)
because lambda is "lazy" and it doesn't copy value from x to function when you define lambda but it gets value from x when you release mouse - but at that momen x has last value from some_list.
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.