在Tkinter应用程序中,是否继承Frame类?

39

我看到了两种基本的设置 tkinter 程序的方式,有没有理由更喜欢其中的一种?

from Tkinter import *

class Application():
    def __init__(self, root, title):
        self.root = root
        self.root.title(title) 

        self.label = Label(self.root, text='Hello')
        self.label.grid(row=0, column=0)  

root = Tk()
app = Application(root, 'Sample App')
root.mainloop()

并且

from Tkinter import *

class Application(Frame):
    def __init__(self, title, master=None):
        Frame.__init__(self, master)
        self.grid()
        self.master.title(title) 

        self.label = Label(self, text='Hello')
        self.label.grid(row=0, column=0) 

app = Application('Sample App')
app.mainloop()   
3个回答

41

我更喜欢的选项是继承Tk类。我认为这是更合理的选择,因为窗口实际上就是您的应用程序。从Frame继承对我来说没有任何意义,就好像从ButtonCanvasLabel继承一样。由于您只能有一个根,所以从那里继承是有意义的。

我还认为,如果您使用import Tkinter as tk ,而不是from Tkinter import *,则代码更易读。这样您所有的调用都明确提到了tk模块。我不建议在所有模块中都这样做,但在我的看法中,这对于Tkinter来说是有意义的。

例如:

import Tkinter as tk

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.label = tk.Label(text="Hello, world")
        self.label.pack(padx=10, pady=10)

app = SampleApp()
app.mainloop()

* 注意: 自从最初写这个答案以来,我的立场已经改变。我现在更喜欢从Frame继承而不是从Tk继承。两种方式并没有实质性的优劣之分,更多的是一种哲学选择。无论如何,我认为无论你是从 Frame还是Tk继承,都比起代码中未继承任何类的情况要好。

Frame继承相较于从Tk继承有一个小优势,就是当你想让应用程序支持多个相同的窗口时。在这种情况下,从Frame继承可以让你创建第一个窗口作为根窗口的子级,将其他窗口作为Toplevel的实例的子级。但是,我很少见到有需要做到这一点的程序。

关于我认为Tkinter程序应该如何结构化的更多信息,请参阅我在Python Tkinter程序结构问题中的答案


1
@foosion:没有优势也没有劣势。您仍然具有完全相同的“美化”灵活性。如果您想获得特定的边框效果,仍然可以选择嵌入框架。我只是不理解将主应用程序对象设置为其他对象的子级的原因。从逻辑角度来看,应用程序对象应该是层次结构中最顶层的对象。 - Bryan Oakley
只是好奇,在哪个星球上设计良好的调整大小行为就像编写self.resizable(False, False)一样容易。仅供讨论; p - Darren Ringer
@DarrenRinger: 坦白地说,如果你采取一种有条不紊的方法来进行小部件布局,正确的调整大小行为就非常容易。记住,目标是编写满足用户需求的软件,而不是满足开发人员的需求。 - Bryan Oakley
@BryanOakley:作为开发人员,我的需求是满足用户的需求,所以我真的不理解那个说法。至于易用性,这就像说“去酒庄为你的约会买一瓶好的陈年佳酿和从街角店买一瓶Mad Dog 20/20一样容易——毕竟,这是你约会的需求,而不是你自己的需求”。后者虽然劣质,但肯定更容易。 - Darren Ringer
@BryanOakley:这是一个很好的论点,我想我只是被“容易”这个词的语义所困扰——对我来说,这仅仅意味着时间。你正确地指出每种情况都是不同的,但在用户坚持要求在30秒内完成窗口方法的情况下,如果你只有一个用于原型设计的模型,那么将事物锁定在它们已经存在的位置上会更容易。如果您通过代码行数来衡量“容易”,那么它是非常可量化的。实际上,我认为代码行数几乎毫无意义,我同意你所有的观点 ;) - Darren Ringer
显示剩余9条评论

14

Frame通常被用作其他小部件的几何主控件。 由于应用程序通常有许多小部件,您经常会想要将它们全部包含在一个Frame中,或者至少使用Frame添加一些边框、填充或其他美化效果。

您可能在网上找到许多示例代码片段,它们不使用Frame,因为它们只想用最短的代码演示一些功能。

因此,如果需要,请使用Frame,否则就不要用。

编辑:我认为组织GUI的最佳方法在这个Tkinter教程中给出:

simpleApp.py:

import Tkinter as tk

class SimpleApp(object):
    def __init__(self, master, **kwargs):
        title=kwargs.pop('title')
        frame=tk.Frame(master, **kwargs)
        frame.pack()
        self.label = tk.Label(frame, text=title)
        self.label.pack(padx=10,pady=10)

if __name__=='__main__':
    root = tk.Tk()
    app = SimpleApp(root,title='Hello, world')
    root.mainloop()

这主要类似于你的第一个例子,即SimpleApp继承自object,而不是Frame。我认为这比从Frame进行子类化更好,因为我们没有覆盖任何Frame方法。我更喜欢将SimpleApp视为具有Frame,而不是作为Frame

然而,让SimpleApp子类化object确实比子类化tk.Tk有显着优势:它使得将SimpleApp嵌入到较大应用程序中变得容易:

import simpleApp
import Tkinter as tk

class BigApp(object):
    def __init__(self, master, **kwargs):
        title=kwargs.pop('title')
        frame=tk.Frame(master, **kwargs)
        frame.pack()
        self.simple = simpleApp.SimpleApp(frame,title=title)
        frame.pack(padx=10, pady=10)
        self.simple2 = simpleApp.SimpleApp(frame,title=title)    
        frame.pack()

if __name__=='__main__':
    root = tk.Tk()
    app = BigApp(root,title='Hello, world')
    root.mainloop()
因此,simpleApp.py既可以作为独立脚本,也可以作为可导入的模块。如果您尝试使用SimpleApp从tk.Tk继承,则会出现额外的不需要的窗口。

@foosion:Frame文档显示了一些配置选项(是的,主要涉及边框),这些选项Frame具有而使用网格无法实现。如果您需要其中之一,请使用Frame;否则,请勿如此。 - unutbu
我们可以得出这样的结论:通常情况下,我们最好使用框架,因为它没有实际的额外成本,提供了更多的选项,并且不值得为每个程序考虑选择哪种方式。 - foosion
borderwidth=10,relief=SUNKEN 在我看来相当丑陋。 - foosion
Frame继承并没有什么坏处,但也没有太多意义。所有的框架都必须有一个父级,那为什么不让你的应用程序继承所有小部件的父级(即根窗口)呢? - Bryan Oakley
我尝试了这个例子,并将继承更改为tk.Tk,没有出现额外的窗口。 - m3nda
显示剩余6条评论

1
将顶级对象设置为继承自Tk而不是Frame可能会有优势。当GUI具有动态元素时,例如想用textvariable=foo而不是text='Label text'来设置Label内容时,这种优势就出现了。
在这种情况下,使用Tkinter.DoubleVarTkinter.IntVarTkinter.StringVar对象来保存数据非常有帮助,因为每当设置这些对象时,GUI都会自动更新。但是,要使用这些对象,必须将它们的主人指定为正在运行的根Tkinter.Tk()实例。如果您明确地使您的主要对象成为Tkinter.Tk的子类,然后生成框架和小部件,那么您可以传递Tk实例并正确设置变量,这样做会更容易。
以下是一个简短的示例程序,以说明这个想法。
import Tkinter as tk       

class Tkclass(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        app=Application(self)
        app.master.title("Animal to Meat")
        app.mainloop()

class Application(tk.Frame):    

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
        self.meatvar = tk.StringVar(master=parent)
        self.meatvar.set("Meat?")
        self.createWidgets()

    def createWidgets(self):
        top=self.winfo_toplevel()                
        top.rowconfigure(0, weight=1)            
        top.columnconfigure(0, weight=1)         
        self.rowconfigure(0, weight=1)           
        self.columnconfigure(0, weight=1) 
        self.columnconfigure(1, weight=1)  
        self.columnconfigure(2, weight=1)  
        self.columnconfigure(3, weight=1)  

        self.cowButton = tk.Button(self, text='Cow', command=self.setBeef)
        self.cowButton.grid(row=0,column=0)
        self.pigButton = tk.Button(self, text='Pig',command=self.setPork)
        self.pigButton.grid(row=0,column=1)
        self.meatLabel = tk.Label(self)
        self.meatLabel.configure(textvariable=self.meatvar)
        self.meatLabel.grid(row=0,column=2)
        self.quit = tk.Button(self, text='Quit',command=self.QuitApp)
        self.quit.grid(row=0, column=3)           

    def setBeef(self):
        self.meatvar.set("Beef")

    def setPork(self):
        self.meatvar.set("Pork")

    def QuitApp(self):
        top=self.winfo_toplevel()
        top.quit()

main = Tkclass() 

1
你的答案不完全正确。您可以将任何小部件指定为主控件,无需将其设置为根窗口。在从Frame继承时,您可以正常使用tkinter变量。 - Bryan Oakley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接