mouseReleaseEvent triggers at application start, not on click in PyQt5 - python

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.

Related

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 update a variable by grabbing text from a textbox in tkinter using the enter key?

I'm using Tkinter and trying to create a chat program. I have it set to open a new window that prompts you to enter the IP of the user you wish to connect to. When you press enter it is supposed to update a variable to that IP address for later use. The problem is the variable is not updating, instead, it is keeping its initial value. I am using the .get() function to grab text from the textbox. When I print the .get() function it works but setting the variable using .get() does not work. Send is still printing 1. Any ideas?
other_address = 1
def join():
newWindow = Toplevel(master)
newWindow.title("IP")
newWindow.geometry("300x50")
directions = Label(newWindow, text="Enter the IP Address of the user you would like to join")
directions.pack()
enter_hostname = Entry(newWindow)
enter_hostname.pack()
newWindow.bind("<Return>", lambda x: other_address == enter_hostname.get())
newWindow.bind("<Return>", lambda x: print(enter_hostname.get()))
def send():
print (other_address)
This is an answer to the SyntaxError given in the comments:
The issue here is that a lambda expression creates a 'scope' which means that when it ends any variables it has created are automatically destroyed. The solution to this is to create a proper function which gets other_address into its global scope so that it can edit it properly like so:
def set_address(x):
global other_address
other_address = enter_hostname.get()
Then the binding can be:
newWindow.bind("<Return>", set_address)
Where you just pass the set_address function to it instead of a lambda expression
A double equals operator == does not create a variable in python, instead it is what is called an equality operator, so it checks whether they are equal and returns True or False. To set a variable in python you instead need to use a single equals, = so that line would become:
newWindow.bind("<Return>", lambda x: other_address = enter_hostname.get())

Create lambda functions in a loop for PyQt5 Signals? [duplicate]

I have got a loop. I created a QCheckBox and put it in a QTableWidget cell, and everything is Ok. In each step of loop I have called a connect function, for myslot SLOT, but only the last QCheckBox instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect function return True.
How to create connect function in a loop that enumerates all of the instances?
Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda its own local copy of the instance variable.
EDIT
Here's a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have same problem , you should use functools.partial such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.
The problem is that you are creating a function using lambda where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance) at that moment in time. To be clear, every lambda function you make is using the value of instance at runtime, rather than define time. So instance only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:
def make_callback(param):
return lambda: self.on_button(param)
And in the connection, call make_callback(i). Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance to the make_callback function and then place your lambda definition inside the make_callback function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren't following what I've said, make the code in your question clearer and I'll have a go at creating an example!

How to generate lambda functions within for

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))

QtCore.QObject.connect in a loop only affects the last instance

I have got a loop. I created a QCheckBox and put it in a QTableWidget cell, and everything is Ok. In each step of loop I have called a connect function, for myslot SLOT, but only the last QCheckBox instance is applied. I googled a lot and have found many people have my problem. I have applied their solutions, but my problem remains.
for row in xrange(len(uniqueFields)):
instance = QtGui.QCheckBox(uniqueFields[row], findInstance.tableWidget)
print QtCore.QObject.connect(instance,
QtCore.SIGNAL(_fromUtf8("stateChanged (int)")),
lambda: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(),
instance.checkState(), instance))
findInstance.tableWidget.setRowCount(findInstance.tableWidget.rowCount() + 1)
findInstance.tableWidget.setCellWidget(row, 0, instance)
Note: my connect function return True.
How to create connect function in a loop that enumerates all of the instances?
Put the loop variable in a default argument, like this:
lambda state, instance=instance: findInstance.projectsInstance.myslot(
"TWCH", findInstance, instance.text(), instance.checkState(), instance)
This will give each lambda its own local copy of the instance variable.
EDIT
Here's a simple script that demonstrates how to use default lambda arguments:
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
for index in range(4):
instance = QtGui.QCheckBox('Checkbox(%d)' % index, self)
instance.stateChanged.connect(
lambda state, instance=instance:
self.mySlot(instance.text()))
layout.addWidget(instance)
def mySlot(self, text):
print('clicked: %s' % text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have same problem , you should use functools.partial such as:
for key, val in a_DICT_THAT_YOU_STORED_YOUR_OBJECTS_AND_STRINGS:
obj = partial( findInstance.projectsInstance.myslot,arg1="TWCH",arg2=self,arg3=key,arg4=val.checkState() )
QtCore.QObject.connect(val, QtCore.SIGNAL(_fromUtf8("stateChanged (int)")), obj)
Of course, argX should set to your real name of your argument of your function name.
The problem is that you are creating a function using lambda where some of the variables inside the function are not being passed in as arguments to the function. When the lambda function is executed, when the signal is emitted, it uses the value of those variables (like instance) at that moment in time. To be clear, every lambda function you make is using the value of instance at runtime, rather than define time. So instance only holds a reference to the object used in the last iteration of our loop, which explains the behaviour you are seeing.
Some useful information can be found here (read the comments too) http://eli.thegreenplace.net/2011/04/25/passing-extra-arguments-to-pyqt-slot/
From the comments of the above link:
What you can do is have another function generate the lambda, i.e.
something like:
def make_callback(param):
return lambda: self.on_button(param)
And in the connection, call make_callback(i). Then a different lambda is
created for each iteration.
So you would want to generalise this and pass in things like instance to the make_callback function and then place your lambda definition inside the make_callback function. I would provide a clear example of this, but as the other answer says, your formatting appears to have become very messed up in your question and I would likely get it wrong for your specific application. If you aren't following what I've said, make the code in your question clearer and I'll have a go at creating an example!

Categories

Resources