Tkinter和GUI编程方法

3
希望这不属于“一般讨论话题”,因为我希望它更多地关注如何以高效的方式解决问题,而不是就GUI编程的一般方法进行大型辩论。
所以我开始使用tkinter进行GUI编程,长话短说,我的代码很快变得非常丑陋。我试图创建一个基于瓷砖的地图编辑器用于视频游戏。我的主要问题似乎是:
1. 回调函数无法返回值。 2. 在窗口之间轻松传输数据的能力有限。
我认为我之所以将这些视为问题,是因为我更多地使用函数而不是类。例如,我的“加载瓷砖集”窗口完全由函数处理:在主窗口中点击菜单选项调用函数来加载新窗口。在该窗口内,当查找图像时,我创建了一个打开文件对话框,并在按下回车键时修改显示图像的画布(以便绘制适当的网格)。函数,函数,函数。
对我来说看起来像是非常糟糕的做法是包含额外的参数来补偿。例如,当我创建一个瓷砖集时,应该将创建的TileSet类的实例发送回到主窗口,以便显示适当的信息。我有一个已加载的瓷砖集列表作为全局变量(更糟糕的做法:与我的根窗口有关的所有内容都在全局范围内!),因为回调函数无法返回值,我将该列表作为参数传递给我的“加载瓷砖集窗口”函数,然后再将参数传递给创建瓷砖集函数(当您单击窗口中的适当按钮时调用),在那里它实际上是需要的,以便我可以将新创建的瓷砖集添加到列表中。通过这样的函数“层次结构”传递参数似乎是一个可怕的想法。它变得混乱,对于编写模块化代码来说非常糟糕,而且似乎是不必要的。
我的解决问题的尝试是编写代表整个GUI的类,并且自定义窗口类(GUI类可以创建和引用)可以实际存储相关数据。这应该解决在窗口之间传输数据的问题。希望这也能减少我在回调中使用lambda函数的滥用。但是我想知道:这是最好的方法吗?或者至少接近了吗?我不想开始重写,然后最终得到另一个以不同方式笨拙和混乱的系统。我知道我的方法很糟糕,但我不知道最佳方法是什么。我得到了很多关于如何做特定事情的建议,但没有关于如何整体构建程序的建议。任何帮助都将不胜感激。

1
Button(root, text="foo", command=lambda arg="foo": callback(arg)) - Steven Rumbalski
1个回答

8
看起来你正在尝试创建一个过程化的GUI,这是不行的。GUI不是过程化的,它们的代码不是按线性运行的,函数调用回调并返回值。你所问的并不是tkinter特有的。这是事件驱动GUI编程的本质——回调不能返回任何东西,因为调用者是事件而不是函数。
粗略地说,你必须使用某种全局对象来存储你的数据。通常称之为“模型”。它可以是全局变量,也可以是数据库,或者是某种对象。无论如何,它必须存在于整个GUI中可访问的“全局”位置。
通常,这种访问是由第三个组件提供的,称为“控制器”。它是GUI(“视图”)和数据(“模型”)之间的接口。这三个组件构成了所谓的模型-视图-控制器模式,或MVC。
模型、视图和控制器不一定是三个不同的对象。通常,GUI和控制器是同一个对象。对于小程序来说,这很有效——GUI组件直接与数据模型交互。
例如,你可以有一个表示窗口的类,它继承自Tkinter.Toplevel。它可以有一个属性,表示正在编辑的数据。当用户从主窗口选择“新建”时,它会执行类似于self.tileset = TileSet(filename)的操作。也就是说,它将名为self的GUI对象的名为tileset的属性设置为给定文件名特定的TileSet类的实例。稍后操纵数据的函数使用self.tileset来访问对象。对于生活在主窗口对象之外的函数(例如从主窗口的“全部保存”函数),你可以将此对象作为参数传递,或者使用窗口对象作为控制器,要求它对其瓷砖集执行某些操作。
以下是一个简短的示例:
import Tkinter as tk
import tkFileDialog
import datetime

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.windows = []
        menubar = tk.Menu(self)
        self.configure(menu=menubar)
        fileMenu = tk.Menu(self)
        fileMenu.add_command(label="New...", command=self.new_window)
        fileMenu.add_command(label="Save All", command=self.save_all)
        menubar.add_cascade(label="Window", menu=fileMenu)
        label = tk.Label(self, text="Select 'New' from the window menu")
        label.pack(padx=20, pady=40)

    def save_all(self):
        # ask each window object, which is acting both as 
        # the view and controller, to save it's data
        for window in self.windows:
            window.save()

    def new_window(self):
        filename = tkFileDialog.askopenfilename()
        if filename is not None:
            self.windows.append(TileWindow(self, filename))

class TileWindow(tk.Toplevel):
    def __init__(self, master, filename):
        tk.Toplevel.__init__(self, master)
        self.title("%s - Tile Editor" % filename)
        self.filename = filename
        # create an instance of a TileSet; all other
        # methods in this class can reference this
        # tile set
        self.tileset = TileSet(filename)
        label = tk.Label(self, text="My filename is %s" % filename)
        label.pack(padx=20, pady=40)
        self.status = tk.Label(self, text="", anchor="w")
        self.status.pack(side="bottom", fill="x")

    def save(self):
        # this method acts as a controller for the data,
        # allowing other objects to request that the 
        # data be saved
        now = datetime.datetime.now()
        self.status.configure(text="saved %s" % str(now))

class TileSet(object):
    def __init__(self, filename):
        self.data = "..."

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

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