生命不息折騰不止 ---- 開發編輯器第二話

    幾天前折騰出了一個文本編輯器,爲了讓這個項目看起來更正式一點,破天荒的在GIT@OS 上build了一個 項目 ,順手起了個名字NoteSlate.以前的代碼也上傳到 這裏 裏做爲修改的原型. GIT@OS 的使用請看 這裏 .既然做爲一個正式的項目,某些該有的東西就得準備了,好比設計文檔什麼的.東西比較多也比較繁瑣,爲了閒置的幾天後重看代碼不至於想把如今的本身砍死,也爲了讓頭一次看code的人不吐槽,有個相似ReadMe的存在是必要的. python

    First of all,全部的代碼和步驟都是和blog是同時進行的,我也是現學現用,目前不包含任何其餘時尚的技術,將來若是會涉及到那我也是0基礎,若是錯誤歡迎指正.我會更多介紹如何去查找問題的緣由以及解決的方案,授人以漁比授人以魚要好的多.
在完成一個簡單的Design以後就能夠考慮coding了.首先建議的是畫UI(這事很糟心,由於我畫的很爛).基本佈局不變,頂部是menu,中間是text area,在底部如今多了一個status bar.

完成後代碼以下: 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部分到這裏就結束了.

源碼可見這裏.

相關文章
相關標籤/搜索