构造tkinter应用程序的最佳方法?


136

以下是我典型的python tkinter程序的整体结构。

def funA():
    def funA1():
        def funA12():
            # stuff

    def funA2():
        # stuff

def funB():
    def funB1():
        # stuff

    def funB2():
        # stuff

def funC():
    def funC1():
        # stuff

    def funC2():
        # stuff


root = tk.Tk()

button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()

funA funB并在用户单击按钮1、2、3时funC打开另一个Toplevel带有窗口小部件的窗口。

我想知道这是否是编写python tkinter程序的正确方法吗?当然,即使我这样写也可以,但这是最好的方法吗?这听起来很愚蠢,但是当我看到其他人编写的代码时,他们的代码并没有弄乱一堆函数,而且大多数情况下它们都有类。

有没有作为良好实践应遵循的特定结构?开始编写python程序之前,我应该如何计划?

我知道编程中没有最佳实践之类的东西,我也不需要。在我自己学习Python时,我只想一些建议和解释就可以使我保持正确的方向。


2
这是有关tkinter GUI设计的出色教程,并提供了两个示例-python-textbok.readthedocs.org/en/latest/…这是另一个具有MVC设计模式的示例-sukhbinder.wordpress.com/2014/12/ 25 /…
Bondolin

12
这个问题可能很广泛,但是它是有用的,并且h是一个相对流行的答案(相对于几乎所有其他[tkinter]答案)。我要重新打开,因为我看到打开它比关闭它有用。
布莱恩·奥克利

Answers:


271

我主张一种面向对象的方法。这是我开始的模板:

# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        <create the rest of your GUI here>

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

需要注意的重要事项是:

  • 我不使用通配符导入。我将软件包导入为“ tk”,这要求我在所有命令前加上tk.。这样可以防止全局命名空间污染,并且在使用Tkinter类,ttk类或您自己的某些类时,使代码完全显而易见。

  • 主要应用是一类。这为您的所有回调和私有函数提供了私有名称空间,并且通常使组织代码更容易。在过程样式中,您必须自上而下进行编码,在使用函数之前定义函数等。使用此方法,您无需真正地在最后一步之前创建主窗口。我更喜欢从中继承,tk.Frame因为我通常从创建框架开始,但这绝不是必需的。

如果您的应用程序具有其他顶级窗口,建议您将每个窗口都设为一个单独的类,并从继承tk.Toplevel。这为您提供了上述所有相同的优点-窗口是原子的,它们具有自己的名称空间,并且代码井井有条。另外,一旦代码开始变大,就可以很容易地将每个模块放入自己的模块中。

最后,您可能要考虑对接口的每个主要部分使用类。例如,如果您要创建一个带有工具栏,导航窗格,状态栏和主区域的应用程序,则可以使每个类成为一个类。这使您的主要代码非常小,易于理解:

class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.statusbar = Statusbar(self, ...)
        self.toolbar = Toolbar(self, ...)
        self.navbar = Navbar(self, ...)
        self.main = Main(self, ...)

        self.statusbar.pack(side="bottom", fill="x")
        self.toolbar.pack(side="top", fill="x")
        self.navbar.pack(side="left", fill="y")
        self.main.pack(side="right", fill="both", expand=True)

由于所有这些实例共享一个公共父对象,因此该父对象实际上成为了模型-视图-控制器体系结构的“控制器”部分。因此,例如,主窗口可以通过调用在状态栏上放置一些内容self.parent.statusbar.set("Hello, world")。这使您可以在组件之间定义一个简单的接口,从而有助于保持最小的耦合。


22
@Bryan Oakley您知道互联网上有什么好的示例代码可以用来研究其结构吗?
克里斯·昂

2
我第二种面向对象的方法。但是,根据我的经验,不要在调用GUI的类上使用继承是一个好主意。如果Tk和Frame对象都是不从任何东西继承的类的属性,则它将为您提供更大的灵活性。这样一来,您可以更轻松地(并且不那么模棱两可)访问Tk和Frame对象,并且如果不想破坏一个对象,则不会破坏该类中的所有对象。我忘记了在某些程序中至关重要的确切原因,但是它确实允许您执行更多操作。
Brōtsyorfuzthrāx

1
不会仅仅让一个类为您提供一个私有名称空间?为什么将框架子类化对此有所改善?
gcb

3
@gcb:是的,任何类都会为您提供一个私有名称空间。为什么要继承框架?无论如何,我通常都会创建一个框架,所以它要少管理一个类(Frame的子类,而从object继承的类,以frame作为属性)。我已将答案略微改写,以使其更清楚。感谢您的反馈。
布莱恩·奥克利

2
@madtyn:无需保存对的引用parent,除非稍后要使用它。我没有保存它,因为示例中的任何代码都不需要保存它。
Bryan Oakley's

39

将每个顶级窗口放入自己的单独类中,可以使代码重用并更好地组织代码。窗口中存在的任何按钮和相关方法都应在此类内定义。这是一个示例(从此处获取):

import tkinter as tk

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow)

class Demo2:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
        self.quitButton.pack()
        self.frame.pack()
    def close_windows(self):
        self.master.destroy()

def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

另请参阅:

希望有帮助。


6

这不是一个坏结构。它会很好地工作。但是,当某人单击按钮或其他内容时,您必须在函数中具有执行命令的功能

因此,您可以为这些编写类,然后在该类中具有处理按钮单击等命令的方法。

这是一个例子:

import tkinter as tk

class Window1:
    def __init__(self, master):
        pass
        # Create labels, entries,buttons
    def button_click(self):
        pass
        # If button is clicked, run this method and open window 2


class Window2:
    def __init__(self, master):
        #create buttons,entries,etc

    def button_method(self):
        #run this when button click to close window
        self.master.destroy()

def main(): #run mianloop 
    root = tk.Tk()
    app = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

通常带有多个窗口的tk程序是多个大类,并且在__init__所有条目中创建标签等,然后每种方法都将处理按钮单击事件

只要有可读性,实际上就没有正确的方法,只要它对您有用并且可以完成工作,并且您可以轻松地解释它,因为如果您不能轻松地解释您的程序,那么可能会有更好的方法。

看一看《Tkinter中的思考》


3
“在Tkinter中思考”提倡全球进口,我认为这是非常糟糕的建议。
布莱恩·奥克利

1
Thts真正的我不建议你使用全局变量只是一些主类methos结构的youre吧:)
串行

2

OOP应该是方法,frame应该是类变量而不是实例变量

from Tkinter import *
class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print "Tkinter is easy to use!"

root = Tk()
app = App(root)
root.mainloop()

在此处输入图片说明

参考:http : //www.python-course.eu/tkinter_buttons.php


2
您只能TKinter在Python 2上使用。我建议tkinter在Python 3上使用。我还将最后三行代码放在一个main()函数下,并在程序结尾处调用它。我绝对会避免使用from module_name import *它,因为它会污染全局名称空间,并且会降低可读性。
扎克

1
你怎么能告诉之间的差异button1 = tk.Button(root, command=funA)button1 = ttk.Button(root, command=funA)如果tkinter扩展模块也正在进口的?使用该*语法,两行代码似乎都是button1 = Button(root, command=funA)。我不建议使用该语法。
扎克

0

使用类对应用程序进行组织可以使您和与您一起工作的其他人轻松调试问题并轻松改进应用程序。

您可以像这样轻松地组织您的应用程序:

class hello(Tk):
    def __init__(self):
        super(hello, self).__init__()
        self.btn = Button(text = "Click me", command=close)
        self.btn.pack()
    def close():
        self.destroy()

app = hello()
app.mainloop()

-2

学习如何构建程序的最佳方法可能是阅读他人的代码,尤其是如果这是许多人都为之贡献的大型程序。在查看了许多项目的代码之后,您应该了解共识样式应该是什么。

作为一种语言,Python的特殊之处在于,在如何格式化代码方面存在一些严格的指导原则。第一个是所谓的“ Python禅”:

  • 美丽胜于丑陋。
  • 显式胜于隐式。
  • 简单胜于复杂。
  • 复杂胜于复杂。
  • 扁平比嵌套更好。
  • 稀疏胜于密集。
  • 可读性很重要。
  • 特殊情况还不足以打破规则。
  • 尽管实用性胜过纯度。
  • 错误绝不能默默传递。
  • 除非明确地保持沉默。
  • 面对模棱两可的想法,拒绝猜测的诱惑。
  • 应该有一种-最好只有一种-显而易见的方法。
  • 尽管除非您是荷兰人,否则一开始这种方式可能并不明显。
  • 现在总比没有好。
  • 虽然从未往往比了。
  • 如果实现难以解释,那是个坏主意。
  • 如果实现易于解释,则可能是个好主意。
  • 命名空间是一个很棒的主意-让我们做更多这些吧!

在更实际的水平上,有Python的样式指南PEP8

考虑到这些,我会说您的代码风格并不适合,特别是嵌套函数。通过使用类或将它们移到单独的模块中,找到一种解决方案。这将使程序的结构更容易理解。


12
-1用于使用Python的Zen。尽管这都是很好的建议,但它并不能直接解决所提出的问题。删除最后一段,这个答案可能适用于该站点上几乎所有的python问题。这是一个很好的积极建议,但并不能回答问题。
Bryan Oakley

1
@BryanOakley我不同意你的看法。是的,Python的Zen用途广泛,可用于解决许多问题。他确实在最后一段中提到选择类或将功能放在单独的模块中。他还提到了PEP8,它是Python的样式指南,并对其进行了引用。尽管不是直接的答案,但我认为这个答案是可信的,因为它提到了许多可以采取的不同途径。那只是我的意见
-Zac

1
我来这里是为了寻找这个特定问题的答案。即使是一个开放式问题,我也无法对这个回答做任何事情。-1也来自我
乔纳森

没办法,问题是要构建一个tkinter应用程序,而不是样式/编码/禅定准则。引用@Arbiter“虽然不是直接答案”很容易,所以它不是答案。这就像“也许是,也许不是”,以禅宗为前提。
m3nda

-7

我个人不使用面向对象的方法,主要是因为a)只会妨碍;b)您永远不会将其作为模块重复使用。

但是这里没有讨论的是必须使用线程或多处理。总是。否则您的应用程序将很糟糕。

只需做一个简单的测试:启动一个窗口,然后获取一些URL或其他内容。所做的更改是在网络请求发生时不会更新您的用户界面。意思是,您的应用程序窗口将被破坏。取决于您所使用的操作系统,但是大多数情况下,它不会重绘,在窗口上拖动的任何内容都将粘贴在它上面,直到该过程返回到TK mainloop。


4
您所说的完全不对。我已经编写了数百个基于tk的应用程序,包括个人应用程序和商业应用程序,几乎从未使用过线程。线程有它们的位置,但是在编写tkinter程序时必须使用它们是不对的。如果您具有长期运行的功能,则可能需要线程或多处理程序,但是您可以编写很多类型的不需要线程的程序。
布莱恩·奥克利

我认为,如果您将答案改写得更清楚一点,那将是一个更好的答案。有了在tkinter中使用线程的规范示例也将真正有帮助。
布莱恩·奥克利

不在乎是这里的最佳答案,因为这有点题外话。但请记住,从线程/乘法开始非常容易。如果您以后必须添加,那将是一场失败的战斗。如今,绝对没有任何应用程序无法与网络对话。即使您忽略并认为“我的磁盘IO很少”,明天您的客户端也会决定该文件将驻留在NFS上,并且您正在等待网络IO,并且您的应用程序似乎已死。
gcb 2015年

2
@ erm3nda:“使用线程或子进程,每个与网络连接或进行IO写入的应用程序都将更快” –事实并非如此。线程并不一定会使您的程序更快,并且在某些情况下会使其变慢。在GUI编程中,使用线程的主要原因是能够运行一些否则会阻塞GUI的代码。
Bryan Oakley

2
@ erm3nda:不,我不是不需要说的线程在所有。对于很多事情,它们绝对是必需的(很好,线程或多处理)。只是有大量的GUI应用程序适用于tkinter,但根本不需要线程。是的,“安装程序,记事本和其他简单工具”属于此类。世界不是由word,excel,photoshop等组成的,而是由这些“简单工具”组成的。此外,请记住,这里的上下文是tkinter。Tkinter通常不用于非常大型,复杂的应用程序。
Bryan Oakley
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.