带有或不带有对象继承的tkinter GUI

时间:2019-06-15 13:44:00

标签: python-3.x oop tkinter

我正在用GUI编写Python程序,并且已经选择tkinter来这样做。我对Python有一定的经验,但是对tkinter和GUI编程还是陌生的。

我一直在阅读一些有关如何使用tkinter的教程。我可以找到非常基本的内容,例如“如何显示按钮”。这使我难以找到一种有用的模型来构造程序中定义UI的部分。

到目前为止,我的搜索仅产生了1条关于以OOP样式构建python / tkinter GUI的指南:pythonprogramming.net

尽管这是一个受欢迎的示例,并且对它的特殊性很有帮助,但在我看来,tkinter类的继承和向这些新类中添加新的不相关代码违反了严格的关注点分离。从短期看,它很方便,但我不能确定它是否会长期产生不良后果。

作为替代方案,我创建了另一个示例,在该示例中,我制作了类似的类,但通过组合各种tkinter对象避免了从tkinter类继承。这样仅需使用几种其他方法即可将功能分开。

我希望您反馈随着UI复杂性的提高,哪种方法更有用。这可能包括有关要使用的其他模型的具体建议,有关该主题的信息的链接,使用tkinter的程序的源代码示例,等等。

基于pythonprogramming.net的继承示例:

import tkinter as tk


class AppMain(tk.Tk):

    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.frames = {}

        frame = Page(container, self)

        self.frames[Page] = frame

        frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(Page)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()


class Page(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        label = tk.Label(self, text="Start Page", font=("Verdana", 12))
        label.pack(padx=10, pady=10)


def main():
    application = AppMain()
    application.mainloop()


if __name__ == "__main__":
    main()

不继承的替代方法:

编辑1:将网格变量添加到Page init

import tkinter as tk


class AppMain(object):

    def __init__(self):
        self.root = tk.Tk()

        container = tk.Frame(self.root)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.pages = {}

        page = Page(container, self.root, {"row": 0, "column": 0, "sticky": "nsew"})

        self.pages[Page] = page

        self.show_page(Page)

    def show_page(self, container):
        page = self.pages[container]
        page.show()

    def run(self):
        self.root.mainloop()


class Page(object):

    def __init__(self, parent, controller, grid):
        self.frame = tk.Frame(parent)
        self.frame.grid(**grid)

        label = tk.Label(self.frame, text="Start Page", font=("Verdana", 12))
        label.pack(padx=10, pady=10)

    def show(self):
        self.frame.tkraise()


def main():
    application = AppMain()
    application.run()


if __name__ == "__main__":
    main()

1 个答案:

答案 0 :(得分:1)

从tkinter小部件(通常是Frame)继承的主要优点是,在布局UI时,您可以将该对象像其他任何小部件一样对待。

例如,典型的UI可能由工具栏,用于导航的侧面板,主要工作区以及底部的状态栏组成。通过为每个继承自Frame的类创建一个类,您可以像这样布置您的GUI:

toolbar = Toolbar(root)
sidebar = Sidebar(root)
main = WorkArea(root)
statusbar = Statusbar(root)

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

如果您使用合成而不是继承,那么您的主程序需要了解有关对象内部结构的知识,或者您的对象需要了解有关根窗口的知识。

例如,您可能必须使用通用名称命名每个部分的内部框架,以便主程序可以对其进行布局:

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

在您的问题中从object继承的示例中,您的Page类必须知道根窗口正在使用grid,并且还必须知道它需要放置在特定的行和列中。这将代码的这两个部分紧密地结合在一起-您无法修改代码的一个部分而不必修改其他部分。

例如,假设您有一打页面。在处理了一段时间的代码之后,您决定AppMain需要在容器的第零行中放置一个附加小部件。现在,您必须进入并修改所有十二个页面类,以便它们可以将自己置于第1行而不是第0行。