2011-10-16 47 views
3

希望這不屬於「一般性討論主題」,因爲我希望它更有效地解決這些問題,而不是一場關於GUI編程的一般方法的巨大爭論絕對最好。tkinter和GUI編程方法

所以我已經開始一些GUI編程tkinter和長話短說我的代碼變得非常快速相當醜陋。我正在嘗試爲視頻遊戲創建基於圖塊的地圖編輯器。我的主要問題似乎是:

  1. 回調無法返回值。
  2. 無法輕鬆在窗口之間傳輸數據。

我認爲我看到這些問題的原因是因爲我使用的功能比我使用的類多得多。例如,我的「加載圖塊集」窗口在功能上完全處理:單擊主窗口中的菜單選項調用加載新窗口的函數。在該窗口內,我在查找圖像時創建了一個打開的文件對話框,並在按下回車鍵(以便在圖像上繪製適當的網格)時修改顯示圖像的畫布。功能函數功能。

對我來說,看起來非常糟糕的做法是包含額外的參數來補償。例如,當我創建一個tileset時,創建的TileSet類的實例應該被髮回到主窗口,在那裏可以顯示適當的信息。我有一個加載的tilesets作爲一個全局變量的列表(甚至更糟糕的做法:處理我的根窗口的一切都在全局範圍!yay!),並且因爲回調函數不返回值,所以我將該列表作爲參數傳遞到我的「加載圖塊集窗口」函數,其中然後將參數傳遞給創建圖塊集函數(在您單擊窗口中的相應按鈕時調用),實際需要它的位置以便我可以將新創建​​的圖塊集添加到列表。通過像這樣的函數「層次結構」傳遞參數似乎是一個可怕的想法。它變得令人困惑,編寫模塊化代碼非常可怕,而且通常看起來沒有必要。

我嘗試解決問題的方法是編寫一個代表整個GUI的類,以及可以實際存儲相關數據的自定義窗口類(GUI類可以創建和引用)。這應該關注在Windows之間傳輸數據的問題。希望它能減少我在回調中無用的lambda函數。 但我想知道:這是最好的方式嗎?或者至少關閉?我寧願不開始重寫,然後結束另一個系統,只是以不同的方式混淆和混淆。我知道我的方法很糟糕,但我不知道最好的方法是什麼。關於如何做具體的事情,我得到了很多建議,但沒有關於如何構建整個程序的建議。任何幫助將不勝感激。

+1

'按鈕(根,文本= 「foo」 的,命令=拉姆達ARG = 「foo」 的:回調(ARG))' –

回答

8

聽起來好像您正在嘗試創建一個可以程序化操作的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()