本文內容全部出自《Python基礎教程》第二版,在此分享自己的學習之路。 lxx___歡迎轉載:http://www.cnblogs.com/Marlowes/p/5520948.htmllxx___ Created on Marlowes 本章將會介紹如何創建Python程式的圖形用戶界面(GUI ...
本文內容全部出自《Python基礎教程》第二版,在此分享自己的學習之路。
______歡迎轉載:http://www.cnblogs.com/Marlowes/p/5520948.html______
Created on Marlowes
本章將會介紹如何創建Python程式的圖形用戶界面(GUI),也就是那些帶有按鈕和文本框的視窗等。很酷吧?
目前支持Python的所謂“GUI工具包”(GUI Toolkit)有很多,但是沒有一個被認為是標註你的GUI工具包。這樣的情況也好(自由選擇的空間較大)也不好(其他人沒法用程式,除非他們也安裝了相同的GUI工具包),幸好Python的GUI工具包之間沒有衝突,想裝多少個就可以裝多少個。
本章簡要介紹最成熟的跨平臺Python GUI工具包——wxPython。有關更多wxPython程式的介紹,請參考官方文檔(http://wxpython.org)。關於GUI程式設計的更多信息請參見第二十八章。
12.1 豐富的平臺
在編寫Python GUI程式前,需要決定使用哪個GUI平臺。簡單來說,平臺是圖形組件的一個特定集合,可以通過叫做GUI工具包的給定Python模塊進行訪問。Python可用的工具包很多。其中一些最流行的如表12-1所示。要獲取更加詳細的列表,可以在Vaults of Parnassus(http://py.vaults.ca/)上面以關鍵字"GUI"進行搜索。也可以在Python Wiki(http://wiki.python.org/moin/GuiProgramming)上找到完全的工具列表。Guiherme Polo也撰寫過一篇有關4個主要平臺對比的論文("PyGTK,PyQt,Tkinter and wxPython comparison"(PyGTK、PyQt、Tkinter和wxPython的比較),The Python Papers, 捲3,第1期26~37頁。這篇文章可以從http://pythonpapers.org上獲得)。
表12-1 一些支持Python的流行GUI工具包
Tkinter 使用Tk平臺,很容易得到,半標準。 http://wiki.python.org/moin/TkInter
wxpython 基於wxWindows,跨平臺越來越流行。 http://wxpython.org
PythonWin 只能在Windows上使用,使用了本機的Windows GUI功能 http://starship.python.net/crew/mhammond
Java Swing 只能用於Jython,使用本機的Java GUI。 http://java.sun.com/docs/books/tutorial/uiswing
PyGTK 使用GTK平臺,在Linux上很流行。 http://pygtk.org
PyQt 使用Qt平臺,跨平臺。 http://wiki.python.org/moin/PyQt
可選的包太多了,那麼應該用哪個呢?儘管每個工具包都有利弊,但很大程度上取決於個人喜好。Tkinter實際上類似於標準,因為它被用於大多數“正式的”Python GUI程式,而且它是Windows二進位發佈版的一部分。但是在UNIX上要自己編譯安裝。Tkinter和Swing Jython將在12.4節進行介紹。
另外一個越來越受歡迎的工具是wxPython。這是個成熟而且特性豐富的包,也是Python之父Guido van Rossum的最愛。在本章的例子中,我們將使用wxPython。
關於Pythonwin、PyGTK和PyQt的更多信息,請查看這些項目的主頁(見表12-1)。
12.2 下載和安裝wxPython
要下載wxPython,只要訪問它的下載頁面http://wxpython.org/download.php即可。這個網頁提供了關於下載哪個版本的詳細指導,還有使用不同版本的先決條件。
如果使用Windows系統,應該下載預建的二進位版本。可以選擇支持Unicode或不支持Unicode的版本,除非要用到Unicode,否則選擇哪個版本區別並不大。確保所選擇的二進位版本要對應Python的版本。例如,針對Python2.3進行編譯的wxPython並不能用於Python2.4。
對於Mac OS X來說,也應該選擇對應Python版本的wxPython。可能還需要考慮操作系統版本。同樣,你也可以選擇支持Unicode和不支持Unicode的版本。下載鏈接和相關的解釋能非常明確地告訴你應該下載哪個版本。
如果讀者正在使用Linux,那麼可以查看包管理器中是否包括wxPython,它存在於很多主流發佈版本中。對於不同版本的Linux來說也有不同的RPM包。如果運行包含RPM的Linux發行版,那麼至少應該下載wxPython和運行時包(runtime package),而不需要devel包。再說一次,要選擇與Python以及Linux發佈版對應的版本。
如果沒有任何版本適合硬體或操作系統(或者Python版本),可以下載源代碼發佈版。為了編譯可能還需要根據各種先決條件下載其他的源代碼包,這已經超出了本章的範圍。這些內容在wxPython的下載頁面上都有詳細的解釋。
在下載了wxPython之後,強烈建議下載演示版本(demo,它必須進行獨立安裝),其中包含文檔、示常式序和非常詳細的(而且有用的)演示分佈。這個演示程式演示了大多數wxPython的特性,並且還能以對用戶非常友好的方式查看各部分源代碼——如果想要自學wxPython的話非常值得一看。
安裝過程應該很簡單,而且是自動完成的。安裝Windows二進位版本只要運行下載完的可執行文件(.exe文件);在OS X系統中,下載後的文件應該看起來像是可以打開的CD-ROM一樣,並帶有一個可以雙擊的.pkg文件。要使用RPM安裝,請參見RPM文檔。Windows和Mac OS X版本都會運行一個安裝嚮導,用起來很簡單。只要選擇預設設置即可,然後一直惦記Continue,最後點擊Finish即可。
12.3 創建示例GUI應用程式
為使用wxPython進行演示,首先來看一下如何創建一個簡單的示例GUI應用程式。你的任務是編寫一個能編輯文本文件的基礎程式。編寫全功能的文本編輯器已經超出了本章的範圍——關註的只是基礎。畢竟目標是演示在Python中進行GUI編程的基本原理。
對這個小型文本編輯器的功能要求如下:
☑ 它應允許打開給定文件名的文本文件;
☑ 它應允許編輯文本文件;
☑ 它應允許保存文本文件;
☑ 它應允許退出程式。
當編寫GUI程式時,畫個界面草圖總是有點用的。圖12-1展示了一個滿足我們文本編輯要求的佈局:
圖12-1 文本編輯器草圖
界面元素可以像下麵這樣使用。
☑ 在按鈕左側的文本框內輸入文件名,點擊Open打開文件。文件中包含的文本會顯示在下方的文本框內。
☑ 可以在這個大的文本框中隨心所欲地編輯文本。
☑ 如果希望保存修改,那麼點擊Save按鈕,會再次用到包含文件名的文本框——然後將文本框的內容寫入文件。
☑ 沒有Quit(退出)按鈕——如果用戶關閉視窗,程式就會退出。
使用某些語言寫這樣的程式時相當難的。但是利用Python和恰當的GUI工具包,簡直是小菜一碟(雖然現在讀者可能不同意這種說法,但在學習完本章之後應該就會同意了)。
12.3.1 開始
為了查看wxPython是否能工作,可以嘗試運行wxPython的演示版本(要單獨安裝)。Windows內應該可以在開始菜單找到,而OS X可以直接把wxPython Demo文件拖到應用程式中,然後運行。看夠了演示就可以開始寫自己的程式了,當然,這會更有趣的。
開始需要導入wx模塊:
import wx
編寫wxPython程式的方法很多,但不可避免的事情是創建應用程式對象。基本的應用程式類叫做ex.App,它負責幕後所有的初始化。最簡單的wxPython程式應該像下麵這樣:
import wx app = wx.App() app.MainLoop()
註:如果wx.App無法工作,可能需要將它替換為wx.PySimpleApp。
因為沒有任何用戶可以交互的視窗,程式會立刻退出。
例中可以看到,wx包中的方法都是以大寫字母開頭的,而這和Python的習慣是相反的。這樣做的原因是這些方法名和基礎的C++包wxWidgets中的方法名都是對應的。儘管沒有正式的規則反對方法或者函數名以大寫字母開頭,但是規範的做法是為類保留這樣的名字。
12.3.2 視窗和組件
視窗(Windows)也成為框架(frame),它只是wx.Frame類的實例。wx框架中的部件都是由它們的父部件作為構造函數的第一個參數創建的。如果正在創建一個單獨的視窗,就不需要考慮父部件,使用None即可,如代碼清單12-1所示。而且在調用app.MainLoop前需要調用視窗的Show方法——否則它會一直隱藏(可以在事例處理程式中調用win.Show,後面會介紹)。
# 代碼清單12-1 創建並且顯示一個框架 import wx app = wx.App() win = wx.Frame(None) win.Show() app.MainLoop()
如果運行這個程式,應該能看到一個視窗出現,類似於圖12-2。
圖12-2 只有一個視窗的GUI程式
在框架上增加按鈕也很簡單——只要使用win作為符參數實例化wx.Button即可,如代碼清單12-2所示。
# 代碼清單12-2 在框架上增加按鈕 import wx app = wx.App() win = wx.Frame(None) btn = wx.Button(win) win.Show() app.MainLoop()
這樣會得到一個帶有一個按鈕的視窗,如圖12-3所示。
圖12-3 增加按鈕後的程式
當然,這裡做的還不夠,視窗沒有標題,按鈕沒有標簽,而且也不希望讓按鈕覆蓋整個視窗。
12.3.3 標簽、標題和位置
可以在創建部件的時候使用構造函數的label參數設定它們的標簽。同樣,也可以用title參數設定框架的標題。我發現最實用的做法是為wx構造函數使用關鍵字參數,所以我不用記住參數的順序。代碼清單12-3演示了一個例子。
# 代碼清單12-3 使用關鍵字參數增加標簽和標題 import wx app = wx.App()
win = wx.Frame(None, title="Simple Editor") loadButton = wx.Button(win, label="Open") saveButton = wx.Button(win, label="Save")
win.Show() app.MainLoop()
程式的運行結果如圖12-4所示。
這個版本的程式還是有些不對——好像丟了一個按鈕!實際上它沒丟——只是隱藏了。註意一下按鈕的佈局就能將隱藏的按鈕顯示出來。一個很基礎(但是不實用)的方法是使用pos和size參數在構造函數內設置位置和尺寸,如代碼清單12-4所示。
12-4 有佈局問題的視窗
# 代碼清單12-4 設置按鈕位置 import wx app = wx.App() win = wx.Frame(None, title="Simple Editor", size=(410, 335)) win.Show() loadButton = wx.Button(win, label="Open", pos=(225, 5), size=(80, 25)) saveButton = wx.Button(win, label="Save", pos=(315, 5), size=(80, 25)) filename = wx.TextCtrl(win, pos=(5, 5), size=(210, 25)) contents = wx.TextCtrl(win, pos=(5, 35), size=(390, 260), style=wx.TE_MULTILINE | wx.HSCROLL) app.MainLoop()
你看到了,位置和尺寸都包括一對數值:位置包括x和y坐標,而尺寸包括寬和高。
這段代碼中還有一些新東西:我創建了兩個文本控制項(text control,wx.TextCtrl對象),每個都使用了自定義風格。預設的文本控制項是文本框(text field),就是一行可編輯的文本,沒有滾動條,為了創建文本區(text area)只要使用style參數調整風格即可。style參數的值實際上是個整數,但不用直接指定,可以使用按位或運算符OR(或管道運算符)聯合wx模塊中具有特殊名字的風格來指定。本例中,我聯合了wx.TE_MULTILINE來獲取多行文本區(預設有垂直滾動條)以及wx.HSCROLL來獲取水平滾動條。程式運行的結果如圖12-5所示。
圖12-5 位置合適的組件
12.3.4 更智能的佈局
儘管明確每個組件的幾何位置很容易理解,但是過程很乏味。在繪圖紙上畫出來可能有助於確定坐標,但是用數字來調整位置的方法有很多嚴重的缺點。如果運行程式並且試圖調整視窗大小,那麼會註意到組件的幾何位置不變。雖然不是什麼大事,但是看起來還是有些奇怪。在調整視窗大小時,應該能保證視窗中的組件也會隨之調整大小和位置。
考慮一下我是如何佈局的,那麼出現這種情況就不會令人驚訝了。每個組件的位置和大小都顯式設定的,但是沒有明確在視窗大小變化的時候它們的行為是什麼。指定行為的方法有很多,在wx內進行佈局的最簡單方法是使用尺寸器(sizer),最容易使用的工具就是wx.BoxSizer。
尺寸器會管理組件的尺寸。只要將部件添加到尺寸器上,再加上一些佈局參數,然後讓尺寸器自己去管理父組件的尺寸。在剛纔的例子中,需要增加背景組件(wx.Panel),創建一些嵌套的wx.BoxSizer,然後使用面板的SetSizer方法設定它的尺寸器,如代碼清單12-5所示。
import wx app = wx.App() win = wx.Frame(None, title="Simple Editor", size=(410, 335)) bkg = wx.Panel(win) loadButton = wx.Button(bkg, label="open") saveButton = wx.Button(bkg, label="Save") filename = wx.TextCtrl(bkg) contents = wx.TextCtrl(bkg, style=wx.TE_MULTILINE | wx.HSCROLL) hbox = wx.BoxSizer() hbox.Add(filename, proportion=1, flag=wx.EXPAND) hbox.Add(loadButton, proportion=0, flag=wx.LEFT, border=5) hbox.Add(saveButton, proportion=0, flag=wx.LEFT, border=5) vbox = wx.BoxSizer(wx.VERTICAL) vbox.Add(hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) vbox.Add(contents, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5) bkg.SetSizer(vbox) win.Show() app.MainLoop()
這段代碼的運行結果和前例相同,但是使用了相對坐標而不是絕對坐標。
wx.BoxSizer的構造函數帶有一個決定它是水平還是垂直的參數(wx.HORIZONTAL或者wx.VERTICAL),預設為水平。Add方法有幾個參數,proportion參數根據在視窗改變大小時所分配的空間設置比例。例如,水平的BoxSizer(第一個)中,filename組件在改變大小時獲取了全部的額外空間。如果這3個部件都把proportion設為1,那麼都會獲得相等的空間。可以將proportion設定為任何數。
flag參數類似於構造函數中的style參數,可以使用按位或運算符連接構造符號常量(symbolic constant,即有特殊名字的整數)對其進行構造。wx.EXPAND標記確保組件會擴展到所分配的空間中。而wx.LEFT、wx.RIGHT、wx.TOP、wx.BOTTOM和wx.ALL標記決定邊框參數應用於哪個邊,邊框參數用於設置邊緣寬度(間隔)。
就是這樣。我得到了我要的佈局。但是遺漏了一件至關重要的事情——按下按鈕,卻什麼都沒發生。
註:更多有關尺寸器的信息或者與wxPython相關的信息請參見wxPython的演示版本,它裡面會有你想要瞭解的內容和示例代碼。如果看起來比較難,可以訪問wxPython的網站http://wxpython.org。
12.3.5 事件處理
在GUI術語中,用戶執行的動作(比如點擊按鈕)叫做事件(event)。你需要讓程式註意這些事件並且做出反應。可以將函數綁定到所涉及的時間可能發生的組件上達到這個效果。當事件發生時,函數會被調用。利用部件的Bind方法可以將時間處理函數鏈接到給定的事件上。
假設寫了一個負責打開文件的函數,並將其命名為load。然後就可以像下麵這樣將該函數作為loadButton的事件處理函數:
loadButton.Bind(wx.EVT_BUTTON, load)
很直觀,不是嗎?我把函數鏈接到了按鈕——點擊按鈕的時候,函數被調用。名為wx.EVT_BUTTON的符號常量表示一個按鈕事件。wx框架對於各種事件都有這樣的事件常量——從滑鼠動作到鍵盤按鍵,等等。
為什麼用LOAD?
註:我之所以用loadButton和load作為按鈕以及處理函數的名字不是偶然的——儘管按鈕的文本為"Open"。這是因為如果我把按鈕叫做openButton的話,open也就自然成了事件處理函數的名字,這樣就和內建的文件打開函數open衝突,導致後者失效。雖然有很多種方法可以解決這個問題,但我覺得使用不同的名字是最簡單的。
12.3.6 完成了的程式
讓我們來完成剩下的工作。現在需要的就是兩個事件處理函數:load和save。當事件處理函數被調用時,它會收到一個事件對象作為它唯一的參數,其中包括發生了什麼事情的信息,但是在這裡可以忽略這方面的事情,因為程式只關心點擊時發生的事情。
def load(event): file = open(filename.GetValue()) contents.SetValue(file.read()) file.close()
讀過第十一章後,讀者應該對於文件打開/讀取的部分比較熟悉了。文件名使用filename對象的GetValue方法獲取(filename是小的文本框)。同樣,為了將文本引入文本區,只要使用contents.SetValue即可。
save函數也很簡單:幾乎和load一樣——除了它有個"w"標誌,以及用於文件處理部分的write方法。GetValue用於從文本區獲得信息。
def save(event): file = open(filename.GetValue(), "w") file.write(contents.GetValue()) file.close()
就是這樣了。現在我將這些函數綁定到相應的按鈕上,程式已經可以運行了。最終的程式如代碼清單12-6所示。
#!/usr/bin/env python # coding=utf-8 import wx def load(event): file = open(filename.GetValue()) contents.SetValue(file.read()) file.close() def save(event): file = open(filename.GetValue(), "w") file.write(contents.GetValue()) file.close() app = wx.App() win = wx.Frame(None, title="Simple Editor", size=(410, 335)) bkg = wx.Panel(win) loadButton = wx.Button(bkg, label="open") loadButton.Bind(wx.EVT_BUTTON, load) saveButton = wx.Button(bkg, label="Save") saveButton.Bind(wx.EVT_BUTTON, save) filename = wx.TextCtrl(bkg) contents = wx.TextCtrl(bkg, style=wx.TE_MULTILINE | wx.HSCROLL) hbox = wx.BoxSizer() hbox.Add(filename, proportion=1, flag=wx.EXPAND) hbox.Add(loadButton, proportion=0, flag=wx.LEFT, border=5) hbox.Add(saveButton, proportion=0, flag=wx.LEFT, border=5) vbox = wx.BoxSizer(wx.VERTICAL) vbox.Add(hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) vbox.Add(contents, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5) bkg.SetSizer(vbox) win.Show() app.MainLoop()GUI.py
可以按照下麵的步驟使用這個編輯器。
(1) 運行程式。應該看到一個和剛纔差不多的視窗。
(2) 在文本區裡面打些字(比如"Hello, world!")
(3) 在文本框中鍵入文件名(比如hello.txt)。確保文件不存在,否則它會被覆蓋。
(4) 點擊Save按鈕。
(5) 關閉編輯視窗(只為了好玩)。
(6) 重啟程式。
(7) 在文本框內鍵入同樣的文件名。
(8) 點擊Open按鈕。文件的文本內容應該會在大文本區內重現。
(9) 隨便編輯一下文件,再次保存。
現在可以打開、編輯和保存文件,直到感到煩為止——然後應該考慮一下改進。例如使用urllib模塊讓程式下載文件怎麼樣?
讀者可能會考慮在程式中使用更加面向對象的設計。例如,可能希望將主應用程式作為自定義應用程式類(可能是wx.App的子類)的一個實例進行管理,而不是將整個結構置於程式的最頂層。也可以創建一個單獨的視窗類(wx.Frame的子類)。請參見第28章獲取更多示例。
PYW怎麼樣
在Windows中,你可以使用.pyw作為文件名的結尾來保存GUI程式。在第一章中,我告訴給你的文件名使用這個結尾然後雙擊它(Windows中),什麼都沒發生,之後我保證我會在後面解釋。在第十章中,我再次提到了這個是,並且說本章內會解釋,所以現在就說說吧。
其實沒什麼大不了的。在Windows中雙擊普通的Python腳本時,會出現一個帶有Python提示符的DOS視窗,如果使用print和raw_input作為基礎界面,那麼就沒問題。但是現在已經知道如何創建GUI程式了,DOS視窗就顯得有些多餘,pyw視窗背後的真相就是它可以在沒有DOS視窗的情況下運行Python——對於GUI程式就完美了。
12.4 但是我寧願用······
Python的GUI工具包是在太多,所以我沒辦法將所有工具包都展示給你看。不過我可以給出一些流行的GUI包中的例子(比如Tkinter和Jython/Swing)。
為了演示不同的包,我創建了一個簡單的程式——很簡單,比剛纔的編輯器例子還簡單。只有一個視窗,該視窗包含一個帶有"Hello"標簽的按鈕。當點擊按鈕時,它會列印出文本"Hello, world!",為了簡單,我沒有使用任何特殊的佈局特性。下麵是一個wxPython版本的示例。
import wx def hello(event): print "Hello, world!" app = wx.App() win = wx.Frame(None, title="Hello, wxPython!", size=(200, 100)) button = wx.Button(win, label="Hello") button.Bind(wx.EVT_BUTTON, hello) win.Show() app.MainLoop()
最終的結果如圖12-6所示。
圖12-6 簡單的GUI示例
12.4.1 使用Tkinter
Tkinter是個老牌的Python GUI程式。它由Tk GUI工具包(用於Tcl編程語言)包裝而來。預設在Windows版和Mac OS發佈版中已經包括。下麵的網址可能有用:
☑ http://www.ibm.com/developerworks/linux/library/l-tkprg
☑ http://www.nmt.edu/tcc/help/lang/python/tkinter.pdf
下麵是使用Tkinter實現的GUI程式。
from Tkinter import * def hello(): print "Hello, world!" win = Tk() # Tkinter的主視窗 win.title("Hello, Tkinter!") win.geometry("200x100") # Size 200, 100 btn = Button(win, text="Hello", command=hello) btn.pack(expand=YES, fill=BOTH) mainloop()
12.4.2 使用Jython和Swing
如果正在使用Jython(Python的Java實現),類似wxPython和Tkinter這樣的包就不能用了。唯一可用的GUI工具包是Java標準庫包AWT和Swing(Swing是最新的,而且被認為是標準的Java GUI工具包)。好消息是兩者都直接可用,不用單獨安裝。更多信息,請訪問Java的網站,以及為Java而寫的Swing文檔:
☑ http://www.jython.org
☑ http://java.sun.com/docs/books/tutorial/uiswing
下麵是使用Jython和Swing實現的GUI示例。
from javax.swing import * import sys def hello(event): print "Hello, world!" btn = JButton("Hello") btn.actionPerformed = hello win = JFrame("Hello, Swing!") win.contentPane.add(btn) def closeHandler(event): sys.exit() win.windowClosing = closeHandler btn.size = win.size = 200, 100 win.show()
註意,這裡增加了一個額外的事件處理函數(closeHandler),因為關閉按鈕Java Swing中沒有任何有用的預設行為。另外,無須顯式地進入主事件迴圈,因為它是和程式並行運行的(在不同的線程中)。
12.4.3 使用其他開發包
大多數GUI工具包的基礎都一樣,不過遺憾的是當學習如何使用一個新的包時,通過使你能做成想做的事情的所有細節而找到學習新包的方法還是很花時間的。所以在決定使用哪個包(12.1節應該對從何處著手有些幫助)之前應該花上些時間考慮,然後就是泡在文檔中,寫代碼。希望本章能提供了那些理解文檔時需要的基礎概念。
12.5 小結
再來回顧一下本章講了什麼。
☑ 圖形用戶界面(GUI):GUI可以讓程式更友好。雖然並不是所有的程式都需要它們,但是當程式要和用戶交互時,GUI可能會有所幫助。
☑ Python的GUI平臺:Python程式員有很多GUI平臺可用。儘管有這麼多選擇是好事,但是選擇時有時會很困難。
☑ wxPython:wxPython是成熟的並且特色豐富的跨平臺的Python GUI工具包。
☑ 佈局:通過指定幾何坐標,可以直接將組件放置在想要的位置。但是,為了在包含它們的視窗改變大小時能做出適當的改變,需要使用佈局管理器。wxPython中的佈局機制是尺寸器。
☑ 事件處理:用戶的動作觸發GUI工具包的事件。任何應用中,程式都會有對這些事件的反應,否則用戶就沒法和程式交互了。wxPython中事件處理函數使用Bind方法添加到組件上。
接下來學什麼
本章內容就是這樣了。已經學習完瞭如何編寫通過文件和GUI與外部世界交互的程式。下一章將會介紹另外一個很多程式系統都具有的重要組件:資料庫。