具有或不具有对象继承的 tkinter GUI
Posted
技术标签:
【中文标题】具有或不具有对象继承的 tkinter GUI【英文标题】:tkinter GUI with or without object inheritance 【发布时间】:2019-06-15 13:44:00 【问题描述】:我正在编写一个带有 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:将网格变量添加到页面初始化
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】:从 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
需要在容器的第 0 行放置一个额外的小部件。您现在必须进入并修改所有十几个页面类,以便它们可以将自己放置在第 1 行而不是第 0 行。
【讨论】:
在需要修改超类版本的情况下,继承不是更有帮助吗?这里从 Frame 继承用于 Page 类,向我表明它不打算用作修改后的 Frame,而是用作布局类。因此,考虑到 Page 是否“是”框架,我会说不。此外,继承破坏了 Frame 的封装。如果需要一个用于包含或布局的类,那么为什么不创建该类呢?从需要布局的小部件继承并将其他小部件组合到其中,在我看来就像将关注点混合在一起。 “此外,继承破坏了 Frame 的封装。如果需要用于包含或布局的类,那么为什么不创建该类呢?” - 因为没有办法在python中做到这一点。您将不得不修改底层的 C 实现。如果你想要“一个用于包含或布局的类”,那么从Frame
继承正是你这样做的方式 - 你从 Frame
继承,然后使用你需要的任何附加功能对其进行扩展。以上是关于具有或不具有对象继承的 tkinter GUI的主要内容,如果未能解决你的问题,请参考以下文章