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.
Related
I am trying to create the function 'count' that takes in an integer in the form of a variable, and adds 1 to it every time the return key is pressed, saving to the variable each time.
The argument needs to remain generic, because in the future this will run the same 'count' function on multiple variables depending on which button is pressed.
I've tried making messi a global variable by putting global messi at the top, but the same problem occurs.
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(number):
number = number + 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :PlayerStats.count(messi.touches))
root.mainloop()
When I run this snippet, it iterates it once, from 0 to 1, and then resets always printing out 1.
Any thoughts on why this is happening and how to fix would be appreciated!!
You are not saving the result of the operation.
You're instanciating your PlayerStats class with a value of 0 for touches.
That value is then never mutated throughout your code.
When tkinter is calling your count method, it increments number but that variable never leaves the scope of the method, and is thus garbage collected.
To fix it, you should change your class to something like
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(self): # the first argument of a method is always a reference to the instance
self.touches += 1
print(self.touches)
messi = PlayerStats("Messi", 0)
root = tk.Tk()
root.bind('<Return>', lambda event: messi.count()) # You need to call the method on the instance you created.
root.mainloop()
Thanks for your help Dogeek. My functioning code from above now looks like this:
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(self, number):
self.touches += 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :messi.count(messi.touches))
root.mainloop()
One thing this does not solve though is the ability to reuse that function for different variables. I am now trying to come up with an elegant way to do something like this:
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches, shots):
team = "Barcelona"
self.name = name
self.touches = touches
self.shots = shots
def count(self, number):
self.number += 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :messi.count(messi.touches))
root.bind('<s>', lambda event :messi.count(messi.shots))
root.mainloop()
where number represents either messi.shots or messi.touches depending on what key is pressed. I'd like to do this without recreating a bunch of nearly identical functions for each key.
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.
I have defined a new Entry subclass: NewEntry, but it can't get the numbers which are put in it. How can I fix this?
When I click the button, the error message is showed:
ValueError: could not convert string to float:
from Tkinter import *
root = Tk()
class NewEntry(Entry):
def __init__(self,parent,cusdef='1'): #Initiation default number is '1'
Entry.__init__(self,parent)
self.cusdef = cusdef
v=StringVar()
v.set(self.cusdef)
self = Entry(self,textvariable=v)
self.pack()
return
def GetNum():
a=e.get()
print float(a)
return
e = NewEntry(root)
e.pack(fill='x')
button = Button(root,command=GetNum)
button.pack(fill='x')
root.mainloop()
You seem to be trying to initialize your Entry subclass here:
self = Entry(self,textvariable=v)
self.pack()
But instead, you're merely overwriting the variable called self and creating a new Entry which gets discarded.
Instead you need to do the Entry.__init__ call once, with the correct arguments:
class NewEntry(Entry):
def __init__(self,parent,cusdef='1'):
self.cusdef = cusdef
v=StringVar()
v.set(self.cusdef)
Entry.__init__(self,parent, textvariable=v)
self.pack()
return
I have a marvellous function here:
def update_config(val):
config = configparser.ConfigParser()
fonts_comb = ""
for i in range(len(fonts)):
if i == len(fonts) - 1:
fonts_comb = fonts_comb + fonts[i]
else:
fonts_comb = fonts_comb + fonts[i] + ", "
config["LISTS"] = {"Fonts": fonts_comb}
config["variables"] = {"font_size": (screen_font_size.var).get(),
"x_offset": (x_offset_spin.var).get(),
"y_offset": (y_offset_spin.var).get(),
"language": language,
"preview_font_size": (preview_font_size_spin.var).get()}
variables = config["variables"]
if (fonts_menu.var).get() != strings[17]:
variables["font"] = (fonts_menu.var).get()
else:
variables["font"] = font
if (preview_fonts.var).get() != strings[18]:
variables["preview_font"] = (preview_fonts.var).get()
else:
variables["preview_font"] = preview_font
with open("config.ini", "w") as configfile:
config.write(configfile)
I don't know if it's relevant, too, but basically it does what the name says - updates the config file.
What I don't like about the function is that it requires an argument (val here, should be self maybe?). And 'cause it requires that argument, I can't call it "properly". Let me demonstrate, the following works just fine:
class OptionMenus(tk.OptionMenu):
def __init__(self, master, status, *fonts):
self.var = tk.StringVar(master)
(self.var).set(status)
(tk.OptionMenu).__init__(self, master, self.var, *fonts,
command = update_config)
However - calling like the following returns this error: TypeError: update_config() takes 0 positional arguments but 1 was given
class Spinboxes(tk.Spinbox):
def __init__(self, master):
self.var = tk.IntVar()
tk.Spinbox.__init__(self, master, textvariable = self.var,
from_ = 0, to = 100, command = update_config)
For now, I have solved it using this:
def crossover():
val = ""
update_config(val)
But it seems to be kind of a monkey-ish way to do things, so is there a better way to call that function?
Use a default argument value:
def update_config(val=None):
# etc.
You could also remove the argument entirely and use a single-argument lambda to call it in a context where that the argument must be passed:
def update_config():
# etc.
# ...
tk.OptionMenu.__init__(self, master, self.var, *fonts,
command=lambda _: update_config())
But I think the first option is simpler.
update_config looks like an instance method, so yes, I recommend using the accepted variable self.
If an error says it takes 0 arguments but 1 was given, that means exactly what it says. This means that calling update_config from the Spinbox object passes it an argument. However, since it works fine from OptionMenus and therefore works without an argument, you need to set it up to handle an optional argument.
Change:
def update_config(val):
to:
def update_config(self, event=None):
and that should fix the issue.
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.