用Python指令碼語言建立一個基於應用程式的GUI快速啟動開發人員線上 Builder.com.cn 更新時間:
2007-02-27 作者:builder.com.cn
當你想學習一門新的語言時,特別是像Python這樣的指令碼語言,在你準備充分開始寫應用程式的圖形化介面之前,有時候你可能被迫使用應用程式的控制台。
從第一版商業圖形介面發行以來(感興趣的話,可以查看Xerox之星),至今經過了25年的時間,在應用程式上繼續使用控制台看上去似乎有點古老。
非常感謝Python指令碼語言強調簡潔的文法,這意味著你不需要成為Python編程高手就能在程式中使用圖形化使用者介面。為了證明這個說法,我將使用Python標準的GUI(圖形化使用者介面)工具:Tk來建立一個簡單的記錄會話程式。我不會詳細介紹Python的簡單文法,如果你有不明白的地方,請你閱讀我先前關於這個主題的文章(點擊這裡和這裡就可以查看);
讓我們從基本的開始講起,首先你需要輸入Tk介面到你的程式命名空間中。因為我們將會不斷地引用到Tk視窗小組件,我們不希望一直用一個包來限定它們,所以最好的方法就是這樣做:
from Tkinter import *
這個匯入語句與傳統的匯入語句的區別在於,它在模組中將所有的東西匯入程式預設的命名空間,而不是在你需要引用一個像Tkinter.Textbox文字框的時候,你就只能寫文字框。
現在我們來建立根視窗並設定它的標題來解釋一些東西:
root = Tk()
root.title("Note Taker")
建立根視窗就像建立一個Tk類的執行個體一樣簡單,它會裝載圖形工具包並提供給我們一個可以裝載視窗小組件的空白視窗。這是啟動一個Tk程式基本過程的第一部分。
root.mainloop()
第二部分(上面所顯示的)是調用Tk主迴圈(mainloop),這個主迴圈是用來處理事件的,比如鍵盤事件或者滑鼠輸 入,允許使用者與對話方塊交換資訊。事實上,這時候你才真正地用到了GUI程式。用那四種方式運行一個python指令碼,將彈出一個視窗,但是這個視窗僅僅是 放置在那裡,它不會做任何操作。
在你的視窗中放置視窗小組件
雖然這是非常令人煩惱的,它的介面包括以下視窗小組件:按鈕,表單等等,這些使用者能做的事。現在讓我們來建立一些按鈕、一個文字框和一個列表框:
button1 = Button(root, text="button1")
button2 = Button(root, text="button2")
button3 = Button(root, text="button3")
text = Entry(root)
listbox = Listbox(root)
建立基本對象非常簡單。每個類初始化時候的第一個參數是屬於視窗小組件表面的,既然這樣我們只能得到根視窗。按鈕的文本 屬性可以用來設定按鈕的標籤。有很多類型的視窗小組件,同樣也有很多方法可以定製這些視窗小組件,但是這個程式裡面我們需要相當多這樣的視窗小組件——一 個完整的列表,如果想查看Tkinter官方文檔的話就點擊這裡。現在我們需要做的是,用像下面這樣的包方法將這些視窗小組件刪除:
text.pack()
button1.pack()
button2.pack()
button3.pack()
listbox.pack()
在視窗中視窗小組件打包的順序就是他們在螢幕上顯示的順序。運行這個程式後你看到的視窗類別似下面這個圖片所顯示的,它取決於你的作業系統或視窗管理器的特點。
視窗小組件之間的互動
現在,這看起來更有趣,但是它一直沒有真正地做任何事情,你可以點擊按鈕或者在文字框中輸入一些資訊,但是沒有很多要 點。現在讓我們來改變這個。使用Tk和它裡面的GUI工具包,是通過回叫訊號——當某個事件出現的時候調用。讓我們寫一個簡單回叫訊號,以便在按下按鈕1 的時候提供反饋訊號:
def Button1():
listbox.insert(END, "button1 pressed")
這個函數增加“按下按鈕1”這個字串以便在列表框中結束這些項。當按鈕被按下時,我們需要以如下方式來調用這個函數改變按鈕1的建立:
button1 = Button(root, text="button1", command = Button1)
以同樣的方法為按鈕2建立一個新的回叫訊號函數,這個函數用來在按下第二個按鈕時插入不同的字串到列表框中,然後偵錯工具。能夠非常確定的是,按下按鈕將改變列表框的內容。如果我們對這些字串感到很厭煩,那麼我們想在對話方塊中添加些什麼新東西呢?
def Button3():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
然後設定按鈕3的命令達到這樣的功能。當點擊這個按鈕時,按鈕3回叫訊號首先獲得文字框的內容,並將它插到列表框的底部,再清除文字框。裝載這個程式並調試。
如果執行這個,一會兒後這個按鈕可能看上去像停止工作了,其實是列表框滿了。事實上,它工作得很好,但是我們只能在列表 中看到頂部的那組選項,但是我們能使用滑鼠滾輪滾動來看到餘下的列表。這樣有一點笨重,在這裡我們需要一個捲軸,這樣我們就能在列表中選擇我們想看到的 東西。如果我們用下面語句來代替建立列表框,增加一個捲軸只比增加其他視窗小組件中的任何一個組件複雜一點點,這是因為我們必須把它與列表框聯絡起來:
scrollbar = Scrollbar(root, orient=VERTICAL)
listbox = Listbox(root, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
然後當我們在它周圍移動捲軸時,將改變列表框Y軸的位置(垂直位置)。另外,現在如果我們使用滑鼠滾輪滾動列表框,捲軸的位置將會更新。
如果你一直跟著做,現在代碼應該看上去像下面這樣:
#!/usr/bin/python
from Tkinter import *
root = Tk()
root.title("Note Taker")
def Button1():
listbox.insert(END, "button1 pressed")
def Button2():
listbox.insert(END, "button2 pressed")
def Button3():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
button1 = Button(root, text="button1", command = Button1)
button2 = Button(root, text="button2", command = Button2)
button3 = Button(root, text="button3", command = Button3)
text = Entry(root)
scrollbar = Scrollbar(root, orient=VERTICAL)
listbox = Listbox(root, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
text.pack()
button1.pack()
button2.pack()
button3.pack()
listbox.pack()
scrollbar.pack()
root.mainloop()
安置視窗小組件
我們開始來部署這些地方,可以將我們的文本資訊輸入到文字框中,然後把它們增加到一個列表中。雖然這個程式有點不太好看,每個東西都是垂直排放的,文字框和列表框太小而使用起來不太方便。我們需要對視窗小組件放置的地方更多點控制。
有一些方法可以用來做到這點,但是我們實際會將視窗分成兩部分,一部分是文字框和按鈕,另一部分控制列表框。我們將使用架構視窗小組件來實現這個功能,架構視窗小組件看上去像個容器,可以將其它的視窗小組件放在它的裡面就像根視窗一樣。現在讓我們來建立一對架構
textframe = Frame(root)
listframe = Frame(root)
到目前為止我們已經將每個視窗小組件的所有者都設定為根視窗,接下來,我們改變它的所有者並應用到我們的新設計中:
button1 = Button(textframe, text="button1", command = Button1)
button2 = Button(textframe, text="button2", command = Button2)
button3 = Button(textframe, text="button3", command = Button3)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set)
scrollbar.configure(command=listbox.yview)
其實,這樣不會起太多作用,除非我們告訴Tk視窗小組件在架構中放置的位置,為實現這個我們可以使用包方法和三個關鍵 字:side、fill和expand。在文文書處理器中你能把邊認為與對準線相似,它告訴視窗小組件架構它們一般需要設定的哪一邊,是頂部,底部,左邊還 是右邊——預設的形式是群圍繞著中心這種方式。在這種情況下,除了用作左邊對準器的捲軸外,我們需要將其它的每件東西用捲軸包圍列表框的右邊。
第二個選項,fill,如果它增長了就告訴Tk哪個方向溢出了小視窗組件,在X軸方向(水平方向),Y軸方向(垂直方 向)或者兩個方向都有。一般來說,我們能很好處理這些按鈕,但是它希望文字框和列表框能使用盡量多的空間——所以設定文字框在X軸方向溢出和列表框在兩個 方向溢出——捲軸應該重新設定列表框的大小,所以設定它在Y軸方向溢出。
最後,expand選項告訴視窗小組件如果可能是否要擴張到免費的空間——增加expand=1這條語句到文字框和列表框包選項中就能實現這個效果。現在,你的代碼應該看上去像下面這樣:
text.pack(side=LEFT, fill=X, expand=1)
button1.pack(side=LEFT)
button2.pack(side=LEFT)
button3.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
目前,所有的左邊按鈕像打包到其他的視窗小組件一樣,打包到兩個架構中並放置到根視窗裡面。記住,這種方式架構應該要擴張:
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
最後,我們應該設定視窗的預設大小,以便使我們的視窗小組件的視窗能有更多點的空間。
root.geometry("600x400")
經過這些步驟後,我們的視窗現在應該看上去像下面這樣:
鍵盤事件和滑鼠事件
我們有一些工作,或多或少的——但是它的介面一直有點不實用。如果我們能使用滑鼠和鍵盤使事情更直接的話,這些工作看上 去將會更容易處理。當你們運行Tk主迴圈,在鍵盤上敲擊一個鍵或者四處移動滑鼠產生事件,你可以以同樣的方法綁定回叫訊號函數到按鈕上,這樣按鈕被按下去 的時候也會產生相應事件。現在我們已經有個函數處理這樣的情況了,那就是按鈕3的回叫訊號,但是不幸地是,我們現在還不能使用,因為按鈕的事件回叫訊號與 滑鼠和鍵盤不一樣。我們必須把它限制在其它的函數中:
def ReturnInsert(event):
Button3()
然後我們註冊這些事件的回叫訊號,並尋找使用綁定函數:
text.bind("<Return>", ReturnInsert)
這裡我們使用<Return>事件代碼而不使用<Enter>事件代碼,這點是非常重要的,因 為當滑鼠進入列表框時就會觸發第二個觸發器。現在我們想讓使用者使用右擊來從列表框中移動項目,對於相同的處理這種處理是相當棒的。首先我們寫一個回叫訊號 作為輸入來接收事件:
def DeleteCurrent(event):
listbox.delete(ANCHOR)
然後我們綁定事件到這個回叫訊號上:
listbox.bind("<Double-Button-3>", DeleteCurrent)
滑鼠右鍵在Tk中被稱為按鈕-3(不要與我們第三個形式按鈕回叫訊號的名字混淆了),因為第二個滑鼠按鍵涉及到滑鼠的中 間鍵。最後,以防萬一他們想修改它,我們可以允許使用者拷貝一個易事貼返回到文字框。我們沒有一個函數能實現這個功能,所以我們將不得不在回叫訊號中寫一些 新的代碼:
def CopyToText(event):
text.delete(0,END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
然後像先前那樣將事件綁定到回叫訊號中:
listbox.bind("<Double-Button-1>", CopyToText)
你不僅僅可以限制這些事件,如果你想知道更多的關於哪些事件你能綁定的話,請查看Tk內部庫介紹。
現在看上去是整理我們程式的時候了。按鈕1對於一個函數來說是一個不太好的名字——曾經有相似的名字使我們陷入困境,在 這裡我們就不要重蹈覆轍了。我們也有機會改變一些按鈕作用,比如,我們可以設定輸入按鈕來關閉文字框,按鈕1沒有特別的作用,所以我們可以除去它了。修改 這些並沒有真正地功能性改變,但是程式現在看上去像這樣:
#!/usr/bin/python
from Tkinter import *
root = Tk()
root.geometry("600x400")
root.title("Note Taker")
def Enter():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
def Remove():
listbox.delete(ANCHOR)
def Save():
pass
def ReturnInsert(event):
Enter()
def DeleteCurrent(event):
Remove()
def CopyToText(event):
text.delete(0, END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
textframe = Frame(root)
listframe = Frame(root)
enter_button = Button(textframe, text="Enter", command = Enter)
remove_button = Button(textframe, text="Remove", command = Remove)
save_button = Button(textframe, text="Save", command = Save)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set, selectmode=EXTENDED)
scrollbar.configure(command=listbox.yview)
text.bind("<Return>", ReturnInsert)
listbox.bind("<Double-Button-3>", DeleteCurrent)
listbox.bind("<Double-Button-1>", CopyToText)
text.pack(side=LEFT, fill=X, expand=1)
enter_button.pack(side=LEFT)
remove_button.pack(side=LEFT)
save_button.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
root.mainloop()
儲存會話間的資料
做了這些以後,我們的程式就可以用了,但是當我們關閉程式後,所以的易事貼都會消失——我們可以通過儲存列表的內容到一 個檔案中,同時在程式啟動並執行時候下載它來安裝這個程式。為實現這個我們將使用pickle模組——Python的系列版本,或者使用集結待發的資料類型到 檔案中。首先我們需要輸入模組:
import pickle
然後我們得到一個變數,易事貼,這個變數包含了易事貼列表。為了將它儲存到一個檔案中,我們僅僅需要開啟檔案寫入,並使用dump函數。你可能會注意到第三個按鈕的回叫訊號是空的,讓我們來改變它以便將當前的易事貼列表儲存到檔案中:
def Save():
f = file("notes.db", "wb")
notes = listbox.get(0, END)
pickle.dump(notes, f)
現在,當按下“儲存”按鈕,我們可以在案頭上得到我們的易事貼的副本。這個不會給我們太多的協助,除非我們有一些下載這些易事貼的方法,所以讓我們在啟動主迴圈前正確地把列表框填滿:
try:
f = file("notes.db", "rb")
notes = pickle.load(f)
for item in notes:
listbox.insert(END,item)
f.close()
except:
pass
我們需要用一個try語句將所有的東西捆綁起來,以防檔案開啟拋出一個異常,也就是說,如果檔案“notes.db”不存在的話,應該拋出一個異常,產生警告。然而,如果一個異常被拋出,我們不會真正的注意到它,我們只是僅僅不會下載任何易事貼到列表中而已。
這樣,你有了一個用Python和Tk寫的,不超過70行代碼的,可啟動並執行迷你易事貼程式——用鍵盤和滑鼠輸入來完成, 並且可以下載和儲存資料。使用這個,你能看到建立一個基於應用程式的快速GUI是多麼簡單的事情,但是這個程式只有少許的改進,比如,多線程文本地區,或 者複製一個易事貼到剪貼版中,自動儲存易事貼,等等,它對於儲存你的蹤跡到列表中來說,是一個恰當地唾手可得的方法。我將把這個作為一個練習留下來。如果 你想找到更多的關於你能用Python在Tk做些什麼的文擋,最好的地方是查看這裡的庫檔案。如果你一直跟著做下來的話,程式應該看上去像下面這樣:
下面是這個例子的代碼:
#!/usr/bin/python
from Tkinter import *
import pickle
root = Tk()
root.geometry("600x400")
root.title("Note Taker")
def Enter():
text_contents = text.get()
listbox.insert(END, text_contents)
text.delete(0,END)
def Remove():
listbox.delete(ANCHOR)
def Save():
f = file("notes.db", "wb")
notes = listbox.get(0, END)
pickle.dump(notes, f)
def ReturnInsert(event):
Enter()
def DeleteCurrent(event):
Remove()
def CopyToText(event):
text.delete(0, END)
current_note = listbox.get(ANCHOR)
text.insert(0, current_note)
textframe = Frame(root)
listframe = Frame(root)
enter_button = Button(textframe, text="Enter", command = Enter)
remove_button = Button(textframe, text="Remove", command = Remove)
save_button = Button(textframe, text="Save", command = Save)
text = Entry(textframe)
scrollbar = Scrollbar(listframe, orient=VERTICAL)
listbox = Listbox(listframe, yscrollcommand=scrollbar.set, selectmode=EXTENDED)
scrollbar.configure(command=listbox.yview)
text.bind("<Return>", ReturnInsert)
listbox.bind("<Double-Button-3>", DeleteCurrent)
listbox.bind("<Double-Button-1>", CopyToText)
text.pack(side=LEFT, fill=X, expand=1)
enter_button.pack(side=LEFT)
remove_button.pack(side=LEFT)
save_button.pack(side=LEFT)
listbox.pack(side=LEFT,fill=BOTH, expand=1)
scrollbar.pack(side=RIGHT, fill=Y)
textframe.pack(fill=X)
listframe.pack(fill=BOTH, expand=1)
try:
f = file("notes.db", "rb")
notes = pickle.load(f)
for item in notes:
listbox.insert(END,item)
f.close()
except:
pass
root.mainloop()