【问题标题】:tkinter and GUI programming methodstkinter 和 GUI 编程方法
【发布时间】:2011-10-16 04:51:53
【问题描述】:

希望这不属于“一般性讨论主题”,因为我希望它更多地是关于以有效的方式解决这些问题,而不是关于哪种 GUI 编程的一般方法绝对是最好的巨大辩论。

所以我开始使用 tkinter 进行一些 GUI 编程,长话短说,我的代码很快就变得非常丑陋。我正在尝试为视频游戏创建一个基于图块的地图编辑器。我的主要问题似乎是:

  1. 回调无法返回值。
  2. 无法在窗口之间轻松传输数据。

我认为我将这些视为问题的原因是因为我使用函数比使用类更多。例如,我的“加载tileset”窗口完全是在功能上处理的:单击主窗口中的菜单选项调用加载新窗口的函数。在该窗口中,我在查找图像时创建了一个打开的文件对话框,并在我按下回车键时修改了显示图像的画布(以便在图像上绘制适当的网格)。函数函数函数。

对我来说,真正糟糕的做法是包含额外的参数来补偿。例如,当我创建一个tileset时,创建的TileSet类的实例应该被发送回主窗口,在那里可以显示适当的信息。我有一个加载的tileset列表作为全局变量(更糟糕的做法:处理我的根窗口的所有内容都在全局范围内!耶!),并且因为回调函数不返回值,我将该列表作为参数传递到我的“加载tileset窗口”函数,然后将参数传递给创建tileset函数(当您单击窗口中的相应按钮时调用),它实际上是需要的,以便我可以添加我的新在列表中创建了tileset。通过这样的函数“层次结构”传递参数似乎是一个可怕的想法。这会让人感到困惑,编写模块化代码很糟糕,而且通常看起来没有必要。

我解决问题的尝试是编写一个代表整个 GUI 的类,以及可以实际存储相关数据的定制窗口类(GUI 类可以创建和引用)。这应该解决在窗口之间传输数据的问题。希望它也能减少我在回调中无故使用 lambda 函数。 但我想知道:这是最好的方法吗?或者至少接近?我宁愿不开始重写,然后以另一种方式草率和混乱而结束另一个系统。我知道我的方法很糟糕,但我真的不知道最好的方法是什么。我得到了很多关于如何做具体事情的建议,但没有关于如何将程序作为一个整体来构建的建议。任何帮助将不胜感激。

【问题讨论】:

  • Button(root, text="foo", command=lambda arg="foo": callback(arg))

标签: python user-interface tkinter paradigms


【解决方案1】:

听起来您正在尝试创建一个按程序执行的 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()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 1970-01-01
    • 2021-03-04
    • 2014-02-01
    相关资源
    最近更新 更多