tkinter和GUI编程方法 [英] tkinter and GUI programming methods

查看:88
本文介绍了tkinter和GUI编程方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

希望这不属于一般性讨论主题",因为我希望它更有效地解决这些问题,而不是就哪种通用的GUI编程方法绝对是最佳的争论而展开./p>

因此,我已经开始使用tkinter进行一些GUI编程,并且总的来说,我的代码很快变得非常丑陋.我正在尝试为视频游戏创建基于图块的地图编辑器.我的主要问题似乎是:

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

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

在我看来,真正的坏习惯是添加了一些额外的论据来弥补.例如,当我创建一个tileet时,应将创建的TileSet类的实例发送回主窗口,在该窗口中可以显示适当的信息.我有一个已加载的tileset列表作为全局变量(甚至更糟糕的做法:处理我的根窗口的一切都在全局范围内!是的!),因为回调函数不返回值,所以我将该列表作为参数传递到我的加载图块集窗口"功能,该功能然后将参数传递到创建图块集功能(当您单击窗口中的相应按钮时调用),实际需要在该位置上使用,以便我可以添加新的为列表创建了图块集.通过这样的函数层次结构"传递参数似乎是一个可怕的想法.令人迷惑,编写模块化代码太可怕了,而且通常似乎没有必要.

我试图解决该问题的方法是编写一个代表整个GUI的类,以及可以实际存储相关数据的定制窗口类(GUI类可以创建和引用).那应该解决在窗口之间传输数据的问题.希望它也可以减少我在回调中对lambda函数的免费使用. 但是我想知道:这是最好的方法吗?或至少接近?我宁愿不开始重写,而最终得到另一个系统,该系统以不同的方式草率且令人困惑.我知道我的方法不好,但是我真的不知道最好的方法是什么.对于如何做特定的事情,我有很多建议,但是对于如何整体构建程序却没有任何建议.任何帮助将不胜感激.

解决方案

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

Hopefully this doesn't fall under "general discussion topic", since I'd like it to be more about resolving these issues in an efficient manner than a giant debate about which general approach to GUI programming is the absolute best.

So I've started some GUI programming with tkinter and long story short my code is getting pretty ugly pretty quickly. I'm trying to create a tile-based map editor for a video game. My main issues seem to be:

  1. the inability of callbacks to return values.
  2. the inability to transfer data between windows easily.

I assume that the reason I see these as issues is because I'm using functions a lot more than I'm using classes. For instance, my "load tileset" window is handled entirely functionally: Clicking the menu option in the main window calls the function that loads the new window. From within that window, I create an open file dialog when looking for the image, and modify the canvas displaying the image when I press the enter key (so that it draws the appropriate grid over the image). function function function.

What looks like really bad practice to me is the inclusion of extra arguments to compensate. For example, when I create a tileset, the instance of the TileSet class created should be sent back to the main window where the appropriate information can be displayed. I have a list of loaded tilesets as a global variable (even more bad practice: Everything dealing with my root window is in the global scope! yay!), and because callback functions don't return values, I pass that list as an argument to my "load tileset window" function, which then passes the argument to the create tileset function (called when you click the appropriate button in the window), where it's actually needed so that I can add my newly created tileset to the list. Passing arguments through a function 'hierarchy' like that seems like a horrible idea. It gets confusing, it's horrible for writing modular code, and just generally seems unnecessary.

My attempt at fixing the problem would be to write a class representing the whole GUI, and custom made window classes (that the GUI class can create and reference) that can actually store relevant data. That should take care of issues with transferring data between windows. Hopefully it would cut down on my gratuitous use of lambda functions in callbacks as well. But I'm wondering: is this the best way? Or at least close? I'd rather not start rewriting and then end up with another system that's just sloppy and confusing in a different way. I know my methods are bad, but I don't really know what the best approach would be. I'm getting a lot of advice on how to do specific things, but none on how to structure the program as a whole. Any help would be greatly appreciated.

解决方案

It sounds like you're trying to create a GUI that acts procedurally, which won't work. GUIs aren't procedural, their code doesn't run linearly where functions call callbacks which return values. What you're asking isn't unique to tkinter. This is the nature of event based GUI programming -- callbacks can't return anything because the caller is an event rather than a function.

Roughly speaking, you must use a global object of some sort to store your data. Typically this is called the "Model". It can be a global variable, or it might be a database, or it can be an object of some sort. In any case, it must exist "globally"; that is, it must be accessible to the whole GUI.

Often, this access is provided by a third component called a "Controller". It is the interface between the GUI (the "View") and the data (the "Model"). These three components make up what is called the model-view-controller pattern, or MVC.

The model, view and controller don't have to be three different objects. Often, the GUI and the controller are the same object. For small programs this works quite well -- the GUI components talk directly to your data model.

For example, you could have a class that represents a window which inherits from Tkinter.Toplevel. It can have an attribute that represents the data being edited. When the user selects "New" from a main window, it does something like self.tileset = TileSet(filename). That is, it sets the attribute named tileset of the GUI object named self to be an instance of the TileSet class specific to the given filename. Later functions that manipulate the data use self.tileset to access the object. For functions that live outside the main window object (for example, a "save all" function from the main window) you can either pass this object as an argument, or use the window object as the controller, asking it to do something to its tileset.

Here's a brief example:

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()

这篇关于tkinter和GUI编程方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆