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.
Related
I used to work with this way of coding and it worked fine, but after going back to it a few weeks later, it does not anymore. I simplidied my code so it is easy to type here.
import tkinter as tk
from tkinter import ttk
class wind(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# id shutter
self.SOURCE_SHUTTER = "/dev/ttyUSB0"
# menu deroulant
self.listeFlux = ["/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2", "/dev/ttyUSB3"]
self.listeCombo = ttk.Combobox(self, values=self.listeFlux)
self.listeCombo.current(0)
self.listeCombo.bind("<<ComboboxSelected>>", self.action)
self.listeCombo.pack(side="top")
def action(self):
self.SOURCE_SHUTTER = self.listeCombo.get()
print(self.SOURCE_SHUTTER)
if __name__ == "__main__":
win = wind()
win.geometry("800x600")
win.mainloop()
This code gives me the error : TypeError: action() takes 1 positional argument but 2 were given.
Does someone know why ? I have seen people make this mistake but their error was that a parameter was missing "self" somewhere in their code, which I don't think I am forgetting here.
Thanks a lot for your help.
Valentin
I tried looking in another topic that had the same problem but mine seems different here.
Event bindings in tkinter will inherently pass an event parameter to the bound function, so you'll need deal with it one way or another
Option 1
Add an _event parameter to the action method.
The leading underscore _ is convention to let people know that the value is unused by the function, and event is the conventional name for the event argument taken by event-driven functions in tkinter. The default value of None isn't strictly necessary, but it's good practice.
def action(self, _event=None):
self.SOURCE_SHUTTER = self.listeCombo.get()
print(self.SOURCE_SHUTTER)
Option 2
Use *_args in your method definition to allow it to accept any number of arguments (as suggested in #3ddavies' answer!). Again, the _ is convention for unused values, and args is convention for this type of parameter. As has been mentioned, the caveat here is that now your action method will accept any number of arguments - this is unlikely to be an issue in this particular case, but keep it in mind!
def action(self, *_args):
self.SOURCE_SHUTTER = self.listeCombo.get()
print(self.SOURCE_SHUTTER)
Option 3
Use a lambda to absorb the event and call action as an anonymous function
def __init__(self):
... # code omitted for brevity
self.listeCombo.bind("<<ComboboxSelected>>", lambda event: self.action)
...
def action(self, _event = None):
self.SOURCE_SHUTTER = self.listeCombo.get()
print(self.SOURCE_SHUTTER)
A quick and dirty fix would be to change the definition of action to:
def action(self, *args):
The underlying issue here is that <<ComboboxSelected>> seems to return a tuple, so when the event is triggered, both the self object and the tuple are being passed to the action function, which is causing the error.
The above solution is a "dirty fix" because it will allow the function to take any number of arguments greater than one.
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))
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 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.
I'm trying to create a Scrabble game with Python. I'd like to display the points that the word worth when the user is typing the word.
I already asked this question as I didn't know what method to use. As I discovered which method to use, and my question is about how to use this method, I think this deserve a new question.
My problem is that I created a function called bind_entry(event) that is supposed to set a label every time the user type a letter. But the function bind_entry(event)doesn't know the label to set and the entry where the word is.
Here is my code :
#this the function creating the label
def create_variabletext_intlabel(root,col,row):
val=IntVar()
label=Label(root,textvariable=val)
label.grid(column=col,row=row)
return val, label
#this is the function creating the entry
def create_entry_string(root,width,col,row,columnspan,rowspan):
val=StringVar()
entry=ttk.Entry(root,width=width,textvariable=val)
entry.grid(column=col,row=row,columnspan=columnspan,rowspan=rowspan)
entry.bind("<Any-KeyPress>",bind_entry)
#Here is my problem, when I call the function bind_entry.
return val, entry
def bind_entry(event):
label.set(m.counting_point(char(event)))
# m.counting_point() is a function counting the word's points
# my problem is that the function doesn't know yet the label.
# I don't know how to call the label.
# I call the function create_entry_string in another file initiating
# all the widget for the GUI
val_entry_word, entry_word =g.create_entry_string(root,15,1,1,1,1)
# I call the function create_variabletext_intlabel in another file
# initiating all the widget for the GUI
val_points,label_points=g.create_variabletext_intlabel(root,1,2)
I just noticed that the function m.counting_points() will count only the letter that is typed by the user. Here I should call val_entry_word.
So here is my question :
As val_entry_word and val_points are created in a function in another file How could I call val_entry_word and val_points in the function bind_entry() ?
Generally, when you need different function calls to share information without passing it explicitly, the best practice is to use a class.
e.g.
class LabelUpdater(object):
def create_variabletext_intlabel(self,root,col,row):
val=IntVar()
self.label=label=Label(root,textvariable=val)
label.grid(column=col,row=row)
return val, label
#this is the function creating the entry
def create_entry_string(self,root,width,col,row,columnspan,rowspan):
val=StringVar()
entry=ttk.Entry(root,width=width,textvariable=val)
entry.grid(column=col,row=row,columnspan=columnspan,rowspan=rowspan)
entry.bind("<Any-KeyPress>",self.bind_entry)
#Here is my problem, when I call the function bind_entry.
return val, entry
def bind_entry(self,event):
self.label.set(m.counting_point(char(event)))
#At this point, I don't understand your code anymore since I don't know what g
#is or how it's create_entry_string method calls your create_entry_string function...
#I'll assume that the module where g's class is defined imports this file...
#If that's how it works, then the following may be ok, although probably not because
#of circular imports...
container=LabelUpdater()
create_variabletext_intlabel=container.create_variabletext_intlabel
create_entry_string=container.create_entry_string
val_entry_word, entry_word =g.create_entry_string(root,15,1,1,1,1) #somehow calls create_variabletext_intlabel which is mapped to container.create_variable_intlabel???
# I call the function create_variabletext_intlabel in another file
# initiating all the widget for the GUI
val_points,label_points=g.create_variabletext_intlabel(root,1,2)
Of course, you could also use globals...(though that is definitely discouraged)
Finally, an idiom that I often use to add additional information in a bind callback is to wrap the callback function in another function...
def myfunc(root):
label=Label(root,text="cow")
label.pack()
return label
#This is the callback we want...
# Q: but how do we pass S?
# A: we need to wrap this call in another -- a perfect use for lambda functions!
def change_label(label,S):
label.config(text=S)
root=Tk()
lbl=myfunc(root)
lbl.bind("<Enter>",lambda e: change_label("Horse"))
lbl.bind("<Leave>",lambda e: change_label("Cow"))