希望这不属于“一般性讨论主题”,因为我希望它能够以一种有效的方式解决这些问题,而不是关于哪种GUI编程的一般方法绝对最好的大讨论。 / p>
所以我用tkinter开始了一些GUI编程,长话短说我的代码很快变得非常难看。我正在尝试为视频游戏创建基于图块的地图编辑器。我的主要问题似乎是:
我认为我认为这些问题的原因是因为我使用的函数比使用类要多得多。例如,我的“加载tileset”窗口完全在功能上处理:单击主窗口中的菜单选项调用加载新窗口的函数。在该窗口中,我在查找图像时创建一个打开的文件对话框,并在按下回车键时修改显示图像的画布(以便在图像上绘制适当的网格)。功能函数功能。
对我来说真正糟糕的做法是包含额外的参数来补偿。例如,当我创建一个tileset时,创建的TileSet类的实例应该被发送回主窗口,在那里可以显示适当的信息。我有一个加载的tilesets列表作为一个全局变量(更糟糕的做法:处理我的根窗口的一切都在全局范围内!yay!),并且由于回调函数不返回值,我将该列表作为参数传递到我的“加载tileset窗口”函数,其中然后将参数传递给create tileset函数(当你单击窗口中的相应按钮时调用),它实际上需要它,以便我可以添加我的新创建了tileset到列表中。通过函数“层次结构”传递参数似乎是一个可怕的想法。它变得令人困惑,编写模块化代码非常糟糕,而且通常看起来没什么必要。
我解决问题的方法是编写一个代表整个GUI的类,以及可以实际存储相关数据的自定义窗口类(GUI类可以创建和引用)。这应该解决在窗口之间传输数据的问题。希望它能减少我在回调中对lambda函数的无偿使用。 但我想知道:这是最好的方法吗?或者至少接近?我宁愿不开始重写,然后最终得到另一个系统,它只是以不同的方式草率和混乱。我知道我的方法很糟糕,但我真的不知道最好的方法是什么。我对如何做特定的事情有很多建议,但没有关于如何整体构建程序的建议。任何帮助将不胜感激。
答案 0 :(得分:8)
听起来你正在尝试创建一个程序化的GUI,这将无法正常工作。 GUI不是程序性的,它们的代码不会在函数调用返回值的回调时线性运行。你要问的并不是tkinter独有的。这是基于事件的GUI编程的本质 - 回调不能返回任何内容,因为调用者是事件而不是函数。
粗略地说,必须使用某种全局对象来存储您的数据。通常这称为“模型”。它可以是全局变量,也可以是数据库,也可以是某种对象。无论如何,它必须“全球”存在;也就是说,整个GUI必须可以访问它。
通常,此访问由称为“Controller”的第三个组件提供。它是GUI(“视图”)和数据(“模型”)之间的接口。这三个组件组成了所谓的模型 - 视图 - 控制器模式或MVC。
模型,视图和控制器不必是三个不同的对象。通常,GUI和控制器是同一个对象。对于小程序,这非常有效 - GUI组件直接与您的数据模型对话。
例如,您可以拥有一个表示从Tkinter.Toplevel继承的窗口的类。它可以具有表示正在编辑的数据的属性。当用户从主窗口中选择“新建”时,它会执行self.tileset = TileSet(filename)
之类的操作。也就是说,它将名为tileset
的GUI对象的名为self
的属性设置为特定于给定文件名的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()