How to pass an argument to event handler in tkinter? - python

widget.bind('<Button-1>',callback) # binding
def callback(self,event)
#do something
I need to pass an argument to callback() . The argument is a dictionary object.

You can use lambda to define an anonymous function, such as:
data={"one": 1, "two": 2}
widget.bind("<ButtonPress-1>", lambda event, arg=data: self.on_mouse_down(event, arg))
Note that the arg passed in becomes just a normal argument that you use just like all other arguments:
def on_mouse_down(self, event, arg):
print(arg)

What about
import functools
def callback(self, event, param):
pass
arg = 123
widget.bind("", functools.partial(callback, param=arg))

I think that in most cases you don't need any argument to a callback because the callback can be an instance method which can access the instance members:
from Tkinter import *
class MyObj:
def __init__(self, arg):
self.arg = arg
def callback(self, event):
print self.arg
obj = MyObj('I am Obj')
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', obj.callback)
btn.pack()
root.mainloop()
But I think the functools solution proposed by Philipp is also very nice

Here is an entry on this from the New Mexico Tech Tkinter 8.5 Reference (https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/extra-args.html)
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
This way allows you to add as many arguments as you need:
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
54.7. The extra arguments trick
Sometimes you would like to pass other arguments to a handler besides the event.
Here is an example. Suppose your application has an array of ten checkbuttons whose >widgets are stored in a list self.cbList, indexed by the checkbutton number in >range(10).
Suppose further that you want to write one handler named .__cbHandler for >events in all ten of these checkbuttons. The handler can get the actual Checkbutton >widget that triggered it by referring to the .widget attribute of the Event object that >gets passed in, but how does it find out that checkbutton's index in self.cbList?
It would be nice to write our handler with an extra argument for the checkbutton number, >something like this:
def __cbHandler(self, event, cbNumber):
But event handlers are passed only one argument, the event. So we can't use the function >above because of a mismatch in the number of arguments.
Fortunately, Python's ability to provide default values for function arguments gives us >a way out. Have a look at this code:
def __createWidgets(self):
…
self.cbList = [] # Create the checkbutton list
for i in range(10):
cb = tk.Checkbutton(self, …)
self.cbList.append(cb)
cb.grid( row=1, column=i)
def handler(event, self=self, i=i): 1
return self.__cbHandler(event, i)
cb.bind('<Button-1>', handler)
…
def __cbHandler(self, event, cbNumber):
…
These lines define a new function handler that expects three arguments. The first >argument is the Event object passed to all event handlers, and the second and third >arguments will be set to their default values—the extra arguments we need to pass it.
This technique can be extended to supply any number of additional arguments to >handlers.

How to pass an argument to event handler in tkinter?
Here is the simplest and easiest-to-read solution of them all I think:
widget.bind('<Button-1>', callback2)
# change "None" to whatever you want the default value to be
def callback(self, event, custom_arg=None):
# do something
def callback2(self, event):
# set custom_arg to whatever you want it to be when Button-1 is pressed
callback(event, custom_arg=something_you_set)

Pass the callback function to the instance and call it from the instance method.
from tkinter import *
class MyClass:
def __init__(self, my_callback, message):
self.my_callback = my_callback
self.message = message
def callback(self, event):
self.my_callback(self)
def my_callback(o):
print(o.message)
obj = MyClass(my_callback, "I am instance of MyClass")
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', obj.callback)
btn.pack()

You can also supply arguments to a callback function of a widget, given only that this widget is defined as a part of a class definition ,, i.e. consider this tiny python 2.7 program (without the parts responsible of program's execution):
import Tkinter as tk #To be able to get "tk.Button" safely
from Tkinter import *
class EXAMPLE(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
#make the widgets appear to a grid of size = 2 X 2
for row in range(2):
self.grid_rowconfigure(row,minsize=20)
for col in range(2):
self.grid_columnconfigure(col,minsize=20)
#Call our METHOD OF INTEREST
self.AnyMethod()
#This is our method of interest
def AnyMethod(self):
#arguments to be supplied
self.arg1 = 'I am 1st argument'
self.arg2 = 'I am 2nd argument'
self.arg3 = 'I am 3rd argument'
#Draw the widget, & supply its callback method
self.widgetname=tk.Button(self.master,text="My Button",command=self.method_callback)
self.widgetname.grid(row=0,column=0)
#create a so-called 'shell method' to swallow the REAL callback function
def method_callback(self):
func_callback(self.arg1,self.arg2,self.arg3)
#Define the REAL callback function in the Module's scope
def func_callback(arg1,arg2,arg3):
print arg1
print arg2
print arg3
NOTE THAT the supplied arguments must be proceeded with self.

Related

Generic function for displaying images with an associated function? [duplicate]

widget.bind('<Button-1>',callback) # binding
def callback(self,event)
#do something
I need to pass an argument to callback() . The argument is a dictionary object.
You can use lambda to define an anonymous function, such as:
data={"one": 1, "two": 2}
widget.bind("<ButtonPress-1>", lambda event, arg=data: self.on_mouse_down(event, arg))
Note that the arg passed in becomes just a normal argument that you use just like all other arguments:
def on_mouse_down(self, event, arg):
print(arg)
What about
import functools
def callback(self, event, param):
pass
arg = 123
widget.bind("", functools.partial(callback, param=arg))
I think that in most cases you don't need any argument to a callback because the callback can be an instance method which can access the instance members:
from Tkinter import *
class MyObj:
def __init__(self, arg):
self.arg = arg
def callback(self, event):
print self.arg
obj = MyObj('I am Obj')
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', obj.callback)
btn.pack()
root.mainloop()
But I think the functools solution proposed by Philipp is also very nice
Here is an entry on this from the New Mexico Tech Tkinter 8.5 Reference (https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/extra-args.html)
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
This way allows you to add as many arguments as you need:
‌‌‌‌‍‬‍‌‌‌‌‍‬‬‍‌‌‌‌‍‬‌‬‌‌‌‌‍‬‌‬‌‌‌‌‍‬‍‍‌‌‌‌‍‌‬‌‌‌‌‍‬‬‍‌‌‌‌‍‌‌‌‌‌‍‬‬‌
54.7. The extra arguments trick
Sometimes you would like to pass other arguments to a handler besides the event.
Here is an example. Suppose your application has an array of ten checkbuttons whose >widgets are stored in a list self.cbList, indexed by the checkbutton number in >range(10).
Suppose further that you want to write one handler named .__cbHandler for >events in all ten of these checkbuttons. The handler can get the actual Checkbutton >widget that triggered it by referring to the .widget attribute of the Event object that >gets passed in, but how does it find out that checkbutton's index in self.cbList?
It would be nice to write our handler with an extra argument for the checkbutton number, >something like this:
def __cbHandler(self, event, cbNumber):
But event handlers are passed only one argument, the event. So we can't use the function >above because of a mismatch in the number of arguments.
Fortunately, Python's ability to provide default values for function arguments gives us >a way out. Have a look at this code:
def __createWidgets(self):
…
self.cbList = [] # Create the checkbutton list
for i in range(10):
cb = tk.Checkbutton(self, …)
self.cbList.append(cb)
cb.grid( row=1, column=i)
def handler(event, self=self, i=i): 1
return self.__cbHandler(event, i)
cb.bind('<Button-1>', handler)
…
def __cbHandler(self, event, cbNumber):
…
These lines define a new function handler that expects three arguments. The first >argument is the Event object passed to all event handlers, and the second and third >arguments will be set to their default values—the extra arguments we need to pass it.
This technique can be extended to supply any number of additional arguments to >handlers.
How to pass an argument to event handler in tkinter?
Here is the simplest and easiest-to-read solution of them all I think:
widget.bind('<Button-1>', callback2)
# change "None" to whatever you want the default value to be
def callback(self, event, custom_arg=None):
# do something
def callback2(self, event):
# set custom_arg to whatever you want it to be when Button-1 is pressed
callback(event, custom_arg=something_you_set)
Pass the callback function to the instance and call it from the instance method.
from tkinter import *
class MyClass:
def __init__(self, my_callback, message):
self.my_callback = my_callback
self.message = message
def callback(self, event):
self.my_callback(self)
def my_callback(o):
print(o.message)
obj = MyClass(my_callback, "I am instance of MyClass")
root = Tk()
btn=Button(root, text="Click")
btn.bind('<Button-1>', obj.callback)
btn.pack()
You can also supply arguments to a callback function of a widget, given only that this widget is defined as a part of a class definition ,, i.e. consider this tiny python 2.7 program (without the parts responsible of program's execution):
import Tkinter as tk #To be able to get "tk.Button" safely
from Tkinter import *
class EXAMPLE(Frame):
def __init__(self,master=None):
Frame.__init__(self,master)
#make the widgets appear to a grid of size = 2 X 2
for row in range(2):
self.grid_rowconfigure(row,minsize=20)
for col in range(2):
self.grid_columnconfigure(col,minsize=20)
#Call our METHOD OF INTEREST
self.AnyMethod()
#This is our method of interest
def AnyMethod(self):
#arguments to be supplied
self.arg1 = 'I am 1st argument'
self.arg2 = 'I am 2nd argument'
self.arg3 = 'I am 3rd argument'
#Draw the widget, & supply its callback method
self.widgetname=tk.Button(self.master,text="My Button",command=self.method_callback)
self.widgetname.grid(row=0,column=0)
#create a so-called 'shell method' to swallow the REAL callback function
def method_callback(self):
func_callback(self.arg1,self.arg2,self.arg3)
#Define the REAL callback function in the Module's scope
def func_callback(arg1,arg2,arg3):
print arg1
print arg2
print arg3
NOTE THAT the supplied arguments must be proceeded with self.

How to get reference to an instance attribute, and modify its value from outside

I'm working on an application in tkinter. I have many Entry widgets in UI, and a few classes in app engine. I need to bind tkinter variables of those entries to instances attributes.
i.e.:
class Pipe(Variable):
"""class for pipes"""
def __init__(self):
self.diameter = 0
self.variables = {}
pipe1 = Pipe(self)
pipe2 = Pipe(self)
I want to bind value from one entry to pipe1.diameter, and value from another entry to pipe2.diameter. I'm doing it by a trace function, where is lambda statement, pointing to a function, which identifies entry, and, using a dictionary proper for each instance, pass a value from entry to dictionary value. Dictionaries are produced like here, and then passed as instance attribute:
def pipe1_vars(object_):
variables = {
'ui_variable_name_for_pipe1_diameter': [object_.diameter]
}
return variables
def pipe2_vars(object_):
variables = {
'ui_variable_name_for_pipe2_diameter': [object_.diameter]
}
return variables
pipe1.variables = pipe1_vars(pipe1)
pipe2.variables = pipe2_vars(pipe2)
Unfortunately, Variable class method, assigning value, isn't working properly.
class Variable():
def set_var_value(variable_name, value):
ui_variable = tkinterbuilder.get_variable(variable_name)
self.variables[variable_name][0] = value
if ui_variable.get() != value:
ui_variable.set(value)
Obviously self.variables[variable_name][0] is something different than self.diameter. The dictionary value is changing, but instance.diameter stays the same.
How can I pass a real instance attribute to this method, instead of a copy in a dictionary value?
I'm assuming it is important to my app, to build something working as those dictionaries, because i need to bind similar attributes of different pipes to different entries - so it's have to be defined outside of a Pipe() class. I don't know if I should change dictionary to something else, or maybe should I rebuild those functions, building dictionary. I've run out of ideas, what to ask google.
Code is much complex, I've posted only most important elements, but if any other details are important, please note in comment.
If the number of Pipe attributes is small, make them properties, and when you create a Pipe object, pass it the corresponding tk binded variable:
import tkinter as tk
from tkinter import Tk, ttk
root = Tk()
var_e1 = tk.StringVar()
def print_e1():
print(var_e1.get())
def inc_e1():
var_e1.set(int(var_e1.get())+1)
class Pipe():
def __init__(self, tkvar):
self.tkvar = tkvar
tkvar.set('')
#property
def diameter(self):
return self.tkvar.get()
#diameter.setter
def diameter(self, value):
self.tkvar.set(value)
e1 = tk.Entry(root, textvariable=var_e1)
b1 = tk.Button(root, text='Print e1', command=print_e1)
b2 = tk.Button(root, text='Increment e1', command=inc_e1)
e1.pack(side=tk.LEFT)
b1.pack()
b2.pack()
p1 = Pipe(var_e1)
p1.diameter = 200
root.mainloop()

button command option in tkinter

In the little GUI app below. When I use button's command option to call a function. It doesn't work like this: self.update() rather it works like this: self.update. Why so? Is is some special way that command option of a button works? I think a method or a function should be called with those braces (), unless it's a property:
i.e.
#name.setter:
def setter(self, name):
self.name = name
#main
object.name = "New_obj"
Note: The above is just a template so you might get my point. I didn't write the complete valid code. Including class and everything.
from tkinter import *
class MuchMore(Frame):
def __init__(self, master):
super(MuchMore,self).__init__(master)
self.count =0
self.grid()
self.widgets()
def widgets(self):
self.bttn1 = Button(self, text = "OK")
self.bttn1.configure(text = "Total clicks: 0")
self.bttn1["command"] = self.update # This is what I am taking about
self.bttn1.grid()
def update(self):
self.count += 1
self.bttn1["text"] = "Total clicks" + str(self.count)
#main
root = Tk()
root.title("Much More")
root.geometry("324x454")
app = MuchMore(root)
It is a high order function, meaning you are referencing a function as an object. You are not calling the function and assigning the command to the return value of the function. See here for more information.
The command parameter takes a reference to a function -- ie: the name of the function. If you add parenthesis, you're asking python to execute the function and give the result of the function to the command parameter.

How to recognize a button from its command?

I have created a list of entries in a for-loop. All entries are stored in a list so I can just obtain all of the inputs later:
inputs = [e.get() for e in self.entries]
However, I have also created a button next to each entry in the for-loop (so they each call the same function). How can I make it so that it recognizes which button belongs to which row/entry? Is there something I can do with event?
row = 0
self.entries = []
self.comments = []
for n in names:
e = Entry(self.top, bd = 5)
e.insert(0, n)
e.grid(column = 1, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.entries.append(e)
self.comments += [""]
commentButton = Button(self.top, text = "comment", command = self.commentSelected)
commentButton.grid(column = 3, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.row = self.row + 1
Yes -- use Callback Shims ( Currying Functions )
( courtesy Russell Owen )
I find I often wish to pass extra data to a callback function, in addition that that normally given. For instance the Button widget sends no arguments to its command callback, but I may want to use one callback function to handle multiple buttons, in which case I need to know which button was pressed.
The way to handle this is to define the callback function just before you pass it to the widget and include any extra information that you require. Unfortunately, like most languages, Python doesn't handle the mixing of early binding (information known when the function is defined) and late binding (informtation known when the function is called) particularly well. I personally find the easiest and cleanest solution is:
Write my callback function to take all desired data as arguments.
Use a callback shim class to create a callable object that stores my function and the extra arguments and does the right thing when called. In other words, it calls my function with the saved data plus the data that the caller supplies.
I hope the example given below makes this clearer.
The callback shim I use is RO.Alg.GenericCallback, which is available in my RO package. A simplified version that does not handle keyword arguments is given in the example below. All shim code is based on a python recipe by Scott David Daniels, who calls this "currying a function" (a term that is probably more common than "callback shim").
#!/usr/local/bin/Python
""" Example showing use of a callback shim"""
import Tkinter
def doButton(buttonName):
""" My desired callback.
I'll need a callback shim
because Button command callbacks receive no arguments.
"""
print buttonName, "pressed"
class SimpleCallback:
""" Create a callback shim.
Based on code by Scott David Daniels
(which also handles keyword arguments).
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs
def __call__(self, *args):
return self.__callback (*(self.__firstArgs + args))
root = Tkinter.Tk()
buttonNames = ( "Button 1", "Button 2", "Button 3" )
for name in buttonNames:
callback = SimpleCallback( doButton, name )
Tkinter.Button( root, text = name, command = callback ).pack()
root.mainloop()
You can also use lambda:
from tkinter import *
def bla(b):
...
root = Tk()
buttons = []
for i in range(...):
button = Button(root)
button.configure(command=lambda b=button: bla(b)) # Make sure the Button object already exists
buttons.append(button)
button.pack()
root.mainloop()
As far as I see, you can't create the buttons in a single list comprehension now, but it is simpler and more readable than a class imho.

wxpython error - "compute() takes exactly 2 arguments (1 given)" but it has 2 arguments

So recently i'v been trying to learn wxpython and I keep getting this error, I know the error says there isn't enough arguments but under the compute function it has both arguments.
import wx
import math
class game(wx.Frame):
def __init__(self,parrent,id):
c3 = "0"
wx.Frame.__init__(self,parrent,id,"Form", size=(250,160))
panel=wx.Panel(self)
box2=wx.TextEntryDialog(None, "Input b", "Pythagorean theorem", "")
if box2.ShowModal()==wx.ID_OK:
b=box2.GetValue()
box1=wx.TextEntryDialog(None, "Input A", "Pythagorean theorem", "")
if box1.ShowModal()==wx.ID_OK:
a=box1.GetValue()
def compute(self, event):
a2=int(a)**2
b2=int(b)**2
c = a2 + b2
c2=math.sqrt(c)
c3=str(c2)
button=wx.Button(panel, label="Compute",pos=(90,70), size=(60,40))
self.Bind(wx.EVT_BUTTON, compute, button)
wx.StaticText(panel, -1, c3, (10,10))
if __name__=="__main__":
app=wx.PySimpleApp()
frame=game(parrent=None, id=-1)
frame.Show()
app.MainLoop()
error : "compute() takes exactly 2 arguments (1 given)"
nested functions dont get or require the self argument
class XYZ:
def __init__(self):
def some_func(x):
print "SELF:",self
print "X:",x
some_func(6)
>>> print XYZ()
SELF: <__main__.XYZ instance at 0x029F7148>
X: 6
<__main__.XYZ instance at 0x029F7148>
this is because nested functions have access to all of their parent functions local variables (ie self)
in your instance just remove self from the argument list or make compute a class method instead of a method inside a class method
You are binding as self.Bind(wx.EVT_BUTTON, compute, button) but compute takes arguments of (self, evt) so you need to move compute out of __init__ into your game class and bind it as self.compute
The event handler, 'compute', is not a method here, but a function inside a method (_init_). If you add 'self' as an argument it expects an instance of parent class to be passed whenever it is called, which you didn't do specifically (not that you should). When you bind an event wx automatically sends in the event to the handler and if your handler accepts it, well...you can use it for further actions. A handler can be any function like a method or a lambda function etc.
So, that being said, here you can just remove the 'self' argument or move the handler in to its own method. By the way, if that is the only button in the code, you don't need to specifically bind the event to that button.
Also, will this work? you're expecting a label to display the results, right? Since c3 is inside that function I'm thinking it won't! (you might need your method to call SetLabel())
You need to move the definition on "compute" out of init.
Also, declare panel belonging to the class and make a handler for the static text, since after you press the button the static text must be updated with the new result.
You can also save some space and not allocate "c" in init:
class game(wx.Frame):
def __init__(self,parrent,id):
wx.Frame.__init__(self,parrent,id,"Form", size=(250,160))
self.panel=wx.Panel(self)
box2=wx.TextEntryDialog(None, "Input B", "Pythagorean theorem", "")
if box2.ShowModal()==wx.ID_OK:
self.b=box2.GetValue()
box1=wx.TextEntryDialog(None, "Input A", "Pythagorean theorem", "")
if box1.ShowModal()==wx.ID_OK:
self.a=box1.GetValue()
button=wx.Button(self.panel, label="Compute", pos=(90,70), size=(60,40))
self.Bind(wx.EVT_BUTTON, self.compute, button)
self.st = wx.StaticText(self.panel, -1, "", (10,10))
def compute(self, event):
c = str(math.sqrt(int(self.a)**2 + int(self.b)**2))
self.st = wx.StaticText(self.panel, -1, c, (10,10))

Categories

Resources