【问题标题】:tkinter button in class can't call function类中的 tkinter 按钮无法调用函数
【发布时间】:2018-02-05 02:16:12
【问题描述】:

完全的菜鸟,认真而愤怒地与 Python 斗争......

我想做的应该很简单:

  1. 制作一个按钮。
  2. 连接那个按钮去功能。
  3. 点击按钮 --> 运行函数。

当我们必须使用 CLASS 时问题就来了(无论我读了多少书,学习了多少,甚至花钱上课,对我来说仍然是零意义)...

我已经尝试了所有可以想象的组合,将这个小 convert() 函数放入类中,添加 self.convert 或 root.convert - 但没有一个有效。而且,我不知道为什么 - 或者接下来要尝试什么。

代码如下:

from tkinter import *
from tkinter.ttk import Frame, Button, Style


def convert():
    print("clicked")
    kg = entry_kg.get()
    print(kg)



class Example(Frame):

    def __init__(self):
        super().__init__()

    self.initUI()    # initiate the GUI
    # -------------------------------


    def initUI(self):

        self.master.title("Weight Converter")
        self.pack(fill=BOTH, expand=True)
        # -------------------------------------

        frame_kg = Frame(self)   # frame for Kilograms
        frame_kg.pack(fill=X)

        lbl_kg = Label(frame_kg, text="Kilograms", width=16)
        lbl_kg.pack(side=LEFT, padx=5, pady=5)

        entry_kg = Entry(frame_kg)
        entry_kg.pack(fill=X, padx=(5, 30), expand=True)
        # ------------------------------------------------

        frame_btn = Frame(self)    # frame for buttons
        frame_btn.pack(fill=BOTH, expand=True, padx=20, pady=5)

        btn_convert=Button(frame_btn, text="Convert", command=convert)
        btn_convert.pack(side=LEFT, padx=5, pady=5)

        # -------------------------------------------

def main():

    root = Tk()
    root.geometry("300x200+300+200")
    app = Example()
    root.mainloop()


if __name__ == '__main__':
    main()

我做错了什么?

怎么做才对?

一个简单的任务看似随意和无用的过度复杂化是非常令人抓狂的......

【问题讨论】:

  • 请修正代码的缩进。
  • TkInter 是一个旧接口,为了向后兼容和那些真正需要自包含可执行文件的人而保留。现代代码将使用像 Flask 这样的 Web 框架并提供一个简单的 HTML 页面。当然,这意味着学习 Flask 和 HTML。
  • 欢迎来到Stack Overflow。请尝试提供minimal reproducible example,而不是您当前的代码。您将更了解代码的哪些部分做了什么,并提高问题的质量以激发更好的答案。
  • @verisimilidude 我不明白这对 OP 有什么帮助。
  • 对 Bryan Oakley 的丑陋代码感到抱歉。我粘贴了,希望编辑保留缩进。而且,说真的?这个编辑器不仅不能维护现有的缩进,也不能使用制表符?严重地?我们必须坐下来按空格键?致 versimilidude(伟大的名字):非常感谢您的评论。我无知 - 我正在上 Udemy 上的一门课,上面说要使用 tkinter。现在,我很生气,因为我一直在为此撕扯头发。感谢您将我指向 Flask。猜猜那是下一个谷歌...

标签: python tkinter


【解决方案1】:

如果您希望在类之外设计函数,但又想从类中定义的按钮调用它,则解决方案是在您的类中创建一个方法,该方法获取输入值,然后将它们传递给外部功能。

这称为解耦。您的功能与 UI 的实现解耦,这意味着您可以在不更改功能的情况下完全更改 UI 的实现,反之亦然。这也意味着您可以在许多不同的程序中重复使用相同的功能而无需修改。

您的代码的整体结构应如下所示:

# this is the external function, which could be in the same
# file or imported from some other module
def convert(kg):
    pounds = kg * 2.2046
    return pounds

class Example(...):
    def initUI(self):
        ...
        self.entry_kg = Entry(...)
        btn_convert=Button(..., command=self.do_convert)
        ...

    def do_convert(self):
        kg = float(self.entry_kg.get())
        result = convert(kg)
        print("%d kg = %d lb" % (kg, result))

【讨论】:

    【解决方案2】:

    这是您的代码修改后的版本。更改已用全大写行 cmets 指示。我显然误解了你的问题(这确实说你可以弄清楚如何使 convert() 函数成为类的一部分。但是,你提到你想要相反的,所以我在这里相应地修改了代码。

    基本上问题归结为convert() 函数需要访问在其他地方创建的tkinter.Entry 小部件——在这种情况下是在您的Example 类中。

    这样做的一种方法是将小部件保存在全局变量中,并通过分配给它的变量名称访问它。许多人这样做是因为这样做最容易,但您应该知道,全局变量被认为是一种不好的做法,通常是应该避免的。

    有一种常用的方法可以避免使用tkinter,有时称为“The extra arguments trick”。要使用它,您需要做的就是创建一个简短的匿名函数,其参数定义了一个默认值——在这种情况下,它将是您想要传递给现在“包装”的convert() 函数的Entry 小部件。这可以使用所谓的lambda expression 来完成。下面修改后的代码和其中的 cmets 显示并描述了如何执行此操作:

    from tkinter import *
    from tkinter.ttk import Frame, Button, Style
    
    
    def convert(entry_widget):  # ADDED WIDGET ARGUMENT
        """ Some function outside class. """
        print("clicked")
        kg = entry_widget.get()  # REFERENCE ENTRY WIDGET PASSED AS ARGUMENT
        print(kg)
    
    
    class Example(Frame):
        def __init__(self):
            super().__init__()
    
            self.initUI()    # initiate the GUI
    
        def initUI(self):
            self.master.title("Weight Converter")
            self.pack(fill=BOTH, expand=True)
    
            frame_kg = Frame(self)   # frame for Kilograms
            frame_kg.pack(fill=X)
    
            lbl_kg = Label(frame_kg, text="Kilograms", width=16)
            lbl_kg.pack(side=LEFT, padx=5, pady=5)
    
            entry_kg = Entry(frame_kg)
            entry_kg.pack(fill=X, padx=(5, 30), expand=True)
    
            frame_btn = Frame(self)    # frame for buttons
            frame_btn.pack(fill=BOTH, expand=True, padx=20, pady=5)
    
            btn_convert=Button(frame_btn, text="Convert",
                            # DEFINE ANONYMOUS FUNCTION WITH DEFAULT ARGUMENT SO IT'S
                            # AUTOMATICALLY PASSED TO THE TARGET FUNCTION.
                            command=lambda entry_obj=entry_kg: convert(entry_obj))
            btn_convert.pack(side=LEFT, padx=5, pady=5)
    
    
    def main():
        root = Tk()
        root.geometry("300x200+300+200")
        app = Example()
        root.mainloop()
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 感谢您的帮助。非常感激。我想将函数保留在接口类之外,但是,唉,这似乎是不可能的......
    • 约翰:显然我误解了你想要的。查看更新的答案。
    • 非常感谢您的更新。这里的人太棒了!而且,男孩,我现在有问题。您将 entry_widget 参数添加到 def convert() 函数。如果你不介意我问,为什么我们传递小部件对象而不是其中包含的值?
    • 这里还有人讨厌这个评论编辑器吗?其他人注意到它也不起作用 - 不遵循下面的帮助所说的格式化规则?
    • 按钮函数中的部分行是:command=lambda entry_obj=entry_kg: convert(entry_obj))。为什么我们不能只使用 command=lambda: convert(entry_kg.get()) ??
    【解决方案3】:

    您的entry_kginitUI 方法范围之外的任何地方已知。因此。您可以通过替换将其从方法变量转换为实例属性,以便类方法可以使用:

    entry_kg = Entry(frame_kg)
    entry_kg.pack(fill=X, padx=(5, 30), expand=True)
    

    与:

    self.entry_kg = Entry(frame_kg)
    self.entry_kg.pack(fill=X, padx=(5, 30), expand=True)
    

    只有那么

    您可以在类方法中提及它,例如:

    ...
    kg = self.entry_kg.get()
    

    如果你让你的convert 再次成为Example 下的一个方法,这样你:

    def initUI(self):
    ...
    def convert(self):           # is defined under the same scope as initUI
        print("clicked")
        kg = self.entry_kg.get() # notice that it now refers to self.e...
        print(kg)
    

    也不要忘记替换命令选项:

    btn_convert=Button(..., command=self.convert)
    

    或者只有那么

    在类范围之外,通过在类创建的对象上使用点表示法:

    def main():
    
        root = Tk()
        root.geometry("300x200+300+200")
        app = Example()
        kg = app.entry_kg.get() # This would return an empty string, but it would still be valid
        root.mainloop()
    

    要与全局方法一起使用,例如convert 的当前状态,您需要将app(它是其属性的对象)设为全局,或者将其显式传递给方法。

    【讨论】:

    • @JohnBailey 有,但有点乱。您是否在“添加评论”按钮下看到帮助链接?它解释了一点。
    • 感谢您的耐心等待。
      我必须学习一个全新的编辑器才能发表评论?
      而且,这仍然行不通。这里没有换行符
    • @JohnBailey 好吧,你没有来格式化你的 cmets。但是,是的,这里的规则确实适用于帖子,但反之则不然。
    • 再次感谢您。我得到了第一个版本,但这使我无法将函数放入类中 - 这不是我想要组织它的方式。我想要一个用于 tkinter 接口的类和一个用于函数的不同位置。
    • 帮助中的说明不起作用。行尾的两个空格不会创建换行符。两个空格。两个空格。
    猜你喜欢
    • 2017-08-05
    • 1970-01-01
    • 1970-01-01
    • 2013-08-21
    • 1970-01-01
    • 2015-09-23
    • 1970-01-01
    • 2021-08-27
    • 1970-01-01
    相关资源
    最近更新 更多