英文:
How can I reuse logic to handle a keypress and a button click in Python's tkinter GUI?
问题
I have translated the code portion for you:
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()
If you need further assistance or have questions about this code, please feel free to ask.
英文:
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 to write the code so that both the button click event and pressing Enter will trigger the print command?
答案1
得分: 3
以下是您要翻译的内容:
"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()
请注意,这是您提供的代码的翻译部分。
英文:
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()
答案2
得分: 1
注意,这个答案是错误的,仅用于展示错误
这里的名称"self
"毫无意义。答案虽然有效,但不是正确的命名事件参数的方式,因为传递的"tk.Event
"参数与调用print_test
函数的类无关。请查看其他答案和评论,以获取所需参数event
而不是self
(或者您可以将事件命名为其他名称,例如tkevent
,以显示它是一个tkinter事件,但不要混淆使用"self
"。将其命名为"event
"或"tkevent
"或"tk_event
"或类似的名称可以清楚地表明这是关于tk.Event
而不是其他任何内容)。我将保留这个"self=None
/self
"答案,以更好地理解这个错误,不要将tkinter事件命名为"self
"。
答案(写self
有效,但在这里是错误的)
将事件参数命名为self
混淆了按钮的事件与类的self
的命名。这个类和按钮事件之间没有任何关系,因此参数名称不应该显示任何两者之间的链接。
使用self=None
将参数设置为可选:
from tkinter import *
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
def print_test(self=None):
print('test')
def button_click():
print_test()
super().__init__(master)
master.geometry("200x50")
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()
根据Bryan Oakley的评论,这是为什么需要这样做的原因:
绑定的函数(entry.bind('<Key-Return>', print_test)
)传递了一个参数,而通过command
属性调用的函数(command=button_click
)没有传递参数。
使这两种触发方式都能正常工作而不报错的另一种方法是从button_click
函数中调用print_test(self)
而不是print_test()
。尽管看起来很简单,但在更复杂的代码中我没有这样做。
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(self)
super().__init__(master)
master.geometry("200x50")
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()
再次输出:
英文:
Mind, this answer is wrong, it is kept only to show the mistake
The name "self
" does not make any sense here. The answer works as such but is not the right way to name the event parameter since the passed "tk.Event" argument has nothing to do with the class from where the print_test function is called. Check the other answer and comments for the needed parameter event
instead of self
(or you could name the event whatever, for example tkevent to show that it is a tkinter event, but not confusingly "self". Naming it "event" or "tkevent" or "tk_event" or the like makes clear rightaway that this is about the tk.Event and nothing else). I leave this self=None
/self
answer just for a better understanding of this mistake, do not name the tkinter event "self
".
Answer (writing self
works but is wrong here)
Naming the event argument self
mixes up the naming of the event of the button with self
of the class. The class and the button event have nothing to do with each other, therefore the parameter names should not show any link between the two.
Make the argument optional with self=None
:
from tkinter import *
import tkinter as tk
class App(tk.Frame):
def __init__(self, master):
def print_test(self=None):
print('test')
def button_click():
print_test()
super().__init__(master)
master.geometry("200x50")
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()
Taking up the comment by Bryan Oakley, here is why this is needed:
a bound function (entry.bind('<Key-Return>', print_test)
) is passed an argument while a function called via the command
attribute (command=button_click
) is not.
Another way to make both triggers work without an error is to call print_test(self)
instead of print_test()
from the button_click
function. Easy as it seems, I did not in a more complex 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(self)
super().__init__(master)
master.geometry("200x50")
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()
Out again:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论