I have this code:
from tkinter import *
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
def print_test(self):
print('test')
def button_click():
print_test()
super().__init__(master)
master.geometry("250x100")
entry = Entry()
test = DoubleVar()
entry["textvariable"] = test
entry.bind('<Key-Return>', print_test)
entry.pack()
button = Button(root, text="Click here", command=button_click)
button.pack()
root = tk.Tk()
myapp = App(root)
myapp.mainloop()
A click on the button throws:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "[somefilepath]", line 10, in button_click
print_test()
TypeError: App.__init__.<locals>.print_test() missing 1 required positional argument: 'self'
While pressing Enter
while in the Entry widget works, it prints:
test
See:
Now if I drop the (self)
from def print_test(self):
, as TypeError: button_click() missing 1 required positional argument: 'self' shows, the button works, but pressing Enter
in the Entry widget does not trigger the command but throws another exception:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
TypeError: App.__init__.<locals>.print_test() takes 0 positional arguments but 1 was given
How can I write the code so that both the button click event and pressing Enter will trigger the print command?
Commands callbacks for button clicks are called without arguments, because there is no more information that is relevant: the point of a button is that there's only one "way" to click it.
However, key presses are events, and as such, callbacks for key-binds are passed an argument that represents the event (not anything to do with the context in which the callback was written).
For key-press handlers, it's usually not necessary to consider any information from the event. As such, the callback can simply default this parameter to None
, and then ignore it:
def print_test(event=None):
print('test')
Now this can be used directly as a handler for both the key-bind and the button press. Note that this works perfectly well as a top-level function, even outside of the App
class, because the code uses no functionality from App
.
Another way is to reverse the delegation logic. In the original code, the button handler tries to delegate to the key-press handler, but cannot because it does not have an event object to pass. While it would work to pass None
or some other useless object (since the key-press handler does not actually care about the event), this is a bit ugly. A better way is to delegate the other way around: have the key-press handler discard the event that was passed to it, as it delegates to the button handler (which performs a hard-coded action).
Thus:
from tkinter import *
import tkinter as tk
def print_test():
print('test')
def enter_pressed(event):
print_test()
class App(tk.Frame):
def __init__(self, master):
super().__init__(master)
master.geometry("250x100")
entry = Entry()
test = DoubleVar()
entry["textvariable"] = test
entry.bind('<Key-Return>', enter_pressed)
entry.pack()
button = Button(root, text="Click here", command=print_test)
button.pack()
root = tk.Tk()
myapp = App(root)
myapp.mainloop()