Python:Tkinter:为什么是root.mainloop()而不是app.mainloop()

5 投票
3 回答
35265 浏览
提问于 2025-04-18 00:13

我刚加入Stack Overflow这个社区。 我发现了这个讨论,但不能在上面评论或提问,所以我想在这里提一下:如何在Python的Tkinter中制作一个带有按钮的互动列表,可以编辑这些列表项?

from tkinter import *
import os
import easygui as eg

class App:

    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        # character box
        Label(frame, text = "Characters Editor").grid(row = 0, column = 0, rowspan = 1, columnspan = 2)
        charbox = Listbox(frame)
        for chars in []:
            charbox.insert(END, chars)
        charbox.grid(row = 1, column = 0, rowspan = 5)
        charadd = Button(frame, text = "   Add   ", command = self.addchar).grid(row = 1, column = 1)
        charremove = Button(frame, text = "Remove", command = self.removechar).grid(row = 2, column = 1)
        charedit = Button(frame, text = "    Edit    ", command = self.editchar).grid(row = 3, column = 1)

    def addchar(self):
        print("not implemented yet")
    def removechar(self):
        print("not implemented yet")
    def editchar(self):
        print("not implemented yet")


root = Tk()
root.wm_title("IA Development Kit")
app = App(root)
root.mainloop()

有人能告诉我为什么最后一行是root.mainloop()吗? 我还是个Python新手,之前的编程背景都是面向过程的,没有面向对象的经验,我本以为应该是app.mainloop()。

实际上,app = App(root)这行代码之后,app在后面的代码中根本没再用到!我很困惑,为什么root.mainloop()还能正常工作。

3 个回答

0

App对象就是你的应用程序代码,调用App(root)的原因是为了用你的类创建一个实例,这样它就可以访问你的根窗口。

这个实例是在__init__方法中接收这个根窗口的引用:

def __init__(self, master):
    # master refers to the root window now
    ...

你可以看到App对象的完整定义(从class App:开始的那一块),而且它甚至没有mainloop方法,所以要启动Tkinter的主循环,你必须在根窗口上调用它。

Python2文档中的示例里,他们确实像你猜测的那样调用了它,但要注意,他们的示例类是从Tk对象Frame继承而来的。而在你的示例代码中,App是一个旧式类,没有继承任何东西。

1

我测试了你看到的这两种写法:

一种是用"app." + ".pack()",另一种是用"mainframe." + ".grid()"来调用。

 #-*- coding: utf-8 -*-
#THIS IS THE "MAINFRAME." - PART
from Tkinter import *
import ttk

def show():
    p = password.get() #get password from entry
    print(p)


root = Tk()
root.title("Ingos first program")

mainframe = ttk.Frame(root, padding="30 30 60 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)


password = StringVar() #Password variable
passEntry = Entry(mainframe, textvariable=password, show='*').grid(column=3, row=3, sticky=S)
submit = Button(mainframe, text='Show Console',command=show).grid(column=3, row=4, sticky=S)

root.mainloop()
def show():
    p = password.get() #get password from entry
    print(p)

#THIS IS THE "APP."-PART. BOTH WORKS FINE.
app = Tk()
app.title("Ingos first program")
password = StringVar() #Password variable
passEntry = Entry(app, textvariable=password, show='#').pack()
submit = Button(app, text='Show Console',command=show).pack()

app.mainloop()

这个实例在python 2.7上运行得很好。在这个测试中,甚至连app.也可以处理"mainloop()"这个命令。这个脚本会依次打开两个窗口(如果你关闭第一个窗口的话),而第一个程序的格式也很好,我没有尝试在pack()里写column=3之类的东西。

我还是刚开始学习Tkinter,所以别和我争,我只是试试看.. 希望我能帮到你解答问题。

祝一切顺利,Ingo

8

我不确定这个回答是否能让你满意,但你需要调用 root.mainloop() 是因为 root 是一个有 mainloop 方法的对象。在你给出的代码中,App 并没有 mainloop 函数。

简单来说,这就是 tkinter 的工作方式——你总是通过调用根窗口的 mainloop 方法来结束你的脚本。当这个方法返回时,你的程序就会退出。

我们从头开始。最简单的非面向对象的 Tkinter 程序看起来会像下面这个例子。请注意,这是一个 Python 2.x 的例子,我不使用全局导入,因为我认为全局导入不好。

import Tkinter as tk
root = tk.Tk()
<your widgets go here>
root.mainloop()

很多人发现纯过程式的风格并不是写代码的有效方式,所以他们可能会选择用面向对象的风格来写。这种情况下,把“应用程序”看作一个单例对象是很自然的。有很多方法可以做到这一点——但你提到的那种方法不幸的是不是最清晰的方式。

我认为稍微好一点的方法是把代码结构化成这样:

class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        <your widgets go here>
app = App()
app.mainloop()

在这种情况下,mainloop 仍然被调用,不过现在它是 App 的一个方法,因为 App 继承自 Tk。从概念上讲,这和 root.mainloop() 是一样的,因为在这个情况下,app 就是根窗口,尽管它有一个不同的名字。

所以,在这两种情况下,mainloop 是属于根窗口的方法。而且在这两种情况下,都必须调用它,才能让图形界面正常工作。

还有第三种变体,就是你选择的代码所使用的。在这种变体中,有几种实现方式。这种变体是你的问题使用了一个类来定义图形界面,但并没有从 Tk 继承。这完全没问题,但你仍然必须在某个时刻调用 mainloop。因为你在类中没有创建(或继承)一个 mainloop 函数,所以你必须调用与根窗口关联的那个。这里我说的变体是指 App 的实例是如何以及在哪里添加到根窗口的,以及 mainloop 最终是如何被调用的。

就我个人而言,我更喜欢 App 继承自 Frame,并且你在应用程序的定义之外打包应用。我的模板看起来像这样:

class App(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        <your widgets go here>

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    app.pack(fill="both", expand=True)
    root.mainloop()

在这个最后的例子中,approot 是两个完全不同的对象。app 代表一个存在于根窗口 内部 的框架。框架通常以这种方式使用,作为其他小部件的容器。

所以,在所有情况下,都必须调用 mainloop在哪里调用它,以及如何调用,稍微取决于你的编码风格。有些人喜欢从根窗口继承,有些人则不喜欢。无论哪种情况,你都必须调用根窗口的 mainloop 函数。

撰写回答