如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

huangapple go评论80阅读模式
英文:

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(&#39;test&#39;)

        def button_click():
            print_test()

        super().__init__(master)
        master.geometry(&quot;250x100&quot;)
        entry = Entry()

        test = DoubleVar()
        entry[&quot;textvariable&quot;] = test
        entry.bind(&#39;&lt;Key-Return&gt;&#39;, print_test)
        entry.pack()
        button = Button(root, text=&quot;Click here&quot;, 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 &quot;C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py&quot;, line 1921, in __call__
    return self.func(*args)
  File &quot;[somefilepath]&quot;, line 10, in button_click
    print_test()
TypeError: App.__init__.&lt;locals&gt;.print_test() missing 1 required positional argument: &#39;self&#39;

While pressing Enter while in the Entry widget works, it prints:
test

See:

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

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:

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

Exception in Tkinter callback
Traceback (most recent call last):
  File &quot;C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py&quot;, line 1921, in __call__
    return self.func(*args)
TypeError: App.__init__.&lt;locals&gt;.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(&#39;test&#39;)

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(&#39;test&#39;)

def enter_pressed(event):
    print_test()

class App(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        master.geometry(&quot;250x100&quot;)

        entry = Entry()
        test = DoubleVar()
        entry[&quot;textvariable&quot;] = test
        entry.bind(&#39;&lt;Key-Return&gt;&#39;, enter_pressed)
        entry.pack()

        button = Button(root, text=&quot;Click here&quot;, 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()

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

根据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()

再次输出:

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

英文:

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(&#39;test&#39;)

        def button_click():
            print_test()

        super().__init__(master)
        master.geometry(&quot;200x50&quot;)
        entry = Entry()

        test = DoubleVar()
        entry[&quot;textvariable&quot;] = test
        entry.bind(&#39;&lt;Key-Return&gt;&#39;, print_test)
        entry.pack()
        button = Button(root, text=&quot;Click here&quot;, command=button_click)
        button.pack()

root = tk.Tk()
myapp = App(root)
myapp.mainloop()

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

Taking up the comment by Bryan Oakley, here is why this is needed:

a bound function (entry.bind(&#39;&lt;Key-Return&gt;&#39;, 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(&#39;test&#39;)

        def button_click():
            print_test(self)

        super().__init__(master)
        master.geometry(&quot;200x50&quot;)
        entry = Entry()

        test = DoubleVar()
        entry[&quot;textvariable&quot;] = test
        entry.bind(&#39;&lt;Key-Return&gt;&#39;, print_test)
        entry.pack()
        button = Button(root, text=&quot;Click here&quot;, command=button_click)
        button.pack()

root = tk.Tk()
myapp = App(root)
myapp.mainloop()

Out again:

如何重复使用逻辑来处理Python的tkinter GUI中的按键和按钮点击?

huangapple
  • 本文由 发表于 2023年5月18日 06:26:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76276568.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定