最近在研究wxpython,需要在win32下編程。
1
另一種可供Python使用的GUI工具包叫做wxPython。目前這個工具對於Python環境來說還是陌生的,但正在Python開發人員中間快速地流行起來。
wxPython是Python擴充模組,它封裝了wxWindows C++類庫。
wxPython是一個為Python提供的交叉平台GUI架構工具,它在Windows平台上相當成熟。它是基於流行的wxWindows C++架構的Python,為GUI
開發人員提供了一種有吸引力的替代工具。
wxWindows
wxWindows是一個自由C++編程架構,被設計用來實現跨平台編程。wxWindows 2.0支援Windows 3.1/95/98/NT,Unix下支援
GTK/Motif/Lesstif,還有正在開發中的Mac版本。其它系統正在考慮中。
wxWindows是一套庫函數,允許C++應用程式只需要微量的原始碼改動,就可在幾種不同類型的機構上編譯和運行。每一種支援的GUI都有一
個對應的庫( 象Motif或Windows)。同時為了實現GUI功能提供了通用的API,還為了處理一些通常要用到的作業系統裝置提供了功能,應用程式
可以根據需要使用或替換,這樣就會節省大量的編碼工作。基本資料結構,象字串,鏈表,和雜湊表也提供了。
控制項的本地版本,通用對話方塊,和其它的視窗類別型被用在支援它們的平台上。對於其它的平台,相適應的替代品使用wxWindows自身來產生
。例如,在W in32平台上,使用了本地的清單控制項,但是在GTK,具有相似功能的通用清單控制項則是使用wxWindows類庫建立的。
有經驗的Windows程式員對於wxWindows物件模型會感到象是在家一樣。類和原則的許多地方都很相似。例如,多重文件介面,用GDI對象,如
刷子,筆,在上下文設定上繪圖,等等。
wxWdinws + Python = wxPython
wxPython是一個Python擴充模組,它提供了一套從wxWindows庫到Python語言的綁定。換句話說,擴充模組允許Python程式員建立wxW
indows類的執行個體,並且調用這些類的方法。
wxPython擴充模組試圖近可能的將wxWindows的類的層次也鏡像下來。這就是說在wxPython中有一個wxFrame的類,看上去,聞上去,嘗上
去和行為上幾乎同C ++版本中的wxFrame一樣。
wxPython與C++版本如此接近,這樣wxPython文檔的大多數實際上是對C++文檔中,wxPython與之不同地方的註解。其中還包含了一系列的
例子程式,和一系列協助程式員開始使用w xPython的文檔頁
2
哪裡可以得到wxPython
wxPython的最新版本可以在http://alldunn.com/wxPython/上找到。你可以從這個網站下載一個Win32系統的自安裝軟體,其中包含一個已
經產生好的擴充模組,H TML協助格式文檔,和一組樣本程式。
也可以從這個網站獲得Linux RPM,wxPython源碼,原始的HTML文檔,和其它網站的連結,郵件清單,wxPython FAQ,等等。
如果你想自已從原始碼建立wxPython,你也需要wxWindows原始碼,可以從http://www.wxwindows.org/得到。
下一步去哪裡?
實踐證明,學習的最好方法就是動手,接著做實驗,觀察得到的結果。所以你應該下載和安裝wxPython,啟動你常用的文字編輯器,準備
執行在下面幾節所讀到的東西。
一個簡單的例子
應該對下面的小wxPython程式進行熟悉,當讀到跟著的解釋時,可以回過頭來進行參考:
from wxPython.wx import *
class MyApp(wxApp):
def OnInit(self):
frame = wxFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
一個基本的wxPython程式要做的第一件事情就是匯入整個wxPython庫,使用 from wxPython import * 語句。這是編wxPython程式的通常習
慣,但是你可以根據需要明確地執行更多限制的匯入。
每一個wxPython應用程式需要從wxApp派生出一個類,並且為其提供一個OnInit 方法。架構(即視窗)會調用這個方法作為自身初始化序
列的一部分。Oninit通常是用來建立視窗,和對於程式開始運行完全必需的操作。在例子中,你建立了一個沒有父親的架構,標題是“Hello
from wxPython ”,然後顯示它。我們也可以在它的建構函式中為架構指定位置和大小,但是因為沒有,所以這裡使用了預設值。OnInit方法
的最後兩行可能對於所有應用程式來說都是一樣的。SetTopWindow方法告訴wxWindows ,這個架構是應用程式主架構其中之一(在這個例子中
只有一個),並且你返回true來表明成功。當所有項層視窗實關閉,應用程式結束。
指令碼的最後兩行可能又是對於所有的wxPython應用程式是一樣的。你建立了一個應用程式類的執行個體,並且調用它的MainLoop方法。
MainLoop是應用程式的心臟:在這裡事情被處理,並且被發送到各個視窗,當最後一個視窗關閉後,它返回。幸運的是,wxWindows對你屏蔽了
各種GUI工具包在事件處理上的不同。
3
大多數情況下,你會想要定製應用程式的主架構,所以使用普通的wxFrame是不夠的。你可能希望,從wxFrame派生出自已的類進行定製。下一
個例子定義了一個架構類,並且在應用程式中的O nInit方法中建立了一個執行個體。注意除了在OnInit中建立的類的名字,MyApp代碼的其它部分
同以前的例子是一樣的。
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,wxDefaultPosition, wxSize(200, 150))
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About","More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
這個例子顯示了一些wxFrame內建的一些功能。例如,為架構建立一個狀態條只要簡單地調用一個方法。架構本身會自動地管理它的位置,
大小,和繪製。另一方面,如果你想定製狀態條,從你自已的wxStatusBar衍生類別建立執行個體,並將其附加到架構上。
在這個例子中也示範了建立一個簡單的菜單條和一個下拉式功能表。期待的菜單功能全部都支援:層疊子功能表,可核選的項,快顯功能表等等;
你要做的只是建立一個菜單對象,向它追加功能表項目。功能表項目可以是象這裡顯示的文本,或其它的菜單。每一項你可以有選擇地指定一些簡單的
協助性文本,就象我們所做的。當功能表項目被選中,這些文本會自動地顯示在狀態條上。
4
在wxPython中的事件上一個例子沒有做的一件事情就是:展示如何讓菜單自動做一些事情。如果你運行這個例子,並且從菜單中選中Exit,
什麼都沒有發生。下一個例子改正了這個小問題。
為了在wxPython中處理事件,任何方法(或同樣方式的獨立函數)都可以使用工具包中的協助性函數與任意事件相連。wxPython也提供了
一個w xEvent 類,和一整串衍生類別,包含了事件的細節。每次當發生一個事件,一個方法被調用,一個從wxEvent 派生的對象被做為一參數傳
遞,事件對象的實際類型要依賴於事件的類型。wxSizeEvent用於當視窗改變大小,wxCommandEvent用於菜單選擇和按鈕點擊,w xMouseEvent
用於(可以猜到)滑鼠的移動,等等。
為瞭解決上一個例子中的小問題,你要做的就是,在MyFrame建構函式中增加兩行,並且增加處理事件的一些方法。我們也示範了一個通用
對話方塊,w xMessageDialog。下面就是代碼。
from wxPython.wx import *
ID_ABOUT = 101
ID_EXIT = 102
class MyFrame(wxFrame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,wxDefaultPosition, wxSize(200, 150))
self.CreateStatusBar()
self.SetStatusText("This is the statusbar")
menu = wxMenu()
menu.Append(ID_ABOUT, "&About","More information about this program")
menu.AppendSeparator()
menu.Append(ID_EXIT, "E&xit", "Terminate the program")
menuBar = wxMenuBar()
menuBar.Append(menu, "&File");
self.SetMenuBar(menuBar)
EVT_MENU(self, ID_ABOUT, self.OnAbout)
EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def OnAbout(self, event):
dlg = wxMessageDialog(self, "This sample program shows off\n"
"frames, menus, statusbars, and this\n""message dialog.",
"About Me", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def TimeToQuit(self, event):
self.Close(true)
class MyApp(wxApp):
def OnInit(self):
frame = MyFrame(NULL, -1, "Hello from wxPython")
frame.Show(true)
self.SetTopWindow(frame)
return true
app = MyApp(0)
app.MainLoop()
在這裡調用的EVT_MENU函數是一個協助性函數,是為了將事件與方法連在一起。有時候,如果你將函數調用翻譯成英語,它可以協助我們
理解發生了什麼。第一個說的是,“對於任一個功能表項目選中事件,發送視窗本身和一個ID_ABOUT的ID號,調用self.OnAbout方法。”
有很多EVT_*協助性函數,所有的都對應某個事件類型,或事件。下面將常用的一些事件列出。
請參閱wxPython文檔瞭解更多的細節。
常見wxPython事件函數 事件函數事件描述
EVT_SIZE
由於使用者幹預或由程式實現,當一個視窗大小發生改變時發送給視窗。
EVT_MOVE
由於使用者幹預或由程式實現,當一個視窗被移動時發送給視窗。
EVT_CLOSE
當一個架構被要求關閉時發送給架構。除非關閉是強制性的,否則可以調用event.Veto(true)來取消關閉。
EVT_PAINT
無論何時當視窗的一部分需要重繪時發送給視窗。
EVT_CHAR
當視窗擁有輸入焦點時,每產生非修改性(Shift鍵等等)按鍵時發送。
EVT_IDLE
這個事件會當系統沒有處理其它事件時週期性發送。
EVT_LEFT_DOWN
滑鼠左鍵按下。
EVT_LEFT_UP
滑鼠左鍵抬起。
EVT_LEFT_DCLICK
滑鼠左鍵雙擊。
EVT_MOTION
滑鼠在移動。
EVT_SCROLL
捲軸被操作。這個事件其實是一組事件的集合,如果需要可以被單獨捕捉。
EVT_BUTTON
按鈕被點擊。
EVT_MENU
菜單被選中
5
用Python建立一個Doubletalk瀏覽器
Ok, 現在讓我們做些有用的東西,用這種方法學習更多關於wxPython架構的知識。就象其它的GUI工具包所展示的,我們將建立一個小型的
應用程式,圍繞著Doubletalk類庫(它允許瀏覽和編輯交易)。
MDI架構
我們打算實現一個多重文件介面(譯註,即MDI),子架構除了為獨立的“文檔”外,其中還包含著交易資料的不同視圖。如同前面的例子,
要做的第一件事就是建立一個應用程式類,並且在它的O nInit方法中建立一個主架構:
from wxPython.wx import *
class DoubleTalkBrowserApp(wxApp):
def OnInit(self):
frame = MainFrame(NULL)
frame.Show(true)
self.SetTopWindow(frame)
return true
app = DoubleTalkBrowserApp(0)
app.MainLoop()
因為我們正在使用MDI,所以需要一個特別的類用來做為架構的基本類。這裡的給出主程式架構的初始化方法的代碼:
class MainFrame(wxMDIParentFrame):
title = "Doubletalk Browser - wxPython Edition"
def __init__(self, parent):
wxMDIParentFrame.__init__(self, parent, -1, self.title)
self.bookset = None
self.views = []
if wxPlatform == ’__WXMSW__’:
self.icon = wxIcon(’chart7.ico’, wxBITMAP_TYPE_ICO)
self.SetIcon(self.icon)
# 建立一個狀態條,在右邊顯示時間和日期
sb = self.CreateStatusBar(2)
sb.SetStatusWidths([-1, 150])
self.timer = wxPyTimer(self.Notify)
self.timer.Start(1000)
self.Notify()
menu = self.MakeMenu(false)
self.SetMenuBar(menu)
menu.EnableTop(1, false)
EVT_MENU(self, ID_OPEN, self.OnMenuOpen)
EVT_MENU(self, ID_CLOSE, self.OnMenuClose)
EVT_MENU(self, ID_SAVE, self.OnMenuSave)
EVT_MENU(self, ID_SAVEAS,self.OnMenuSaveAs)
EVT_MENU(self, ID_EXIT, self.OnMenuExit)
EVT_MENU(self, ID_ABOUT, self.OnMenuAbout)
EVT_MENU(self, ID_ADD, self.OnAddTrans)
EVT_MENU(self, ID_JRNL, self.OnViewJournal)
EVT_MENU(self, ID_DTAIL, self.OnViewDetail)
EVT_CLOSE(self, self.OnCloseWindow)
很明顯,我們沒有展示全部的代碼,但是隨著我們一點一點地學習,我們將最終將其變得完整。
注意,使用wxMDIParentFrame作為MainFrame的基類。通過使用這個類,你會自動地獲得為實現MDI應用程式的所有需要的東西,不需要關
心在外表後面發生了什麼。wxMDIParentFrame 類有著與wxFrame類同樣的介面,只是擁有一些額外的方法。通常將一個單文檔介面程式改為一
個多文檔程式只是象改變應用程式衍生類別的基類一樣容易。對於wxMDIParentFrame 類,存在相對應的wxMDIChildFrame類,用於作文件視窗,
在後面就會看到。如果你需要處理MDI父視窗的用戶端區域(或背景地區),你可以使用 wxMDIClientWindow類。你可以使用它來在所有子視窗的
後面放置一個背景映像。
在檔案對話方塊成功完成之後,要做的第一件事就是詢問對話方塊被選中的路徑名是什麼,然後使用這個路徑來修改架構的標題,然後開啟
一個BookSet檔案。
看一下後面一行。它重新使BookSet菜單有效,因為現在已經存在一個開啟檔案了。它實際是兩行語句合成一句,相當於這兩行:
menu = self.GetMenuBar()
menu.EnableTop(1, true)
因為當使用者開啟一個檔案時,讓他們確實地看到一些東西是有意義的,你應該建立並且顯示其中一個視圖,用上面的OnMenuOpen處理函數
中最後幾行代碼。在下面,我們會看到。
wxListCtrl
日誌視圖是由wxListCtrl組成,其中每一個交易都有一個單行的小計。這個控制項放置在wxMDIChildFrame中,並且因為它是架構中唯一的東
西,所以不用擔心設定或維護大小,架構會自動維護它。(不幸地是,因為某些平台在不同的時間發送第一個改變大小(r esize)事件,有時
候視窗顯示時,它的子視窗大小會不正確。)
class JournalView(wxMDIChildFrame):
def __init__(self, parent, bookset, editID):
wxMDIChildFrame.__init__(self, parent, -1, "")
self.bookset = bookset
self.parent = parent
tID = wxNewId()
self.lc = wxListCtrl(self, tID, wxDefaultPosition,
wxDefaultSize, wxLC_REPORT)
## Forces a resize event to get around a minor bug...
self.SetSize(self.GetSize())
self.lc.InsertColumn(0, "Date")
self.lc.InsertColumn(1, "Comment")
self.lc.InsertColumn(2, "Amount")
self.currentItem = 0
EVT_LIST_ITEM_SELECTED(self, tID, self.OnItemSelected)
EVT_LEFT_DCLICK(self.lc, self.OnDoubleClick)
menu = parent.MakeMenu(true)
self.SetMenuBar(menu)
EVT_MENU(self, editID, self.OnEdit)
EVT_CLOSE(self, self.OnCloseWindow)
self.UpdateView()