幾天前折騰出了一個文本編輯器,爲了讓這個項目看起來更正式一點,破天荒的在GIT@OS 上build了一個 項目 ,順手起了個名字NoteSlate.以前的代碼也上傳到 這裏 裏做爲修改的原型. GIT@OS 的使用請看 這裏 .既然做爲一個正式的項目,某些該有的東西就得準備了,好比設計文檔什麼的.東西比較多也比較繁瑣,爲了閒置的幾天後重看代碼不至於想把如今的本身砍死,也爲了讓頭一次看code的人不吐槽,有個相似ReadMe的存在是必要的. python
完成後代碼以下: git
#-*-coding:utf8 -*- __version__=0.2 __author__ ="Alycat" from Tkinter import * class View(): def __init__(self): self.tk=Tk() self.createUI() self.tk.mainloop() ''' This function init the gui compentent,include the menubar,textarea and statusBar. ''' def createUI(self): #create menu menubar=Menu(self.tk) fmenu=Menu(menubar,tearoff=0) fmenu.add_command(label='Open') fmenu.add_command(label='History') fmenu.add_command(label='Save') fmenu.add_command(label='Exit') menubar.add_cascade(label="File", menu=fmenu) emenu=Menu(menubar,tearoff=0) emenu.add_command(label='Copy') emenu.add_command(label='Paste') emenu.add_command(label='Encode') emenu.add_command(label='Language') menubar.add_cascade(label="Edit", menu=emenu) rmenu=Menu(menubar,tearoff=0) rmenu.add_command(label='Search') rmenu.add_command(label='Replace') menubar.add_cascade(label="Find", menu=rmenu) self.tk.config(menu=menubar) #create text area self.text=Text() self.text.pack() #create status bar statusBar=Label(text='statusBar') statusBar.pack() if __name__ == '__main__': View()
寫到這裏,簡單粗暴的作法明顯會讓總體代碼臃腫並且難以維護,這個只有3列菜單的界面在將來須要添加功能時會再次被修改,這種直接修改源文件的作法會帶來一些沒必要要的隱患和麻煩.爲了不這些問題,咱們來換一種作法.利用配置來實現界面的具體結構.嗯嗯,XML貌似是個不錯的選擇,不過我更喜歡用JSON,更簡單粗暴. json
Json配置信息以下: 編輯器
{ "version":0.2, "author":"alycat", "name":"main", "title":"NoteSlate", "geometry":"500x400+300+200", "menu":"menu.json", "status":"status.json", "body":"txt.json", "layout":{} }
看着很簡單,即便將來菜單裏有大動做也不怕了╮(╯▽╰)╭,相應的部分代碼以下: 函數
''' This function used to load josn file. ''' def loadJson(file=None): loadFile=open(file,'r') jsonObj=json.loads(loadFile.read()) return jsonObj DEFAULT_PATH="../Config/" MENU="menu" LAYOUT="layout" ITEM="item" NAME="name" TITLE="title" TEAROFF="tearoff" INDEX="index" GEOMETRY="geometry" class View(): def __init__(self): self.tk=Tk() self.initUI() self.tk.mainloop() ''' This function init the gui compentent,include the menubar,textarea and statusBar. ''' def initUI(self): default=loadJson(DEFAULT_PATH+"default.json") self.tk.title(default[TITLE]) self.tk.geometry(default[GEOMETRY]) self.createMenu(default[MENU]) ''' Create Menu ''' def createMenu(self,menu_file=None): if menu_file is None: return -1 menu_info=loadJson(DEFAULT_PATH+menu_file) self.menu=menubar=Menu(self.tk) for menu_item in menu_info[ITEM]: menuItem=Menu(menubar,tearoff=menu_item[TEAROFF]) menubar.insert_cascade(label=menu_item[NAME],menu=menuItem,index=menu_item[INDEX]) for menu_button in menu_item[ITEM]: menuItem.insert_command(label=menu_button[NAME],index=menu_button[INDEX]) self.tk.config(menu=menubar)
界面寫到這裏,雖然不少信息都靠配置文件裏的JSON數據定義,可是我的以爲這個界面生成代碼能夠再作優化.查閱Tkinter的官方文檔和源碼時,發如今Menu對象中有index這個屬性卻沒有按index返回對象的方法,其中有個index(self,index)的函數返回的倒是一個int類型,我表示理解不了. oop
再反覆查閱了Tkinter.py的源碼後,Menu的初始化code已經完成: 佈局
def createMenu(self,menu_file=None): if menu_file is None: return -1 menu_info=loadJson(DEFAULT_PATH+menu_file) self.menu=menubar=Menu(self.tk) for menu in menu_info: if menu.has_key(CONF): menuItem=Menu(menubar,menu[CONF]) menu[VALUE][MENU]=menuItem menubar.insert(menu[INDEX], menu[TYPE],menu[VALUE]) elif menu.has_key(FATHER): menubar.children[menu[FATHER]].insert(menu[INDEX], menu[TYPE],menu[VALUE]) self.tk.config(menu=menubar)
相應的配置文件: 優化
[ { "index": 1,"type": "cascade","value": {"label": "File"},"conf": {"name": "file","tearoff": 0} }, { "index": 1,"type": "command","father": "file","value": {"label": "Open","command": ""} }, { "index": 2,"type": "command","father": "file","value": {"label": "History","command": ""} }, { "index": 3,"type": "command","father": "file","value": {"label": "Save","command": ""} }, { "index": 4,"type": "command","father": "file","value": {"label": "Exit","command": ""} }, { "index": 2,"type": "cascade","value": {"label": "Edit"},"conf": {"name": "edit","tearoff": 0} }, { "index": 1,"type": "command","father": "edit","value": {"label": "Copy","command": ""} }, { "index": 2,"type": "command","father": "edit","value": {"label": "Paste","command": ""} }, { "index": 3,"type": "command","father": "edit","value": {"label": "Encode","command": ""} }, { "index": 4,"type": "command","father": "edit","value": {"label": "Language","command": ""} }, { "index": 3,"type": "cascade","value": {"label": "Find"},"conf": {"name": "find","tearoff": 0} }, { "index": 1,"type": "command","father": "find","value": {"label": "Search","command": ""} }, { "index": 2,"type": "command","father": "find","value": {"label": "Replace","command": ""} } ]
這裏要註明的是 ui
在Menu Widget中元素是靠name字段來保證其惟一性,默認狀況下name爲當前對象的內存地址(即調用id函數),而index屬性只具備標識元素順序的功能,調用index函數並不能返回menu對象.
接下來的工做就是對每個菜單進行綁定相應的觸發條件以及響應函數. spa
針對不一樣的UI組件綁定事件,作法不少.一些常見的入門級作法是註冊一個組件就綁定一個事件.在Java Swing中能夠在事件event接口中根據不一樣組件對象去實現相應的事件內容.在Python/Tkinter中,bind和對象command屬性調用的方式反而有些許差別.
在組件對象command屬性能夠直接調用function,象這樣:
button=Button(text='Button',command=func)可是bind函數是將按鍵與響應進行 綁定,代碼以下:
def func(event): print event tk.bind('<Button-1>',func)區別在於bind須要的響應function必需要傳遞一個event對象,而command則不須要.
所以咱們作以下工做:
def handlerAdaptor(self,fun, *kwds): return lambda event,fun=fun,kwds=kwds: fun(event, *kwds) def handler(self,event,key): func=self.keyset[key] func()
讓人以爲驚奇的是bind(handlerAdaptor)這裏並無將event傳遞給handlerAdaptor,可是handler卻能夠從adaptor給出event中獲取event對象.
在結合源碼和handlerAdaptor,這個event竟然是從lambda裏蹦出來的.具體的lambda的特色,之後再表.
通過斷斷續續的大半個月龜爬後,NoteSlate的Menu部分到這裏就結束了.
源碼可見這裏.