[英]tkinter and GUI programming methods
希望這不屬於“一般性討論主題”,因為我希望它能夠以一種有效的方式解決這些問題,而不是關於哪種GUI編程的一般方法絕對最好的大辯論。
所以我已經用tkinter開始了一些GUI編程,長話短說我的代碼很快變得非常難看。 我正在嘗試為視頻游戲創建基於圖塊的地圖編輯器。 我的主要問題似乎是:
我認為我認為這些問題的原因是因為我使用的函數比使用類要多得多。 例如,我的“加載tileset”窗口完全在功能上處理:單擊主窗口中的菜單選項調用加載新窗口的函數。 在該窗口中,我在查找圖像時創建一個打開的文件對話框,並在按下回車鍵時修改顯示圖像的畫布(以便在圖像上繪制適當的網格)。 功能功能。
對我來說真正糟糕的做法是包含額外的參數來補償。 例如,當我創建一個tileset時,創建的TileSet類的實例應該被發送回主窗口,在那里可以顯示適當的信息。 我有一個加載的tilesets列表作為一個全局變量(更糟糕的做法:處理我的根窗口的一切都在全局范圍內!yay!),並且由於回調函數不返回值,我將該列表作為參數傳遞到我的“加載tileset窗口”函數, 然后將參數傳遞給create tileset函數(當你單擊窗口中的相應按鈕時調用),它實際上需要它,以便我可以將新創建的tileset添加到列表中。 通過函數“層次結構”傳遞參數似乎是一個可怕的想法。 它變得令人困惑,編寫模塊化代碼非常糟糕,而且通常看起來沒什么必要。
我解決這個問題的嘗試是編寫一個代表整個GUI的類,以及可以實際存儲相關數據的自定義窗口類(GUI類可以創建和引用)。 這應該解決在窗口之間傳輸數據的問題。 希望它能減少我在回調中對lambda函數的無償使用。 但我想知道:這是最好的方法嗎? 或者至少接近? 我寧願不開始重寫,然后最終得到另一個系統,它只是以不同的方式草率和混亂。 我知道我的方法很糟糕,但我真的不知道最好的方法是什么。 我對如何做特定的事情有很多建議,但沒有關於如何整體構建程序的建議。 任何幫助將不勝感激。
聽起來你正在嘗試創建一個程序化的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()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.