# 第二章 給wxPython程序一個堅實的基礎
1. [給你的wxPython程序一個穩固的基礎](#A.2BftlPYHaE-wxPython.2Begtej04ATip6M1b6doRX.2BnhA-)
1. [關于所要求的對象我們需要知道些什么?](#A.2BUXNOjmJAiYFsQnaEW.2FmMYWIRTuyXAImBd.2BWQU06bTsBOSP8f-)
2. [如何創建和使用一個應用程序對象?](#A.2BWYJPVVIbXvpUjE9.2FdShOAE4qXpR1KHoLXo9b.2BYxh.2Fx8-)
1. [創建一個wx.App的子類](#A.2BUhte.2Bk4ATio-wx.App.2BdoRbUHx7-)
2. [理解應用程序對象的生命周期](#A.2BdAaJ416UdSh6C16PW.2FmMYXaEdR9UfVRoZx8-)
3. [如何定向wxPython程序的輸出?](#A.2BWYJPVVuaVBE-wxPython.2Begtej3aEj5NR.2Bg.3F)
1. [重定向輸出](#A.2Bkc1bmlQRj5NR.2Bg-)
2. [修改默認的重定向行為](#A.2BT.2B5lOZ7Yi6R2hJHNW5pUEYhMTjo-)
4. [如何關閉wxPython應用程序?](#A.2BWYJPVVFzle0-wxPython.2BXpR1KHoLXo8.3F)
1. [管理正常的關閉](#A.2Be6F0BmtjXjh2hFFzle0-)
2. [管理緊急關閉](#A.2Be6F0Bn0nYCVRc5Xt-)
5. [如何創建和使用頂級窗口對象?](#A.2BWYJPVVIbXvpUjE9.2FdSiYdn6nepdT41v5jGE.3F)
1. [使用wx.Frame](#A.2BT391KA-wx.Frame)
2. [使用wxPython的ID](#A.2BT391KA-wxPython.2BdoQ-ID)
3. [使用wx.Size和wx.Point](#A.2BT391KA-wx.Size.2BVIw-wx.Point)
4. [使用wx.Frame的樣式](#A.2BT391KA-wx.Frame.2BdoRoN18P-)
6. [如何為一個框架增加對象和子窗口?](#A.2BWYJPVU46TgBOKmhGZ7ZYnlKgW.2FmMYVSMW1B6l1Pj.3F)
1. [給框架增加窗口部件](#A.2BftloRme2WJ5SoHqXU.2BOQ6E72-)
2. [給框架增加菜單欄、工具欄和狀態欄。](#A.2BftloRme2WJ5SoIPcU1VoDzABXeVRd2gPVIxytmABaA8wAg-)
7. [如何使用一般的對話框?](#A.2BWYJPVU9.2FdShOAIIsdoRb.2BYvdaEY.3F)
8. [一些最常見的錯誤現象及解決方法?](#A.2BTgBOm2cAXjiJwXaElRmL73OwjGFTyonjUbNluWzV.3F)
9. [總結](#A.2BYDt.2B0w-)
房屋的基礎是混凝土結構,它為其余的建造提供了堅固的基礎。你的`wxPython`程序同樣有一個基礎,它由兩個必要的對象組成,用于支持你的應用程序的其余部分。它們是應用程序對象和頂級窗口對象。適當地使用這兩個對象將給你的`wxPython`應用程序一個穩固的開始并使得構造你的應用程序的其余部分更容易。
## 關于所要求的對象我們需要知道些什么?
讓我們來說明一下這兩個基礎對象。**應用程序對象**管理主事件循環,主事件循環是你的`wxPython`程序的動力。啟動主事件循環是應用程序對象的工作。沒有應用程序對象,你的`wxPython`應用程序將不能運行。 **頂級窗口**通常管理最重要的數據,控制并呈現給用戶。例如,在詞處理程序中,主窗口是文檔的顯示部分,并很可能管理著該文檔的一些數據。類似地,你的`web`瀏覽器的主窗口同時顯示你所關注的頁面并把該頁作為一個數據對象管理。 下圖顯示了這兩個基礎對象和你的應用程序的其它部分這間的關系:

如圖所示,這個應用程序對象擁有頂級窗口和主事件循環。頂級窗口管理其窗口中的組件和其它的你分配給它的數據對象。窗口和它的組件的觸發事件基于用戶的動作,并接受事件通知以便改變顯示。
## 如何創建和使用一個應用程序對象?
任何`wxPython`應用程序都需要一個應用程序對象。這個應用程序對象必須是類`wx.App`或其定制的子類的一個實例。應用程序對象的主要目的是管理幕后的主事件循環。這個事件循環響應于窗口系統事件并分配它們給適當的事件處理器。這個應用程序對象對`wxPython`進程的管理如此的重要以至于在你的程序沒有實例化一個應用程序對象之前你不能創建任何的`wxPython`圖形對象。
父類`wx.App`也定義了一些屬性,它們對整個應用程序是全局性的。很多時候,它們就是你對你的應用程序對象所需要的全部東西。假如你需要去管理另外的全局數據或連接(如一個數據庫連接),你可以定制應用程序子類。在某些情況下,你可能想為專門的錯誤或事件處理而擴展這個主事件循環。然而,默認的事件循環幾乎適合所有的你所要寫的`wxPython`應用程序。
### 創建一個wx.App的子類
創建你自己的`wx.App`的子類是很簡單的。當你開始你的應用程序的時候,創建你自己的`wx.App`的子類通常是一個好的想法,即使是你不定制任何功能。創建和使用一個`wx.App`子類,你需要執行四個步驟:
1、定義這個子類 2、在定義的子類中寫一個`OnInit()`方法 3、在你的程序的主要部分創建這個類的一個實例 4、調用應用程序實例的`MainLoop()`方法。這個方法將程序的控制權轉交給`wxPython`
我們在第一章中看到過`OnInit()`方法。它在應用程序開始時并在主事件循環開始前被`wxPython`系統調用。這個方法不要求參數并返回一個布爾值,如果所返回的值是`False`,則應用程序將立即退出。大多數情況下,你將想要該方法返回的結果為真。處理某些錯誤條件,退出可能是恰當的方法,諸如所一個所需的資源缺失。
由于`OnInit()`方法的存在,并且它是`wxPython`架構的一部分,所以任何關于你的定制的類的所需的初始化通常都由`OnInit()`方法管理,而不在`Python`的`__init__`方法中。如果由于某些原因你決定需要`__init__`方法,那么你必須在你的`__init__`方法中調用父類的`__init__`方法,如下所示:
```
wx.App.__init__(self)
```
通常,你在`OnInit()`方法中將至少創建一個框架對象,并調用該框架的`Show()`方法。你也可以有選擇地通過調用`SetTopWindow()`方法來為應用程序指定一個框架作為頂級窗口。頂級窗口被作為那些沒有指定父窗口的對話框的默認父窗口。
**何時省略`wx.App`的子類**
你沒有必要創建你自己的`wx.App`子類,你通常想這樣做是為了能夠在`OnInit()`方法中創建你的頂級框架。 通常,如果在系統中只有一個框架的話,避免創建一個`wx.App`子類是一個好的主意。在這種情況下,`wxPython`提供了一個方便的類`wx.PySimpleApp`。這個類提供了一個最基本的`OnInit()`方法,`wx.PySimpleApp`類定義如下:
```
class PySimpleApp(wx.App):
def __init__(self, redirect=False, filename=None,
useBestVisual=False, clearSigInt=True):
wx.App.__init__(self, redirect, filename, useBestVisual,
clearSigInt)
def OnInit(self):
return True
```
下面是`wx.PySimpleApp`一個簡單用法:
```
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyNewFrame(None)
frame.Show(True)
app.MainLoop()
```
在上面這段代碼的第一行,你創建了一個作為`wx.PySimpleApp`的實例的應用程序對象。由于我們在使用 `wx.PySimpleApp`類,所以我們沒有定制`OnInit`方法。第二行我們定義了一個沒有父親的框架,它是一個頂級的框架。(很顯然,這個`MyNewFrame`類需要在別處被定義)這第三行顯示框架,最后一行調用應用程序主循環。
正如你所看到的,使用`wx.PySimpleApp`讓你能夠運行你的`wxPython`程序而無需創建你自己定制的應用程序類。如果你的應用程序十分簡單的話,你應該只使用`wx.PySimpleApp`,且不需要任何其它的全局參數。
### 理解應用程序對象的生命周期
你的`wxPython`應用程序對象的生命周期開始于應用程序實例被創建時,在最后一個應用程序窗口被關閉時結束。這個沒有必要與你的`wxPython`應用程序所在的`Python`腳本的開始和結束相對應。`Python`腳本可以在`wxPython`應用程序創建之前選擇做一動作,并可以在`wxPython`應用程序的`MainLoop()`退出后做一些清理工作。然而所有的`wxPython`動作必須在應用程序對象的生命周期中執行。正如我們曾提到過的,這意味你的主框架對象在`wx.App`對象被創建之前不能被創建。(這就是為什么我們建議在`OnInit()`方法中創建頂級框架——因為這樣一來,就確保了這個應用程序已經存在。)
下圖所示,創建應用程序對象觸發`OnInit()`方法并允許新的窗口對象被創建。在`OnInit()`之后,這個腳本調用`MainLoop()`方法,通知`wxPython`事件現在正在被處理。在窗口被關閉之前應用程序繼續它的事件處理。當所有頂級窗口被關閉后,`MainLoop()`函數返回同時應用程序對象被注銷。這之后,這個腳本能夠關閉其它的可能存豐的連接或線程。

## 如何定向wxPython程序的輸出?
所有的`Python`程序都能夠通過兩種標準流來輸出文本:分別是標準輸出流`sys.stdout`和標準錯誤流`sys.stderr`。通常,`Python`腳本定向標準輸出流到它所運行的控制臺。然而,當你的應用程序對象被創建時,你可以決定使用`wxPython`控制標準流并重定向輸出到一個窗口。在`Windows`下,這個重定向行為是`wxPython`的默認行為。而在`Unix`系統中,默認情況下,`wxPython`不控制這個標準流。在所有的系統中,當應用程序對象被創建的時候,重定向行為可以被明確地指定。我們推薦利用這個特性并總是指定重定向行為來避免不同平臺上的不同行為產生的任何問題。
### 重定向輸出
如果`wxPython`控制了標準流,那么經由任何方法發送到流的文本被重定向到一個`wxPython`的框架。在`wxPyton`應用程序開始之前或結束之后發送到流的文本將按照`Python`通常的方法處理(輸出到控制臺)。下例同時演示了應用程序的生命周期和`stdout`/`stderr`重定向:
```
#!/usr/bin/env python
import wx
import sys
class Frame(wx.Frame):
def __init__(self, parent, id, title):
print "Frame __init__"
wx.Frame.__init__(self, parent, id, title)
class App(wx.App):
def __init__(self, redirect=True, filename=None):
print "App __init__"
wx.App.__init__(self, redirect, filename)
def OnInit(self):
print "OnInit" #輸出到stdout
self.frame = Frame(parent=None, id=-1, title='Startup') #創建框架
self.frame.Show()
self.SetTopWindow(self.frame)
print sys.stderr, "A pretend error message" #輸出到stderr
return True
def OnExit(self):
print "OnExit"
if __name__ == '__main__':
app = App(redirect=True) #1 文本重定向從這開始
print "before MainLoop"
app.MainLoop() #2 進入主事件循環
print "after MainLoop"
```
**說明:**
**#1** 這行創建了應用程序對象。這行之后,所有發送到`stderr`或`stdout`的文本都可被`wxPython`重定向到一個框架。參數`redirect`=`True`決定了是否重定向。
**#2** 運行的時候,應用程序創建了一個空的框架和也生成了一個用于重定向輸出的框架。圖示如下:

注意:`stdout`和`stderr`都定向到這個窗口。 當你運行了這個程序之后,你將會看到你的控制臺有下面的輸出:
`App` `__init__` `after` `MainLoop`
這第一行在框架被打開之前生成,第二行在框架被關閉之后生成。 通過觀察控制臺和框架的輸出,我們可以跟蹤應用程序的生命周期。
下面我們將上面的程序與圖2.2作個比較,圖中的"`Start` `Script`"對應于程序的 `__main__`語句。然后立即過渡到下一“`Application` `obect` `created`",對應于程序的`app` = `App(redirect`=`True)`。應用程序實例的創建通過調用`wx.App.__init__()`方法。然后是`OnInit()`,它被`wxPython`自動調用。從這里,程序跳轉到`wx.Frame.__init__()`,它是在`wx.Frame`被實例化時運行。最后控制轉回到`__main__`語句,這里,`MainLoop()`被調用,對應于圖中的"`MainLoop()` `called`"。主循環結束后,`wx.App.OnExit()`被`wxPython`調用,對應于圖中“`Application` `object` `destroyed`”。然后腳本的其余部分完成處理。
為什么來自`OnExit()`的消息既沒顯示在窗口中也沒顯示在控制臺中呢?其實它是在窗口關閉之前顯示在`wxPython`的框架中,但窗口消失太快,所以無法被屏幕捕獲。
### 修改默認的重定向行為
為了修改這個行為,`wxPython`允許你在創建應用程序時設置兩個參數。第一個參數是`redirect`,如果值為`True`,則重定向到框架,如果值為`False`,則輸出到控制臺。如果參數`redirect`為`True`,那么第二個參數`filename`也能夠被設置,這樣的話,輸出被重定向到`filename`所指定的文件中而不定向到`wxPython`框架。因此,如果我們將上例中的`app` = `App(redirect`=`True)`改為`app` = `App(False)`,則輸出將全部到控制臺中:
```
App __init__
OnInit
Frame __init__
A pretend error message
before MainLoop
OnExit
after MainLoop
```
我們可以注意到`OnExit()`消息在這里顯示出來了。 我們再作一個改變:
```
app = App(True, "output")
```
這將導致所有的應用程序創建后的輸出重定向到名為`output`的文件中。而"`App__init`"和"`after` `MainLoop`"消息仍將發送到控制臺,這是因為它們產生在`wx.App`對象控制流的時期之外。
## 如何關閉wxPython應用程序?
當你的應用程序的最后的頂級窗口被用戶關閉時,`wxPython`應用程序就退出了。我們這里所說的頂層窗口是指任何沒有父親的框架,并不只是使用`SetTopWindow()`方法設計的框架。這包括任何由`wxPython`自身創建的框架。在我們重定向的例子中,`wxPython`應用程序在主框架和輸出重定向的框架都被關閉后退出,僅管只有主框架是使用`SetTopWindow()`登記的,盡管應用程序沒有明確地創建這個輸出重定向框架。要使用編程觸發一個關閉,你可以在所有的這里所謂頂級窗口上調用`Close()`方法。
### 管理正常的關閉
在關閉的過程期間,`wxPython`關心的是刪除所有的它的窗口和釋放它們的資源。你可以在退出過程中定義一個鉤子來執行你自己的清理工作。由于你的`wx.App`子類的`OnExit()`方法在最后一個窗口被關閉后且在`wxPython`的內在的清理過程之前被調用,你可以使用`OnExit()`方法來清理你創建的任何非`wxPython`資源(例如一個數據庫連接)。即使使用了`wx.Exit()`來關閉`wxPython`程序,`OnExit()`方法仍將被觸發。
如果由于某種原因你想在最后的窗口被關閉后`wxPython`應用程序仍然可以繼續,你可以使用`wx.App`的`SetExitOnFrameDelete(flag)`方法來改變默認的行為。如果`flag`參數設置為`False`,那么最后的窗口被關閉后`wxPython`應用程序仍然會繼續運行。這意味著`wx.App`實例將繼續存活,并且事件循環將繼續處理事件,比如這時你還可以創建所有新的這里所謂的頂級窗口。`wxPython`應用程序將保持存活直到全局函數`wx.Exit()`被明確地調用。
### 管理緊急關閉
你不能總是以一個可控的方法關閉你的程序。有時候,你需要立即結束應用程序并不考慮清理工作。例如一個必不可少的資源可能已被關閉或被損壞。如果系統正在關閉,你可能不能做所有的清理工作。
這里有兩種在緊急情況下退出你的`wxPython`應用程序的方法。你可以調用`wx.App`的`ExitMainLoop()`方法。這個方法顯式地使用主消息循環終止,使用控制離開`MainLoop()`函數。這通常將終止應用程序,這個方法實際上等同于關閉所有這里所謂頂級窗口。
你也可以調用全局方法`wx.Exit()`。正常使用情況下,兩種方法我們都不推薦,因為它將導致一些清理函數被跳過。
有時候,你的應用程序由于一個控制之外的事件而需要關閉。例如操作系統的關閉或注銷。在這種情況下,你的應用程序將試圖做一些保存文檔或關閉連接等等。如果你的應用程序為`wx.EVT_QUERY_END_SESSION`事件綁定了一個事件處理器,那么當`wxPython`得到關閉通知時這個事件處理器將被調用。這個`event`參數是`wx.CloseEvent`。我們可以通過關閉事件來否決關閉。這可以使用關閉事件的`CanVeto()`方法,`CanVeto()`方法決定是否可以否決,`Veto()`執行否決。如果你不能成功地保存或關閉所有的資源,你可能想使用該方法。`wx.EVT_QUERY_END_SESSION`事件的默認處理器調用頂級窗口的`Close()`方法,這將依次向頂層窗口發送`wx.EVT_CLOSE`事件,這給了你控制關閉過程的另一選擇。如果任何一個`Close()`方法返回`False`,那么應用程序將試圖否決關閉。
## 如何創建和使用頂級窗口對象?
在你的應用程序中一個頂級窗口對象是一個窗口部件(通常是一個框架),它不被別的窗口部件所包含。頂級窗口對象通常是你的應用程序的主窗口,它包含用戶與之交互的窗口部件和界面對象。當所有的頂級窗口被關閉時應用程序退出。
你的應用程序至少必須有一個頂級窗口對象。頂級窗口對象通常是類`wx.Frame`的子類,盡管它也可以是`wx.Dialog`的子類。大多數情況下,你將為了使用為你的應用程序定義定制的`wx.Frame`的子類。然而,這兒也存在一定數量的預定義的`wx.Dialog`的子類,它們提供了許多你可能會在一個應用程序中遇到的典型的對話框。
這兒可能有一個名稱上的混淆,那就是“頂級窗口”。一般意義上的頂級窗口是指在你的應用程序中任何沒有父容器的窗口部件。你的應用程序必須至少有一個,但是,只要你喜歡可以有多個。但是它們中只有一個可以通過使用`SetTopWindow()`被`wxPython`作為主頂級窗口。如果你沒有使用`SetTopWindow()`指定主頂級窗口,那么在`wx.App`的頂級窗口列表中的第一個框架將被認為是這個主頂級窗口。因此,明確地定義一個主頂級窗口不總是必要的,例如,你只有一個頂級窗口的時候。反復調用`SetTopWindow()`將反復改變當前的主頂級窗口,因為一個應用程序一次只能有一主頂級窗口。
### 使用wx.Frame
按照`wxPython`中的說法,框架就是用戶通常稱的窗口。那就是說,框架是一個容器,用戶可以將它在屏幕上任意移動,并可將它縮放,它通常包含諸如標題欄、菜單等等。在`wxPython`中,`wx.Frame`是所有框架的父類。這里也有少數專用的`wx.Frame`子類,你可以使用它們。
當你創建`wx.Frame`的子類時,你的類應該調用其父類的構造器`wx.Frame.__init__()`。`wx.Frame`的構造器所要求的參數如下:
```
wx.Frame(parent, id=-1, title="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame")
```
我們在別的窗口部件的構造器中將會看到類似的參數。參數的說明如下:
`parent`:框架的父窗口。對于頂級窗口,這個值是`None`。框架隨其父窗口的銷毀而銷毀。取決于平臺,框架可被限制只出現在父窗口的頂部。在多文檔界面的情況下,子窗口被限制為只能在父窗口中移動和縮放。
`id`:關于新窗口的`wxPython` `ID`號。你可以明確地傳遞一個。或傳遞-1,這將導致`wxPython`自動生成一個新的`ID`。
`title`:窗口的標題。
`pos`:一個`wx.Point`對象,它指定這個新窗口的左上角在屏幕中的位置。在圖形用戶界面程序中,通常(0,0)是顯示器的左上角。這個默認的(-1,-1)將讓系統決定窗口的位置。
`size`:一個`wx.Size`對象,它指定這個窗口的初始尺寸。這個默認的(-1,-1)將讓系統決定窗口的初始尺寸。
`style`:指定窗口的類型的常量。你可以使用或運算來組合它們。
`name`:框架的內在的名字。以后你可以使用它來尋找這個窗口。
記住,這些參數將被傳遞給父類的構造器方法:`wx.Frame.__init__()`。
創建`wx.Frame`子類的方法如下所示:
```
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "My Friendly Window",
(100, 100), (100, 100))
```
### 使用wxPython的ID
在`wxPython`中,`ID`號是所有窗口部件的特征。在一個`wxPython`應用程序中,每個窗口部件都有一個窗口標識。在每一個框架內,`ID`號必須是唯一的,但是在框架之間你可以重用`ID`號。然而,我們建議你在你的整個應用程序中保持`ID`號的唯一性,以防止處理事件時產生錯誤和混淆。在`wxPython`中也有一些標準的預定義的`ID`號,它們有特定的意思(例如,`wx.ID_OK`和`wx.ID_CANCEL`是對話框中的`OK`和`Cancel`按鈕的`ID`號)。在你的應用程序中重用標準的`ID`號一般沒什么問題,只要你在預期的方式中使用它們。在`wxPython`中,`ID`號的最重要的用處是在指定的對象發生的事件和響應該事件的回調函數之間建立唯一的關聯。
有三種方法來創建一個窗口部件使用的`ID`號:
1、明確地給構造器傳遞一個正的整數 2、使用`wx.NewId()`函數 3、傳遞一個全局常量`wx.ID_ANY`或-1給窗口部件的構造器
**明確地選擇`ID`號**
第一個或最直接的方法是明確地給構造器傳遞一個正的整數作為該窗口部件的`ID`。如果你這樣做了,你必須確保在一個框架內沒有重復的`ID`或重用了一個預定義的常量。你可以通過調用`wx.RegisterId()`來確保在應用程序中`wxPython`不在別處使用你的`ID`。要防止你的程序使用相同的`wxPython` `ID`,你應該避免使用全局常量`wx.ID_LOWEST`和`wx.ID_HIGHEST`之間的`ID`號。
**使用全局性的`NewID()`函數**
自己確保`ID`號的唯一性十分麻煩,你可以使用`wx.NewId()`函數讓`wxPython`來為你創建`ID`:
```
id = wx.NewId()
frame = wx.Frame.__init__(None, id)
```
**你也可以給窗口部件的構造器傳遞全局常量`wx.ID_ANY`或-1**,然后`wxPython`將為你生成新的`ID`。然后你可以在需要這個`ID`時使用`GetId()`方法來得到它:
```
frame = wx.Frame.__init__(None, -1)
id = frame.GetId()
```
### 使用wx.Size和wx.Point
`wx.Frame`構造器的參數也引用了類`wx.Size`和`wx.Point`。這兩個類在你的`wxPython`編程中將頻繁被使用。 `wx.Point`類表示一個點或位置。構造器要求點的x和y值。如果不設置x,y值,則值默認為0。我們可以使用`Set(x`,`y)`和`Get()`函數來設置和得到x和y值。`Get()`函數返回一個元組。x和y值可以像下面這樣作為屬性被訪問:
```
point = wx.Point(10, 12)
x = point.x
y = point.y
```
另外,`wx.Point`的實例可以像其它`Python`對象一樣作加、減和比較運算,例如:
```
a = wx.Point(2, 3)
b = wx.Point(5, 7)
c = a + b
bigger = a > b
```
在`wx.Point`的實參中,坐標值一般為整數。如果你需要浮點數坐標,你可以使用類`wx.RealPoint`,它的用法如同`wx.Point`。
`wx.Size`類幾乎和`wx.Point`完全相同,除了實參的名字是`width`和`height`。對`wx.Size`的操作與`wx.Point`一樣。
在你的應用程序中當一個`wx.Point`或`wx.Size`實例被要求的時候(例如在另一個對象的構造器中),你不必顯式地創建這個實例。你可以傳遞一個元組給構造器,`wxPython`將隱含地創建這個`wx.Point`或`wx.Size`實例:
```
frame = wx.Frame(None, -1, pos=(10, 10), size=(100, 100))
```
### 使用wx.Frame的樣式
每個`wxPython`窗口部件都要求一個樣式參數。這部分我們將討論用于`wx.Frame`的樣式。它們中的一些也適用于別的`wxPython`窗口部件。一些窗口部件也定義了一個`SetStyle()`方法,讓你可以在該窗口部件創建后改變它的樣式。所有的你能使用的樣式元素都有一個常量標識符(如`wx.MINIMIZE_BOX`)。要使用多個樣式,你可以使用或運算符|。例如,`wx.DEFAULT_FRAME_STYLE`樣式就被定義為如下幾個基本樣式的組合:
```
wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX | wx.RESIZE_BORDER |wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX
```
要從一個合成的樣式中去掉個別的樣式,你可以使用^操作符。例如要創建一個默認樣式的窗口,但要求用戶不能縮放和改變窗口的尺寸,你可以這樣做:
```
wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER | wx.MINIMIZE_BOX |wx.MAXIMIZE_BOX)
```
如果你不慎使用了&操作符,那么將得到一個沒有樣式的、無邊框圖的、不能移動、不能改變尺寸和不能關閉的幀。
**下表2.2列出了用于`wx.Frame`的最重要的樣式**:
`wx.CAPTION`:在框架上增加一個標題欄,它顯示該框架的標題屬性。 `wx.CLOSE_BOX`:指示系統在框架的標題欄上顯示一個關閉框,使用系統默認的位置和樣式。 `wx.DEFAULT_FRAME_STYLE`:默認樣式。 `wx.FRAME_SHAPED`:用這個樣式創建的框架可以使用`SetShape()`方法去創建一個非矩形的窗口。 `wx.FRAME_TOOL_WINDOW`:通過給框架一個比正常更小的標題欄,使框架看起來像一個工具框窗口。在`Windows`下,使用這個樣式創建的框架不會出現在顯示所有打開窗口的任務欄上。 `wx.MAXIMIZE_BOX`:指示系統在框架的標題欄上顯示一個最大化框,使用系統默認的位置和樣式。 `wx.MINIMIZE_BOX`:指示系統在框架的標題欄上顯示一個最小化框,使用系統默認的位置和樣式。 `wx.RESIZE_BORDER`:給框架增加一個可以改變尺寸的邊框。 `wx.SIMPLE_BORDER`:沒有裝飾的邊框。不能工作在所有平臺上。 `wx.SYSTEM_MENU`:增加系統菜單(帶有關閉、移動、改變尺寸等功能)和關閉框到這個窗口。在系統菜單中的改變尺寸和關閉功能的有效性依賴于`wx.MAXIMIZE_BOX`, `wx.MINIMIZE_BOX`和`wx.CLOSE_BOX`樣式是否被應用。
下面的四張圖顯示了幾個通常的框架的樣式。




圖2.4是使用`wx.DEFAULT_STYLE`創建的。 圖2.5是使用`wx.DEFAULT_FRAME_STYLE` ^ (`wx.RESIZE_BORDER` | `wx.MINIMIZE_BOX` |`wx.MAXIMIZE_BOX)`組合樣式創建的。 圖2.6使用的樣式是`wx.DEFAULT_FRAME_STYLE` | `wx.FRAME_TOOL_WINDOW`。 圖2.7使用了擴展樣式 `wx.help.FRAME_EX_CONTEXTHELP`。
## 如何為一個框架增加對象和子窗口?
我們已經說明了如何創建`wx.Frame`對象,但是創建后的是空的。本節我們將介紹在你的框架中插入對象與子窗口的基礎,以便與用戶交互。
### 給框架增加窗口部件
圖2.8顯示了一個定制的`wx.Frame`的子類,名為`InsertFrame`。當點擊`close`按鈕時,這個窗口將關閉且應用程序將退出。例2.3定義了子類`InsertFrame`。

例2.3
```
#!/usr/bin/env python
import wx
class InsertFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Frame With Button',
size=(300, 100))
panel = wx.Panel(self) #創建畫板
button = wx.Button(panel, label="Close", pos=(125, 10),
size=(50, 50)) #將按鈕添加到畫板
#綁定按鈕的單擊事件
self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button)
#綁定窗口的關閉事件
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
def OnCloseMe(self, event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = InsertFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
```
類`InsertFrame`的方法`__init__`創建了兩子窗口。第一個是`wx.Panel`,它是其它窗口的容器,它自身也有一點功能。第二個是`wx.Button`,它是一個平常按鈕。接下來,按鈕的單擊事件和窗口的關閉事件被綁定到了相應的函數,當事件發生時這相應的函數將被調用執行。
大多數情況下,你將創建一個與你的`wx.Frame`大小一樣的`wx.Panel`實例以容納你的框架上的所有的內容。這樣做可以讓定制的窗口內容與其他如工具欄和狀態欄分開。 通過`tab`按鈕,可以遍歷`wx.Panel`中的元素,`wx.Frame`不能。
你不必像使用別的`UI`工具包那樣,你不需要顯式調用一個增加方法來向雙親中插入一個子窗口。在`wxPython`中,你只需在子窗口被創建時指定父窗口,這個子窗口就隱式地增加到父對象中了,例如例2.3所示。
你可能想知道在例2.3中,為什么`wx.Button`被創建時使用了明確的位置和尺寸,而`wx.Panel`沒有。在`wxPython`中,如果只有一個子窗口的框架被創建,那么那個子窗口(例2.3中是`wx.Panel`)被自動重新調整尺寸去填滿該框架的客戶區域。這個自動調整尺寸將覆蓋關于這個子窗口的任何位置和尺寸信息,盡管關于子窗口的信息已被指定,這些信息將被忽略。這個自動調整尺寸僅適用于框架內或對話框內的只有唯一元素的情況。這里按鈕是`panel`的元素,而不是框架的,所以要使用指定的尺寸和位置。如果沒有為這個按鈕指定尺寸和位置,它將使用默認的位置(`panel`的左上角)和基于按鈕標簽的長度的尺寸。
顯式地指定所有子窗口的位置和尺寸是十分乏味的。更重要的是,當用戶調整窗口大小的時候,這使得子窗口的位置和大小不能作相應調整。為了解決這兩個問題,`wxPython`使用了稱為`sizers`的對象來管理子窗口的復雜布局。
### 給框架增加菜單欄、工具欄和狀態欄。
圖2.9顯示了一個有菜單欄、工具欄和狀態欄的框架。

例2.4顯示了`__init__`方法,它用這三個子窗口裝飾了一個簡單的窗口。 例2.4
```
#!/usr/bin/env python
import wx
import images
class ToolbarFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Toolbars',
size=(300, 200))
panel = wx.Panel(self)
panel.SetBackgroundColour('White')
statusBar = self.CreateStatusBar() #1 創建狀態欄
toolbar = self.CreateToolBar() #2 創建工具欄
toolbar.AddSimpleTool(wx.NewId(), images.getNewBitmap(),
"New", "Long help for 'New'") #3 給工具欄增加一個工具
toolbar.Realize() #4 準備顯示工具欄
menuBar = wx.MenuBar() # 創建菜單欄
# 創建兩個菜單
menu1 = wx.Menu()
menuBar.Append(menu1, " ")
menu2 = wx.Menu()
#6 創建菜單的項目
menu2.Append(wx.NewId(), " ", "Copy in status bar")
menu2.Append(wx.NewId(), "C ", "")
menu2.Append(wx.NewId(), "Paste", "")
menu2.AppendSeparator()
menu2.Append(wx.NewId(), " ", "Display Options")
menuBar.Append(menu2, " ") # 在菜單欄上附上菜單
self.SetMenuBar(menuBar) # 在框架上附上菜單欄
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = ToolbarFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
```
**說明:**
* **#1**:這行創建了一個狀態欄,它是類`wx.StatusBar`的實例。它被放置在框架的底部,寬度與框架相同,高度由操作系統決定。狀態欄的目的是顯示在應用程序中被各種事件所設置的文本。
**#2**:創建了一個`wx.ToolBar`的實例,它是命令按鈕的容器。它被自動放置在框架的頂部
**#3**:有兩種方法來為你工具欄增加工具,這行使用了參數較少的一種:`AddSimpleTool()`。參數分別是`ID`,位圖,該工具的短的幫助提示文本,顯示在狀態欄中的該工具的長的幫助文本信息。(此刻不要考慮位圖從哪兒來)
**#4**:`Realize()`方法告訴工具欄這些工具按鈕應該被放置在哪兒。這是必須的。
**#6**:創建菜單的項目,其中參數分別代表`ID`,選項的文本,當鼠標位于其上時顯示在狀態欄的文本。
## 如何使用一般的對話框?
`wxPython`提供了一套豐富的預定義的對話框。這部分,我們將討論三種用對話框得到來自用戶的信息:
1、消息對話框 2、文本輸入對話框 3、從一個列表中選擇
在`wxPython`中有許多別的標準對話框,包括文件選擇器、色彩選擇器、進度對話框、打印設置和字體選擇器。這些將在第9章介紹。
**消息對話框**
與用戶通信最基本的機制是`wx.MessageDialog`,它是一個簡單的提示框。`wx.MessageDialog`可用作一個簡單的`OK`框或`yes`/`no`對話框。下面的片斷顯示了`yes`/`no`對話框:
```
dlg = wx.MessageDialog(None, 'Is this the coolest thing ever!',
'MessageDialog', wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
```
顯示結果如圖2.10所示:

`wx.MessageDialog`參數如下:
```
wx.MessageDialog(parent, message,
caption="Message box",
style=wx.OK | wx.CANCEL,
pos=wx.DefaultPosition)
```
**參數說明**:
`parent`:對話框的父窗口,如果對話框是頂級的則為`None`。 `message`:顯示在對話框中的字符串。 `caption`:顯示在對話框標題欄上的字符串。 `style`:對話框中按鈕的樣式。 `pos`:對話框出現的位置。
`ShowModal()`方法將對話框以模式框架的方式顯示,這意味著在對話框關閉之前,應用程序中的別的窗口不能響應用戶事件。`ShowModal()`方法的返回值是一個整數,對于`wx.MessageDialog`,返回值是下面常量之一: `wx.ID_YES`, `wx.ID_NO`, `wx.ID_CANCEL`, `wx.ID_OK`。
**文本輸入對話框**
如果你想從用戶那里得到單獨一行文本,你可能使用類`wx.TextEntryDialog`。下面的片斷創建了一個文本輸入域,當用戶單擊`OK`按鈕退出時,獲得用戶輸入的值:
```
dlg = wx.TextEntryDialog(None, "Who is buried in Grant's tomb?",
'A Question', 'Cary Grant')
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetValue()
```
圖2.11顯示了上面這個對話框:

上面的`wx.TextEntryDialog`的參數按順序說明是,父窗口,顯示在窗口中的文本標簽,窗口的標題(默認是“`Please` `enter` `text`”),輸入域中的默認值。同樣它也有一個樣式參數,默認是`wx.OK` | `wx.CANCEL`。與`wx.MessageDialog`一樣,`ShowModal()`方法返回所按下的按鈕的`ID`。`GetValue()`方法得到用戶輸入在文本域中的值(這有一個相應的`SetValue()`方法讓你可以改變文本域中的值)。
**從一個列表中選擇**
你可以讓用戶只能從你所提供的列表中選擇,你可以使用類`wx.SingleChoiceDialog`。下面是一個簡單的用法:
```
dlg = wx.SingleChoiceDialog(None,
'What version of Python are you using?',
'Single Choice',
['1.5.2', '2.0', '2.1.3', '2.2', '2.3.1'],
if dlg.ShowModal() == wx.ID_OK:
response = dlg.GetStringSelection()
```
圖2.12顯示了上面代碼片斷的結果。

`wx.SingleChoiceDialog`的參數類似于文本輸入對話框,只是以字符串的列表代替了默認的字符串文本。要得到所選擇的結果有兩種方法,`GetSelection()`方法返回用戶選項的索引,而`GetStringSelection()`返回實際所選的字符串。
## 一些最常見的錯誤現象及解決方法?
有一些錯誤它們可能會發生在你的`wxPython`應用程序對象或初始的頂級窗口在創建時,這些錯誤可能是很難診斷的。下面我們列出一些最常見的錯誤現象及解決方法:
**錯誤現象**: 程序啟動時提示“`unable` `to` `import` `module` `wx`。”
**原因**: `wxPython`模塊不在你的`PYTHONPATH`中。這意味著`wxPython`沒有被正確地安裝。如果你的系統上有多個版本的`Python`,`wxPython`可能安裝在了你沒有使用的`Python`版本中。
**解決方法**: 首先,確定你的系統上安裝了哪些版本的`Python`。在`Unix`系統上,使用`which` `python`命令將告訴你默認的安裝。在`Windows`系統上,如果`wxPython`被安裝到了相應的`Python`版本中,它將位于 `python`-`home` /`Lib`/`site`-`packages`子目錄下。然后重裝`wxPython`。
**錯誤現象**: 應用程序啟動時立即崩潰,或崩潰后出現一空白窗口。
**原因**: 在`wx.App`創建之前,創建或使用了一個`wxPython`對象。
**解決方法**: 在啟動你的腳本時立即創建`wx.App`對象。
**錯誤現象**: 頂級窗口被創建同時又立刻關閉。應用程序立即退出。
**原因**: 沒有調用`wx.App`的`MainLoop()`方法。
**解決方法**: 在你的所有設置完成后調用`MainLoop()`方法。
**錯誤現象**: 頂級窗口被創建同時又立刻關閉。應用程序立即退出。但我調用了`MainLoop()`方法。
**原因**: 你的應用程序的`OnInit()`方法中有錯誤,或`OnInit()`方法調用了某些方法(如幀的`__init__()`方法)。
**解決方法**: 在`MainLoop()`被調用之前出現錯誤的話,這將觸發一個異常且程序退出。如果你的應用程序設置了重定向輸出到窗口,那么那些窗口將一閃而過,你不能看到顯示在窗口中的錯誤信息。這種情況下,你要使用 `redirect`=`False`關閉重定向選項,以便看到錯誤提示。
## 總結
1. `wxPython`程序的實現基于兩個必要的對象:應用程序對象和頂級窗口。任何`wxPython`應用程序都需要去實例化一個`wx.App`,并且至少有一個頂級窗口。
2. 應用程序對象包含`OnInit()`方法,它在啟動時被調用。在這個方法中,通常要初始化框架和別的全局對象。`wxPython`應用程序通常在它的所有的頂級窗口被關閉或主事件循環退出時結束。
3. 應用程序對象也控制`wxPython`文本輸出的位置。默認情況下,`wxPython`重定向`stdout`和`stderr`到一個特定的窗口。這個行為使得診斷啟動時產生的錯誤變得困難了。但是我們可以通過讓`wxPython`把錯誤消息發送到一個文件或控制臺窗口來解決。
4. 一個`wxPython`應用程序通常至少有一個`wx.Frame`的子類。一個`wx.Frame`對象可以使用`style`參數來創建組合的樣式。每個`wxWidget`對象,包括框架,都有一個`ID`,這個`ID`可以被應用程序顯式地賦值或由`wxPython`生成。子窗口是框架的內容,框架是它的雙親。通常,一個框架包含一個單一的`wx.Panel`,更多的子窗口被放置在這個`Panel`中。框架的唯一的子窗口的尺寸自動隨其父框架的尺寸的改變而改變。框架有明確的關于管理菜單欄、工具欄和狀態欄的機制。
5. 盡管你將使用框架做任何復雜的事情,但當你想簡單而快速地得到來自用戶的信息時,你可以給用戶顯示一個標準的對話窗口。對于很多任務都有標準的對話框,包括警告框、簡單的文本輸入框和列表選擇框等等。
- 活學活用wxPython
- 前言
- 致謝
- 關于本書
- 第一部分
- 第一章 歡迎使用wxPython
- 第二章 給wxPython程序一個堅實的基礎
- 第三章 在事件驅動環境中開發
- 第四章 用PyCrust使得wxPython更易處理
- 第五章 繪制藍圖
- 第六章 使用wxPython基本構件
- 第二部分 基礎wxPython
- 第七章 使用基礎控件
- 第八章 將構件放入窗體中
- 第九章 通過對話框讓用戶選擇
- 第十章 創建和使用wxPython菜單
- 第十一章 使用sizer放置構件
- 第十二章 操作基本圖像
- 第三部分 高級wxPython
- 第十三章 建造列表控件并管理列表項
- 第十四章 網格控件
- 第十五章 樹形控件
- 第十六章 在應用程序中加入HTML
- 第十七章 wxPython的打印構架
- 第十八章 使用wxPython的其他功能