<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 第六章 使用wxPython基本構件 1. [使用基本的建造部件](#A.2BT391KFf6Zyx2hF76kCCQ6E72-) 1. [在屏幕上繪畫](#A.2BVyhcT15VTgp.2B2HU7-) 1. [如何在屏幕上繪畫](#A.2BWYJPVVcoXE9eVU4Kfth1Ow-) 2. [添加窗口裝飾](#A.2BbftSoHqXU.2BOIxZlw-) 1. [如何添加和更新一個狀態欄](#A.2BWYJPVW37UqBUjGb0ZbBOAE4qcrZgAWgP-) 2. [如何添加菜單?](#A.2BWYJPVW37UqCD3FNV.2Fx8-) 3. [如何添加一個工具欄](#A.2BWYJPVW37UqBOAE4qXeVRd2gP-) 3. [得到標準信息](#A.2BX5dSMGgHUcZP4WBv-) 1. [如何使用標準文件對話框?](#A.2BWYJPVU9.2FdShoB1HGZYdO9lv5i91oRv8f-) 2. [如何使用標準的顏色選擇器?](#A.2BWYJPVU9.2FdShoB1HGdoSYnIJykAli6VZo.2Fx8-) 4. [給應用程序一個好看的外觀](#A.2BftlelHUoegtej04ATipZfXcLdoRZFonC-) 1. [如何布局窗口部件?](#A.2BWYJPVV4DXEB6l1PjkOhO9v8f-) 2. [如何建造一個關于(about)框?](#A.2BWYJPVV76kCBOAE4qUXNOjg.28about.29.2BaEb.2FHw-) 3. [如何建造一個啟動畫面?](#A.2BWYJPVV76kCBOAE4qVC9SqHU7l2L.2FHw-) 5. [本章小結](#A.2BZyx64FwPftM-) 即使是一個簡單的`wxPython`程序也需要使用標準的元素,諸如菜單和對話框。這兒有對于任一`GUI`應用程序的基本的建造部件。使用這些建造部件,還有像啟動畫面、狀態欄或關于框等這些窗口部件,它們給你提供了一個更友好的用戶環境,并且給了你的應用程序一個專業的感觀。為了要結束本書的第一部分,我們將指導你通過一個程序的創建來使用所有所學的部分。我們將建造一個簡單的繪畫程序,然后添加這些建造部件元素并說明使用它們時的一些問題。我們將鞏固前面章節的基本原理和概念,并且最后我們將產生這個簡單但專業的應用程序。本章位于先前基本概念章節和后面對`wxPython`功能更詳細討論的2、3部分之間。 我們在本章將建造的這個應用程序基本上基于`wxPython`/`samples`目錄中的涂鴉例子。這個應用程序是一個非常簡單的繪畫程序,當鼠標左鍵按下時它跟蹤鼠標指針,并畫線。圖6.1顯示了一個簡單的初始繪畫窗口。 圖6.1 ![](https://box.kancloud.cn/2016-08-21_57b9960a220e6.gif) 我們之所以要選擇這樣一個繪畫例子,是因為它是十分簡單的程序,它演示了在創建更復雜的應用程序時所引出的許多問題。在本章,我們將給你展示如何在屏幕上畫線、添加狀態欄、工具樣以及菜單欄。你將會看到如何使用通用對話框,如文件選擇器和顏色選擇器。我們將使用`sizer`來布置窗口部件,并且我們也將增加一個關于框和一個啟動畫面。本章的最后,你將有一個很好看的繪畫程序。 ## 在屏幕上繪畫 你的繪畫程序的首先的工作是勾畫線條并顯示出來。像其它的`GUI`工具一樣, `wxPython`提供了一套獨立于設備的工具用于繪畫。下面,我們將討論如何在屏幕上繪畫。 ### 如何在屏幕上繪畫 要在屏幕上繪畫,我們要用到一個名為`device` `context`(設備上下文)的`wxPython`對象。設備上下文代表抽象的設備,它對于所有的設備有一套公用的繪畫方法,所以對于不同的設備,你的代碼是相同的,而不用考慮你所在的具體設備。設備上下文使用抽象的`wxPython`的類`wx.DC`和其子類來代表。由于`wx.DC`是抽象的,所以對于你的應用程序,你需要使用它的子類。 **使用設備上下文** 表6.1顯示了`wx.DC`的子類及其用法。設備上下文用來在`wxPython`窗口部件上繪畫,它應該是局部的,臨時性的,不應該以實例變量、全局變量或其它形式在方法調用之間保留。在某些平臺上,設備上下文是有限的資源,長期持有`wx.DC`可能導致你的程序不穩定。由于`wxPython`內部使用設備上下文的方式,對于在窗口部件中繪畫,就存在幾個有著細微差別的`wx.DC`的子類。第十二章將更詳細地說明這些差別。 **表6.1** `wx.BufferedDC`:用于緩存一套繪畫命令,直到命令完整并準備在屏幕上繪畫。這防止了顯示中不必要的閃爍。 `wx.BufferedPaintDC`:和`wx.BufferedDC`一樣,但是只能用在一個`wx.PaintEvent`的處理中。僅臨時創建該類的實例。 `wx.ClientDC`:用于在一個窗口對象上繪畫。當你想在窗口部件的主區域上(不包括邊框或別的裝飾)繪畫時使用它。主區域有時也稱為客戶區。`wx.ClientDC`類也應臨時創建。該類僅適用于`wx.PaintEvent`的處理之外。 `wx.MemoryDC`:用于繪制圖形到內存中的一個位圖中,此時不被顯示。然后你可以選擇該位圖,并使用`wx.DC.Blit()`方法來把這個位圖繪畫到一個窗口中。 `wx.MetafileDC`:在`Windows`操作系統上,`wx.MetafileDC`使你能夠去創建標準窗口圖元文件數據。 `wx.PaintDC`:等同于`wx.ClientDC`,除了它僅用于一個`wx.PaintEvent`的處理中。僅臨時創建該類的實例。 `wx.PostScriptDC`:用于寫壓縮的`PostScript`文件。 `wx.PrinterDC`:用于`Windows`操作系統上,寫到打印機。 `wx.ScreenDC`:用于直接在屏幕上繪畫,在任何被顯示的窗口的頂部或外部。該類只應該被臨時創建。 `wx.WindowDC`:用于在一個窗口對象的整個區域上繪畫,包括邊框以及那些沒有被包括在客戶區域中的裝飾。非`Windows`系統可能不支持該類。 下例6.1包含了顯示圖6.1的代碼。因為該代碼展示了基于設備上下文繪畫的技巧,所以我們將對其詳細注釋。 例6.1 初始的`SketchWindow`代碼 ``` import wx class SketchWindow(wx.Window): def __init__(self, parent, ID): wx.Window.__init__(self, parent, ID) self.SetBackgroundColour("White") self.color = "Black" self.thickness = 1 self.pen = wx.Pen(self.color, self.thickness, wx.SOLID)#1 創建一個wx.Pen對象 self.lines = [] self.curLine = [] self.pos = (0, 0) self.InitBuffer() #2 連接事件 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_PAINT, self.OnPaint) def InitBuffer(self): size = self.GetClientSize() #3 創建一個緩存的設備上下文 self.buffer = wx.EmptyBitmap(size.width, size.height) dc = wx.BufferedDC(None, self.buffer) #4 使用設備上下文 dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() self.DrawLines(dc) self.reInitBuffer = False def GetLinesData(self): return self.lines[:] def SetLinesData(self, lines): self.lines = lines[:] self.InitBuffer() self.Refresh() def OnLeftDown(self, event): self.curLine = [] self.pos = event.GetPositionTuple()#5 得到鼠標的位置 self.CaptureMouse()#6 捕獲鼠標 def OnLeftUp(self, event): if self.HasCapture(): self.lines.append((self.color, self.thickness, self.curLine)) self.curLine = [] self.ReleaseMouse()#7 釋放鼠標 def OnMotion(self, event): if event.Dragging() and event.LeftIsDown():#8 確定是否在拖動 dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)#9 創建另一個緩存的上下文 self.drawMotion(dc, event) event.Skip() #10 繪畫到設備上下文 def drawMotion(self, dc, event): dc.SetPen(self.pen) newPos = event.GetPositionTuple() coords = self.pos + newPos self.curLine.append(coords) dc.DrawLine(*coords) self.pos = newPos def OnSize(self, event): self.reInitBuffer = True #11 處理一個resize事件 def OnIdle(self, event):#12 空閑時的處理 if self.reInitBuffer: self.InitBuffer() self.Refresh(False) def OnPaint(self, event): dc = wx.BufferedPaintDC(self, self.buffer)#13 處理一個paint(描繪)請求 #14 繪制所有的線條 def DrawLines(self, dc): for colour, thickness, line in self.lines: pen = wx.Pen(colour, thickness, wx.SOLID) dc.SetPen(pen) for coords in line: dc.DrawLine(*coords) def SetColor(self, color): self.color = color self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) def SetThickness(self, num): self.thickness = num self.pen = wx.Pen(self.color, self.thickness, wx.SOLID) class SketchFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Sketch Frame", size=(800,600)) self.sketch = SketchWindow(self, -1) if __name__ == '__main__': app = wx.PySimpleApp() frame = SketchFrame(None) frame.Show(True) app.MainLoop() ``` **說明**: **#1**:`wx.Pen`實例決定繪畫到設備上下文的線條的顏色、粗細和樣式。樣式除了`wx.SOLID`還有`wx.DOT`, `wx.LONGDASH`, 和`wx.SHORTDASH`。 **#2**:窗口需要去響應幾個不同的鼠標類型事件以便繪制圖形。響應的事件有鼠標左鍵按下和釋放、鼠標移動、窗口大小變化和窗口重繪。這里也指定了空閑時的處理。 **#3**:用兩步創建了緩存的設備上下文:(1)創建空的位圖,它作為畫面外(`offscreen)`的緩存(2)使用畫面外的緩存創建一個緩存的設備上下文。這個緩存的上下文用于防止我勾畫線的重繪所引起的屏幕閃爍。在這節的較后面的部分,我們將更詳細地討論這個緩存的設備上下文。 **#4**:這幾行發出繪制命令到設備上下文;具體就是,設置背景色并清空設備上下文(`dc.Clear())`。必須調用`dc.Clear()`,其作用是產生一個`wx.EVT_PAINT`事件,這樣,設置的背景就顯示出來了,否則屏幕顏色不會改變。`wx.Brush`對象決定了背景的顏色和樣式。 **#5**:事件方法`GetPositionTuple()`返回一個包含鼠標敲擊的精確位置的`Python`元組。 **#6**:`CaptureMouse()`方法控制了鼠標并在窗口的內部捕獲鼠標,即使是你拖動鼠標到窗口邊框的外面,它仍然只響應窗口內的鼠標動作。在程序的后面必須調用`ReleaseMouse()`來取消其對鼠標的控制。否則該窗口將無法通過鼠標關閉等,試將#7注釋掉。 **#7**:`ReleaseMouse()`方法將系統返回到調用`CaptureMouse()`之前的狀態。`wxPython`應用程序使用一個椎棧來對捕獲了鼠標的窗口的跟蹤,調用`ReleaseMouse()`相當于從椎棧中彈出。這意味著你需要調用相同數據的`CaptureMouse()`和`ReleaseMouse()`。 **#8**:這行確定移動事件是否是線條繪制的一部分,由移動事件發生時鼠標左鍵是否處于按下狀態來確定。`Dragging()`和`LeftIsDown()`都是`wx.MouseEvent`的方法,如果移動事件發生時所關聯的條件成立,方法返回`true`。 **#9**:由于`wx.BufferedDC`是一個臨時創建的設備上下文,所以在我們繪制線條之前需要另外創建一個。這里,我們創建一個新的`wx.ClientDC`作為主要的設備上下文,并再次使用我們的實例變量位圖作為緩存。 **#10**:這幾行實際是使用設備上下文去繪畫新近的勾畫線到屏幕上。首先,我們創建了`coords`元組,它合并了`self.pos`和`newPos`元組。這里,新的位置來自于事件`GetPositionTuple()`,老的位置是最后對`OnMotion()`調用所得到的。我們把該元組保存到`self.curLine`列表中,然后調用`DrawLine()`。*`coords`返回元組`coords`中的元素`x1`,`y1`,`x2`,`y2`。`DrawLine()`方法要求的參數形如`x1`,`y1`,`x2`,`y2`,并從點(`x1`,`y1)`到(`x2`,`y2)`繪制一條線。勾畫的速度依賴于底層系統的速度。 **#11**:如果窗口大小改變了,我們存儲一個`True`值到`self.reInitBuffer`實例屬性中。我們實際上不做任何事直到下一個空閑事件。 **#12**:當一個空閑產生時,如果已發生了一個或多個尺寸改變事件,這個應用程序抓住時機去響應一個尺寸改變事件。我們存儲一個`True`值到`self.reInitBuffer`實例屬性中,并在一個空閑產生時響應的動機是避免對于接二連三的尺寸改變事件都進行屏幕刷新。 **#13**:對于所有的顯示要求,都將產生`wx.EVT_PAINT`事件(描繪事件),并調用我們這里的方法`OnPaint`進行屏幕刷新(重繪),你可以看到這是出乎意料的簡單:創建一個緩存的畫圖設備上下文。實際上`wx.PaintDC`被創建(因為我們處在一個`Paint`請求里,所以我們需要`wx.PaintDC`而非一個`wx.ClientDC`實例),然后在`dc`實例被刪除后(函數返回時被銷毀),位圖被一塊塊地傳送(`blit)`給屏幕并最終顯示。關于緩存的更詳細的信息將在隨后的段落中提供。 **#14**:當由于尺寸改變(和由于從文件載入)而導致應用程序需要根據實際數據重繪線條時,被使用。這里,我們遍歷存儲在實例變量`self.lines`中行的列表,為每行重新創建畫筆,然后根據坐標繪制每一條線。 這個例子使用了兩個特殊的`wx.DC`的子類,以使用繪畫緩存。一個繪畫緩存是一個不顯現的區域,其中存儲了所有的繪畫命令(這些命令能夠一次被執行),并且一步到位地復制到屏幕上。緩存的好處是用戶看不到單個繪畫命令的發生,因此屏幕不會閃爍。正因如此,緩存被普遍地用于動畫或繪制是由一些小的部分組成的場合。 在`wxPython`中,有兩個用于緩存的類:`wx.BufferDC`(通常用于緩存一個`wx.ClientDC`)、`wx.BufferPaintDC`(用于緩存一個`wx.PaintDC`)。它們工作方式基本上一樣。緩存設備上下文的創建要使用兩個參數。第一個是適當類型的目標設備上下文(例如,在例6.1中的#9,它是一個新的`wx.ClientDC`實例)。第二個是一個`wx.Bitmap`對象。在例6.1中,我們使用函數`wx.EmptyBitmap`創建一個位圖。當繪畫命令到緩存的設備上下文時,一個內在的`wx.MemoryDC`被用于位圖繪制。當緩存對象被銷毀時,C++銷毀器使用`Blit()`方法去自動復制位圖到目標。在`wxPython`中,銷毀通常發生在對象退出作用域時。這意味緩存的設備上下文僅在臨時創建時有用,所以它們能夠被銷毀并能用于塊傳送(`blit)`。 例如例6.1的`OnPaint`()方法中,`self.buffer`位圖在建造勾畫(`sketch`)期間已經被寫了。只需要創建緩存對象,從而建立關于窗口的已有的位圖與臨時`wx.PaintDC()`之間的連接。方法結束后,緩存`DC`立即退出作用域,觸發它的銷毀器,同時將位圖復制到屏幕。 **設備上下文的函數** 當你使用設備上下文時,要記住根據你的繪制類型去使用恰當的上下文(特別要記住`wx.PaintDC`和 `wx.ClientDC`的區別)。一旦你有了適當的設備上下文,然后你就可以用它們來做一些事情了。表6.2列出 了`wx.DC`的一些方法。 **表6.2** **`wx.DC`的常用方法** `Blit(xdest`, `ydest`, `width`,`height`, `source`, `xsrc`,`ysrc)`:從源設備上下文復制塊到調用該方法的設備上下文。參數`xdest`, `ydest`是復制到目標上下文的起始點。接下來的兩個參數指定了要復制的區域的寬度和高度。`source`是源設備上下文,`xsrc`,`ysrc`是源設備上下文中開始復制的起點。還有一些可選的參數來指定邏輯疊加功能和掩碼。 `Clear()`:通過使用當前的背景刷來清除設備上下文。 `DrawArc(x1`, `y1`, `x2`, `y2`,`xc`, `yc)`:使用起點(`x1`, `y1)`和終點(`x2`, `y2)`畫一個圓弧。(`xc`, `yc)`是圓弧的中心。圓弧使用當前的畫刷填充。這個函數按逆時針畫。這也有一個相關的方法`DrawEllipticalArc()`。 `DrawBitmap(bitmap`, x,y, `transparent)`:繪制一個`wx.Bitmap`對象,起點為(x, `y)`。如果`transparent`為真,所復制的位圖將是透明的。 `DrawCircle(x`, y, `radius)` `DrawCircle(point`, `radius)`:按給定的中心點和半徑畫圓。這也有一個相關的方法`DrawEllipse`。 `DrawIcon(icon`, x, `y)`:繪制一個`wx.Icon`對象到上下文,起點是(x, `y)`。 `DrawLine(x1`, `y1`, `x2`, `y2)`:從點(`x1`, `y1)`到(`x2`, `y2)`畫一條線。這有一個相關的方法`DrawLines()`,該方法要`wx.Point`對象的一個`Python`列表為參數,并將其中的點連接起來。 `DrawPolygon(points)`:按給定的`wx.Point`對象的一個`Python`列表繪制一個多邊形。與`DrawLines()`不同的是,它的終點和起點相連。多邊形使用當前的畫刷來填充。這有一些可選的參數來設置x和y的偏移以及填充樣式。 `DrawRectangle(x`, y,`width`, `height)`:繪制一個矩形,它的左上角是(x, `y)`,其寬和高是`width`和`height` 。 `DrawText(text`, x, `y)`:從點(x, `y)`開始繪制給定的字符串,使用當前的字體。相關函數包括`DrawRotatedText()`和`GetTextExtent()`。文本項有前景色和背景色屬性。 `FloodFill(x`, y, `color`,`style)`:從點(x, `y)`執行一個區域填充,使用當前畫刷的顏色。參數`style`是可選的。`style`的默認值是`wx.FLOOD_SURFACE`,它表示當填充碰到另一顏色時停止。另一值`wx.FLOOD_BORDER`表示參數`color`是填充的邊界,當填充碰到該顏色的代表的邊界時停止。 `GetBackground()` `SetBackground(brush)`:背景畫刷是一個`wx.Brush`對象,當`Clear()`方法被調用時使用。 `GetBrush()` `SetBrush(brush)`:畫刷是一個`wx.Brush`對象并且用于填充任何繪制在設備上下文上的形狀。 `GetFont()` `SetFont(font)`:字體(`font)`是一個`wx.Font`對象,被用于所有的文本繪制操作。 `GetPen()` `SetPen(pen)`:畫筆(`pen)`是一個`wx.Pen`對象,被用于所有繪制線條的操作。 `GetPixel(x`, `y)`:返回一個關于點(x, `y)`的像素的一個`wx.Colour`對象。 `GetSize()` `GetSizeTuple()`:以一個`wx.Size`對象或一個`Python`元組的形式返回設備上下文的像素尺寸。 上面的列表并沒有囊括所有的方法。另外的一些方法將在第十二章中說明。 ## 添加窗口裝飾 盡管繪制到屏幕是一個畫圖程序不可或缺的部分,但是它距美觀的程序還差的遠。在這一節,我們將談及常用的窗口裝飾:狀態欄、菜單和工具欄。我們將在第10章對這些做更詳細的討論。 ### 如何添加和更新一個狀態欄 在`wxPython`中,你可以通過調用框架的`CreateStatusBar()`方法添加并放置一個狀態欄到一個框架的底部。當父框架調整大小的時候,狀態欄自動的自我調整大小。默認情況下,狀態欄是類`wx.StatusBar`的一個實例。要創建一個自定義的狀態欄,要使用`SetStatusBar()`方法并要求你的新類的實例作為參數來將狀態欄附著到你的框架上。 要在你的狀態欄上顯示單一的一段文本,你可以使用`wx.StatusBar`的`SetStatusText()`方法。例6.2擴展了在例6.1中所演示的`SketchFrame`類來在狀態欄中顯示當前鼠標的位置。 例6.2 給框架添加一個簡單的狀態欄 ``` # python import wx from example1 import SketchWindow class SketchFrame(wx.Frame): def init(self, parent): wx.Frame.init(self, parent, -1, "Sketch Frame", size=(800,600)) self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.statusbar = self.CreateStatusBar() def OnSketchMotion(self, event): self.statusbar.SetStatusText(str(event.GetPositionTuple())) event.Skip() if name == 'main': app = wx.PySimpleApp() frame = SketchFrame(None) frame.Show(True) app.MainLoop() ``` 我們通過使框架捕捉勾畫窗的`wx.EVT_MOTION`事件來在狀態欄中顯示鼠標位置。事件處理器使用由該事件提供的數據設置狀態欄的文?盡H緩蟮饔脅方法來保證另外的`OnMotion()`方法被調用,否則線條將不被繪制。 如果你想在狀態欄中顯示多個文本元素,你可以在狀態欄中創建多個文本域。要使用這個功能,你要調用`SetFieldsCount()`方法,其參數是域的數量;默認情況下只有我們先前所見的那一個域。這之后使用先前的`SetStatusText()`,但是要使用第二個參數來指定此方法所應的域。域的編號從0開始。如果你不指定一個域,那么默認為設置第0號域,這也說明了為什么我們沒有指定域而先前的例子能工作。 默認情況下,每個域的寬度是相同的。要調整文本域的尺寸,`wxPython`提供了`SetStatusWidth()`方法。該方法要求一個整數的`Python`列表作為參數,列表的長度必須和狀態欄中哉的數量一致。按列表中整數的順序來計算對應域的寬度。如果整數是正值,那么寬度是固定的。如果你想域的寬度隨框架的變化而變化,那么應該使用負值。負值的絕對值代表域的相對寬度;可以把它認為是所占總寬度的比例。例如調用`statusbar.SetStatusWidth(`[-1, -2,-3])方法所導致的各域從左到右的寬度比例是1:2:3。圖6.2顯示了這個結果。 圖6.2 ![](https://box.kancloud.cn/2016-08-21_57b9960a343fe.gif) 例子6.3增加了兩個狀態域,其中一個顯示所繪的當前線條的點數,另一個顯示當前所畫的線條的數量。該例所產生的狀態條如圖6.2所示。 例6.3 支持多個狀態域 ``` import wx from example1 import SketchWindow class SketchFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Sketch Frame", size=(800,600)) self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount(3) self.statusbar.SetStatusWidths([-1, -2, -3]) def OnSketchMotion(self, event): self.statusbar.SetStatusText("Pos: %s" % str(event.GetPositionTuple()), 0) self.statusbar.SetStatusText("Current Pts: %s" % len(self.sketch.curLine), 1) self.statusbar.SetStatusText("Line Count: %s" % len(self.sketch.lines), 2) event.Skip() if __name__ == '__main__': app = wx.PySimpleApp() frame = SketchFrame(None) frame.Show(True) app.MainLoop() ``` `StatusBar`類使你能夠把狀態域當作一個后進先出的堆棧。盡管本章的演示程序沒有這樣用,`PushStatus`- `Text()`和`PopStatusText()`使得你能夠在臨時顯示新的文本之后返回先前的狀態文本。這兩個方法都有一個可選的域號參數,以便在多個狀態域的情況下使用。 表6.3歸納了`wx.StatusBar`最常用的方法 **表6.3** **`wx.StatusBar`的方法** `GetFieldsCount()` `SetFieldsCount(count)`:得到或設置狀態欄中域的數量。 `GetStatusText(field`=0) `SetStatusText(text`, `field`=0):得到或設置指定域中的文本。0是默認值,代表最左端的域。 `PopStatusText(field`=0):彈出堆棧中的文本到指定域中,以改變域中的文本為彈出值。 `PushStatusText(text`, `field`=0):改變指定的域中的文本為給定的文本,并將改變前的文本壓入堆棧的頂部。 `SetStatusWidths(widths)`:指定各狀態域的寬度。`widths`是一個整數的`Python`列表。 在第10章中,我們將對狀態欄作更詳細的說明。下面我們將討論菜單。 ### 如何添加菜單? 本節,我們將說明如何添加子菜單和復選或單選菜單。子菜單是頂級菜單中的菜單。復制菜單或單選菜單是一組菜單項,它們的行為類似于一組復選框或單選按鈕。圖6.3顯示了一個菜單欄,其中的一個子菜單包含了單選菜單項。 圖6.3 ![](https://box.kancloud.cn/2016-08-21_57b9960a476bd.gif) 要創建一個子菜單,首先和創建別的菜單方法一樣創建一個菜單,然后再使用`wx.Menu.AppendMenu()`將它添加給父菜單。 帶有復選或單選菜單的菜單可以通過使用`wx.Menu`的`AppendCheckItem()`和`AppendRadioItem()`方法來創建,或通過在`wx.MenuItem`的創建器中使參數`kind`的屬性值為下列之一來創建:`wx.ITEM_NORMAL`, `wx.ITEM_CHECKBOX`, 或 `wx.ITEM_RADIO`。要使用編程的方法來選擇一個菜單項,可以使`wx.Menu`的`Check(id`,`bool)`方法,`id`是所要改變項的`wxPython` `ID`,`bool`指定了該項的選擇狀態。 例6.4為我們初始的繪畫程序添加了菜單支持。我們這里的菜單改進自例5.5中的被重構的公用程序代碼。 **例子6.4** ``` import wx from example1 import SketchWindow class SketchFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Sketch Frame", size=(800,600)) self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.initStatusBar() #1 這里因重構有點變化 self.createMenuBar() def initStatusBar(self): self.statusbar = self.CreateStatusBar() self.statusbar.SetFieldsCount(3) self.statusbar.SetStatusWidths([-1, -2, -3]) def OnSketchMotion(self, event): self.statusbar.SetStatusText("Pos: %s" % str(event.GetPositionTuple()), 0) self.statusbar.SetStatusText("Current Pts: %s" % len(self.sketch.curLine), 1) self.statusbar.SetStatusText("Line Count: %s" % len(self.sketch.lines), 2) event.Skip() def menuData(self): #2 菜單數據 return [(" ", ( (" ", "New Sketch file", self.OnNew), (" ", "Open sketch file", self.OnOpen), (" ", "Save sketch file", self.OnSave), ("", "", ""), (" ", ( (" ", "", self.OnColor, wx.ITEM_RADIO), (" ", "", self.OnColor, wx.ITEM_RADIO), (" ", "", self.OnColor, wx.ITEM_RADIO), (" ", "", self.OnColor, wx.ITEM_RADIO))), ("", "", ""), (" ", "Quit", self.OnCloseWindow)))] def createMenuBar(self): menuBar = wx.MenuBar() for eachMenuData in self.menuData(): menuLabel = eachMenuData[0] menuItems = eachMenuData[1] menuBar.Append(self.createMenu(menuItems), menuLabel) self.SetMenuBar(menuBar) def createMenu(self, menuData): menu = wx.Menu() #3 創建子菜單 for eachItem in menuData: if len(eachItem) == 2: label = eachItem[0] subMenu = self.createMenu(eachItem[1]) menu.AppendMenu(wx.NewId(), label, subMenu) else: self.createMenuItem(menu, *eachItem) return menu def createMenuItem(self, menu, label, status, handler, kind=wx.ITEM_NORMAL): if not label: menu.AppendSeparator() return menuItem = menu.Append(-1, label, status, kind)#4 使用kind創建菜單項 self.Bind(wx.EVT_MENU, handler, menuItem) def OnNew(self, event): pass def OnOpen(self, event): pass def OnSave(self, event): pass def OnColor(self, event):#5 處理顏色的改變 menubar = self.GetMenuBar() itemId = event.GetId() item = menubar.FindItemById(itemId) color = item.GetLabel() self.sketch.SetColor(color) def OnCloseWindow(self, event): self.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = SketchFrame(None) frame.Show(True) app.MainLoop() ``` **說明**: **#1**:現在`__init__`方法包含了更多的功能,我們把狀態欄放在了它自己的方法中。 **#2**:菜單數據的格式現在是(標簽, (項目)),其中的每個頂目也是一個列表(標簽, 描術文字, 處理器, 可選的`kind)`或一個帶有標簽和項目的菜單。確定數據的一個子項目是菜單還是一個菜單項,請記住,菜單的長度是2,項目的長度是3或4。對于更復雜的產品數據,我建議使用`XML`或別的外部格式。 **#3**:如果數據塊的長度是2,這意味它是一個菜單,將之分開,并遞歸調用`createMenu`,然后將之添加。 **#4**:創建菜單項。對`wx.MenuItem`的構造器使用`kind`參數的方法比使用`wx.Menu`的特定方法更容易。 **#5**:`OnColor`方法根據所選菜單項來改變畫筆的顏色。代碼根據事件得到項目的`id`,再使用`FindItemById()`來得到正確的菜單項(注意我們這里使用菜單欄作為數據結構來訪問,而沒有使用項目`id`的哈希表),這個方法是以標簽是`wxPython`顏色名為前提的。 ### 如何添加一個工具欄 菜單欄和工具欄通常是緊密聯系在一起的,工具欄的絕大部分功能與菜單項相對應。在`wxPython`中,這通過工具欄被敲擊時發出`wx.EVT_MENU`事件,這樣就可很容易地在處理菜單項的選擇和工具欄的敲擊時使用相同的方法。一個`wxPython`工具欄是類`wx.ToolBar`的一個實例,正如我們在第二章中所見的,可以使用框架的方法`CreateToolBar()`來創建。和狀態欄一樣,工具欄的大小也隨其父框架的改變而自動改變。工具欄與其它的`wxPython`窗口一樣可以擁有任意的子窗口。工具欄也包含創建工具按鈕的方法。圖6.4顯示了帶有一個工具欄的`sketch`窗口的一部分,這個工具欄使用了6.2.2中菜單的相應方法,以與菜單項的功能相對應。 圖6.4 ![](https://box.kancloud.cn/2016-08-21_57b9960a5bfd6.gif) 例6.5中,我們沒有重復使用菜單改變畫筆顏色的代碼,而是使用了新的方法。 **例6.5** **添加一個工具欄到`sketch`應用程序** ``` def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Sketch Frame",size=(800,600)) self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.initStatusBar() self.createMenuBar() self.createToolBar() def createToolBar(self):#1創建工具欄 toolbar = self.CreateToolBar() for each in self.toolbarData(): self.createSimpleTool(toolbar, *each) toolbar.AddSeparator() for each in self.toolbarColorData(): self.createColorTool(toolbar, each) toolbar.Realize()#2 顯現工具欄 def createSimpleTool(self, toolbar, label, filename,help, handler):#3 創建常規工具 if not label: toolbar.AddSeparator() return bmp = wx.Image(filename,wx.BITMAP_TYPE_BMP).ConvertToBitmap() tool = toolbar.AddSimpleTool(-1, bmp, label, help) self.Bind(wx.EVT_MENU, handler, tool) def toolbarData(self): return (("New", "new.bmp", "Create new sketch",self.OnNew), ("", "", "", ""), ("Open", "open.bmp", "Open existing sketch",self.OnOpen), ("Save", "save.bmp", "Save existing sketch",self.OnSave)) def createColorTool(self, toolbar, color):#4 創建顏色工具 bmp = self.MakeBitmap(color) newId = wx.NewId() tool = toolbar.AddRadioTool(-1, bmp, shortHelp=color) self.Bind(wx.EVT_MENU, self.OnColor, tool) def MakeBitmap(self, color):#5 創建純色的位圖 bmp = wx.EmptyBitmap(16, 15) dc = wx.MemoryDC() dc.SelectObject(bmp) dc.SetBackground(wx.Brush(color)) dc.Clear() dc.SelectObject(wx.NullBitmap) return bmp def toolbarColorData(self): return ("Black", "Red", "Green", "Blue") def OnColor(self, event):#6 改變畫筆顏色以響應工具欄的敲擊 menubar = self.GetMenuBar() itemId = event.GetId() item = menubar.FindItemById(itemId) if not item: toolbar = self.GetToolBar() item = toolbar.FindById(itemId) color = item.GetShortHelp() else: color = item.GetLabel() self.sketch.SetColor(color) ``` **#1**:工具欄的代碼在設置上類似于菜單代碼。然而,這里,我們對常規的按鈕和單選切換按鈕使用了不同的循環設置。 **#2**:`Realize()`方法實際上是在工具欄中布局工具欄對象。它在工具欄被顯示前必須被調用,如果工具欄中的添加或刪除了工具,那么這個方法也必須被調用。 **#3**:這個方法類似于菜單項的創建。主要區別是工具欄上的工具要求顯示位圖。這里我們使用了三個位于同一目錄下基本位圖。在該方法的最后,我們綁定了菜單項所使用的相同的`wx.EVT_MENU`事件。 **#4**:顏色工具的創建類似于常規的工具。唯一的不同是使用了一個不同的方法去告訴工具欄它們是單選工具。純色的位圖由`MakeBitmap()`方法創建。 **#5**:該方法為單選工具創建純色的位圖。 **#6**:該方法在原有的基礎上添加了搜索正確的工具以具此來改變顏色。然而,所寫的代碼的問題是,通過菜單項使畫筆顏色改變了,但是工具欄上的單選工具的狀態沒有相應改變,反過來也是一樣。 工具欄中的工具在鼠標右鍵敲擊時能夠產生`wx.EVT_TOOL_RCLICKED`類型事件。工具欄也有一些不同的樣式,它們被作為位圖參數傳遞給`CreateToolBar()`。表6.4列出了一些工具欄的樣式。 **表6.4** **`wx.ToolBar`類的樣式** `wx.TB_3DBUTTONS`:3D外觀 `wx.TB_HORIZONTAL`:默認樣式,工具欄水平布置 `wx.TB_NOICONS`:不為每個工具顯示位圖 `wx.TB_TEXT`:根據不同的位圖顯示簡短的幫助文本 `wx.TB_VERTICAL`:垂直放置工具欄 工具欄比狀態欄更復雜。表6.5顯示了其常用的一些方法。 **表6.5** **`wx.ToolBar`的常用方法** `AddControl(control)`:添加一個任意的`wxPython`控件到工具欄。相關方法`InsertControl()`。 `AddSeparator()`:在工具之間放置空格。 `AddSimpleTool(id`, `bitmap`,`shortHelpString`="",`kind`=`wx.ITEM_NORMAL)`:添加一個簡單的帶有給定位圖的工具到工具欄。`shortHelpString`作為提示顯示。`kind`的值可以是`wx.ITEM_NORMAL`, `wx.ITEM_CHECKBOX`, 或`wx.ITEM_RADIO`。 `AddTool(id`, `bitmap`,`bitmap2`=`wx.NullBitmap`,`kind`=`wx.ITEM_NORMAL`,`shortHelpString`="",`longHelpString`="", `clientData`=`None)`:簡單工具的其它參數。`bitmap2`是當該工具被按下時所顯示的位圖。`longHelpString`是當指針位于該工具中時顯示在狀態欄中的幫助字符串。`clientData`用于將任意的一塊數據與工具相聯系起來。相關方法`InsertTool()`。 `AddCheckTool(...)`:添加一個復選框切換工具,所要求參數同`AddTool()`。 `AddRadioTool(...)`:添加一個單選切換工具,所要求參數同`AddTool()`。對于連續的未分隔的單選工具被視為一組。 `DeleteTool(toolId)` `DeleteToolByPosition(x`, `y)`:刪除所給定的`id`的工具,或刪除給定顯示位置的工具。 `FindControl(toolId)` `FindToolForPosition(x`, `y)`:查找并返回給定`id`或顯示位置的工具。 `ToggleTool(toolId`, `toggle)`:根據布爾什`toggle`來設置給定`id`的工具的狀態。 下一節,我們將給你展示如何使用通用對話框來得到用戶的信息。 ## 得到標準信息 你的應用程序經常需要從用戶那里得到基本的信息,這通常通過對話框。在這一節,我們將討論針對標準用戶信息的標準文件和顏色對話框。 ### 如何使用標準文件對話框? 大部分的`GUI`應用程序都要保存和載入這樣那樣的數據。考慮到你和你的用戶,應該有一個簡單的,方便的機制來選擇文件。很高興,為此`wxPython`提供了標準的文件對話框`wx.FileDialog`。圖6.5顯示了這個用于`sketch`程序的文件對話框。 ![](https://box.kancloud.cn/2016-08-21_57b9960a70a83.gif) `wx.FileDialog`最重要的方法是它的構造器,語法如下: `wx.FileDialog(parent`, `message`="`Choose` a `file`", `defaultDir`="", `defaultFile`="", `wildcard`="*.*", `style`=0) 表6.6對構造器的參數進行了說明。 **表6.6** **`wx.FileDialog`構造器的參數** `parent`:對話框的父窗口。如果沒有父窗口則為`None`。 `message`:`message`顯示在對話框的標題欄中。 `defaultDir`:當對話框打開時,默認的目錄。如果為空,則為當前工作目錄。 `defaultFile`:當對話框打開時,默認選擇的文件。如果為空,則沒有文件被選擇。 `wildcard`:通配符。指定要選擇的文件類型。格式是 `display` | `wildcard` 。可以指定多種類型的文件,例如:“`Sketch` `files` (*.`sketch)`|*.`sketch`|`All` `files` (*.*)|*.*”。 `style`:樣式。見下表6.7。 **表6.7** **`wx.FileDialog`的樣式** `wx.CHANGE_DIR`:在用戶選擇了一個文件之后,當前工作目錄相應改變到所選文件所在的目錄。 `wx.MULTIPLE`:僅適用于打開對話框。這個樣式使得用戶可以選擇多個文件。 `wx.OPEN`:這個樣式用于打開一個文件。 `wx.OVERWRITE_PROMPT`:僅適用于保存文件對話框。顯示一個提示信息以確認是否覆蓋一個已存在的文件。 `wx.SAVE`:這個樣式用于保存一個文件。 要使用文件對話框,要對一個對話框實例調用`ShowModal()`方法。這個方法根據用戶所敲擊的對話框上的按鈕來返回`wx.ID_OK`或`wx.ID_CANCEL`。選擇之后。使用`GetFilename()`, `GetDirectory()`, 或`GetPath()`方法來獲取數據。之后,調用`Destroy()`銷毀對話框是一個好的觀念。 下例6.6顯了對`SketchFrame`所作的修改以提供保存和裝載(完整的附書源碼請到論壇的"相關資源"的"教程下載"中下載)。這些改變要求導入`cPickle`和`os`模塊。我們使用`cPickle`來將數據的列表轉換為可用于文件讀寫的數據格式。 **例6.6** **`SketchFrame`的保存和裝載方法** ``` def __init__(self, parent): self.title = "Sketch Frame" wx.Frame.__init__(self, parent, -1, self.title,size=(800,600)) self.filename = "" self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.initStatusBar() self.createMenuBar() self.createToolBar() def SaveFile(self):#1 保存文件 if self.filename: data = self.sketch.GetLinesData() f = open(self.filename, 'w') cPickle.dump(data, f) f.close() def ReadFile(self):#2 讀文件 if self.filename: try: f = open(self.filename, 'r') data = cPickle.load(f) f.close() self.sketch.SetLinesData(data) except cPickle.UnpicklingError: wx.MessageBox("%s is not a sketch file." % self.filename, "oops!", style=wx.OK|wx.ICON_EXCLAMATION) wildcard = "Sketch files (*.sketch)|*.sketch|All files (*.*)|*.*" def OnOpen(self, event):#3 彈出打開對話框 dlg = wx.FileDialog(self, "Open sketch file...",os.getcwd(), style=wx.OPEN,wildcard=self.wildcard) if dlg.ShowModal() == wx.ID_OK: self.filename = dlg.GetPath() self.ReadFile() self.SetTitle(self.title + ' -- ' + self.filename) dlg.Destroy() def OnSave(self, event):#4 保存文件 if not self.filename: self.OnSaveAs(event) else: self.SaveFile() def OnSaveAs(self, event):#5 彈出保存對話框 dlg = wx.FileDialog(self, "Save sketch as...", os.getcwd(), style=wx.SAVE | wx.OVERWRITE_PROMPT, wildcard=self.wildcard) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() if not os.path.splitext(filename)[1]:#6 確保文件名后綴 filename = filename + '.sketch' self.filename = filename self.SaveFile() self.SetTitle(self.title + ' -- ' + self.filename) dlg.Destroy() ``` **#1**:該方法寫文件數據到磁盤中,給定了文件名,使用了`cPickle`模塊。 **#2**:該方法使用`cPickle`來讀文件。如果文件不是期望的類型,則彈出一個消息框來警告。 **#3**:`OnOpen()`方法使用`wx.OPEN`樣式來創建一個對話框。通配符讓用戶可以限定選擇.`sketch`文件。如果用戶敲擊`OK`,那么該方法根據所選擇的路徑調用`ReadFile()`方法。 **#4**:如果已經選擇了用于保存當前數據的文件名,那么保存文件,否則,我們打開保存對話框。 **#5**:`OnSave()`方法創建一個`wx.SAVE`文件對話框。 **#6**:這行確保文件名后綴為.`sketch`。 下一節,我們將討論如何使用文件選擇器。 ### 如何使用標準的顏色選擇器? 如果用戶能夠在`sketch`對話框中選擇任意的顏色,那么這將是有用。對于這個目的,我們可以使用`wxPython`提供的標準`wx.ColourDialog`。這個對話框的用法類似于文件對話框。它的構造器只需要一個`parent(`雙親)和一個可選的數據屬性參數。數據屬性是一個`wx.ColourData`的實例,它存儲與該對話框相關的一些數據,如用戶選擇的顏色,還有自定義的顏色的列表。使用數據屬性使你能夠在以后的應用中保持自定義顏色的一致性。 在`sketch`應用程序中使用顏色對話框,要求增加一個菜單項和一個處理器方法。例6.7顯示了所增加的代碼。 **例6.7** **對`SketchFrame`做一些改變,以顯示顏色對話框** ``` def menuData(self): return [(" ", ( (" ", "New Sketch file", self.OnNew), (" ", "Open sketch file", self.OnOpen), (" ", "Save sketch file", self.OnSave), ("", "", ""), (" ", ( (" ", "", self.OnColor,wx.ITEM_RADIO), (" ", "", self.OnColor,wx.ITEM_RADIO), (" ", "", self.OnColor,wx.ITEM_RADIO), (" ", "", self.OnColor,wx.ITEM_RADIO), (" ", "", self.OnOtherColor,wx.ITEM_RADIO))), ("", "", ""), (" ", "Quit", self.OnCloseWindow)))] def OnOtherColor(self, event): dlg = wx.ColourDialog(self) dlg.GetColourData().SetChooseFull(True)# 創建顏色數據對象 if dlg.ShowModal() == wx.ID_OK: self.sketch.SetColor(dlg.GetColourData().GetColour())# 根據用戶的輸入設置顏色 dlg.Destroy() ``` 顏色數據實例的`SetChooseFull()`方法告訴對話框去顯示整個調色板,其中包括了自定義的顏色信息。對話框關閉后,我們根據得到的顏色來拾取顏色數據。顏色數據作為一個`wx.Color`的實例返回并傳遞給`sketch`程序來設置顏色。 ## 給應用程序一個好看的外觀 在這一節中,我們將討論如何讓你的程序有一個好的外觀。從重要性來說,儲如你如何作安排以便用戶調整窗口的大小,從細節來說,儲如你如何顯示一個`about`框。在本書的第二部分,我們將更詳細地對這些主題進行進一步的討論。 ### 如何布局窗口部件? 在你的`wxPython`應用程序中布局你的窗口部件的方法之一是,在每個窗口部件被創建時顯式地指定它的位置和大小。雖然這個方法相當地簡單,但是它一直存在幾個缺點。其中之一就是,因為窗口部件的尺寸和默認字體的尺寸不同,對于在所有系統上要得到一個正確的定位是非常困難的。另外,每當用戶調整父窗口的大小時,你必須顯式地改變每個窗口部件的定位,要正確地實現它是十分痛苦的。 幸運的是,這兒有一個更好的方法。在`wxPython`中的布局機制是一個被稱為`sizer`的東西,它類似于`Java` `AWT`和其它的界面工具包中的布局管理器。每個不同的`sizer`基于一套規則管理它的窗口的尺寸和位置。`sizer`屬于一個容器窗口(比如`wx.Panel`)。在父中創建的子窗口必須被添加給`sizer`,`sizer`管理每個窗口部件的尺寸和位置。 **創建一個`sizer`** 創建一個`sizer`的步驟: 1、創建你想用來自動調用尺寸的`panel`或`container(`容器)。 2、創建`sizer`。 3、創建你的子窗口。 4、使用`sizer`的`Add()`方法來將每個子窗口添加給`sizer`。當你添加窗口時,給了`sizer`附加的信息,這包括窗口周圍空間的度量、在由`sizer`所管理分配的空間中如何對齊窗口、當容器窗口改變大小時如何擴展子窗口等。 5、`sizer`可以嵌套,這意味你可以像窗口對象一樣添加別的`sizer`到父`sizer`。你也可以預留一定數量的空間作為分隔。 6、調用容器的`SetSizer(sizer)`方法。 表6.8列出了在`wxPython`中有效的最常用的`sizer`。對于每個專門的`sizer`的更完整的說明見第11章。 **表6.8** **最常用的`wxPython`的`sizer`** `wx.BoxSizer`:在一條線上布局子窗口部件。`wx.BoxSizer`的布局方向可以是水平或堅直的,并且可以在水平或堅直方向上包含子`sizer`以創建復雜的布局。在項目被添加時傳遞給`sizer`的參數控制子窗口部件如何根據`box`的主體或垂直軸線作相應的尺寸調整。 `wx.FlexGridSizer`:一個固定的二維網格,它與`wx.GridSizer`的區別是,行和列根據所在行或列的最大元素分別被設置。 `wx.GridSizer`:一個固定的二維網格,其中的每個元素都有相同的尺寸。當創建一個`grid` `sizer`時,你要么固定行的數量,要么固定列的數量。項目被從左到右的添加,直到一行被填滿,然后從下一行開始。 `wx.GridBagSizer`:一個固定的二維網格,基于`wx.FlexGridSizer`。允許項目被放置在網格上的特定點,也允許項目跨越多和網格區域。 `wx.StaticBoxSizer`:等同于`wx.BoxSizer`,只是在`box`周圍多了一個附加的邊框(有一個可選的標簽)。 **使用`sizer`** 為了演示`sizer`的用法,我們將給`sketch`應用程序增加一個`control` `panel`。`control` `panel`包含用來設置線條顏色和粗細的按鈕。這個例子使用了`wx.GridSizer`(用于按鈕)和`wx.BoxSizer`(用于其余的布局部分)。圖6.6顯示了使用了`panel`的`sketch`應用程序,并圖解了`grid`和`box`的實際布局。 ![](https://box.kancloud.cn/2016-08-21_57b9960a8b316.gif) 例6.8顯示了實現`control` `panel`而對`sketch`程序所作的必要的改變。在這一節,我們的討論將著重于`sizer`的實現。 **例6.8** ``` def __init__(self, parent): self.title = "Sketch Frame" wx.Frame.__init__(self, parent, -1, self.title, size=(800,600)) self.filename = "" self.sketch = SketchWindow(self, -1) self.sketch.Bind(wx.EVT_MOTION, self.OnSketchMotion) self.initStatusBar() self.createMenuBar() self.createToolBar() self.createPanel() def createPanel(self): controlPanel = ControlPanel(self, -1, self.sketch) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(controlPanel, 0, wx.EXPAND) box.Add(self.sketch, 1, wx.EXPAND) self.SetSizer(box) ``` 在例6.8中,`createPanel()`方法創建了`ControlPanel`(在下面的列表中說明)的實例,并且與`box` `sizer`放在一起。`wx.BoxSizer`的構造器的唯一參數是方向,取值可以是`wx.HORIZONTAL`或`wx.VERTICAL`。接下來,這個新的`controlPanel`和先前創建的`SketchWindow`被使用`Add()`方法添加給了`sizer`。第一個參數是要被添加給`sizer`的對象。第二個參數是被`wx.BoxSizer`用作因數去決定當`sizer`的大小改變時,`sizer`應該如何調整它的孩子的尺寸。我們這里使用的是水平方向調整的`sizer`,`stretch`因數決定每個孩子的水平尺寸如何改變(堅直方向的改變由`box` `sizer`基于第三個參數來決定)。 如果第二個參數(`stretch`因數)是0,對象將不改變尺寸,無論`sizer`如何變化。如果第二個參數大于0,則`sizer`中的孩子根據因數分割`sizer`的總尺寸(類似于`wx.StatusBar`管理文本域的寬度的做法)。如果`sizer`中的所有孩子有相同的因數,那么它們按相同的比例分享放置了固定尺寸的元素后剩下的空間。這里的0表示假如用戶伸展框架時,`controlPanel`不改變水平的尺寸,而1表示繪畫窗口(`sketch` `window`)的尺寸要隨框架的改變而改變。 `Add()`的第三個參數是另一個位掩碼標志。完整的說明將在以后的章節中給出。`wx.EXPAND`指示`sizer`調整孩子的大小以完全填滿有效的空間。其它的可能的選項允許孩子被按比例的調整尺寸或根據`sizer`的特定部分對齊。圖6.7將幫助闡明參數及其控制的調整尺寸的方向。 這些設置的結果是當你運行這個帶有`box` `sizer`的框架的時候,任何在水平方向的改變都將導致`sketch` `window`的尺寸在該方向?系母謀洌瑊 `panel`不會在該方向上改變。在堅直方向的尺寸改變導致這兩個子窗口都要在堅直方向縮放。 例6.8中涉及的類`ControlPanel`結合使用了`grid`和`box` `sizer`。例6.9包含了這個類的代碼。 ![](https://box.kancloud.cn/2016-08-21_57b9960aa350e.gif) **例6.9** **`ControlPanel`類** ``` class ControlPanel(wx.Panel): BMP_SIZE = 16 BMP_BORDER = 3 NUM_COLS = 4 SPACING = 4 colorList = ('Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple', 'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod', 'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey') maxThickness = 16 def __init__(self, parent, ID, sketch): wx.Panel.__init__(self, parent, ID, style=wx.RAISED_BORDER) self.sketch = sketch buttonSize = (self.BMP_SIZE + 2 * self.BMP_BORDER, self.BMP_SIZE + 2 * self.BMP_BORDER) colorGrid = self.createColorGrid(parent, buttonSize) thicknessGrid = self.createThicknessGrid(buttonSize) self.layout(colorGrid, thicknessGrid) def createColorGrid(self, parent, buttonSize):#1 創建顏色網格 self.colorMap = {} self.colorButtons = {} colorGrid = wx.GridSizer(cols=self.NUM_COLS, hgap=2, vgap=2) for eachColor in self.colorList: bmp = parent.MakeBitmap(eachColor) b = buttons.GenBitmapToggleButton(self, -1, bmp, size=buttonSize) b.SetBezelWidth(1) b.SetUseFocusIndicator(False) self.Bind(wx.EVT_BUTTON, self.OnSetColour, b) colorGrid.Add(b, 0) self.colorMap[b.GetId()] = eachColor self.colorButtons[eachColor] = b self.colorButtons[self.colorList[0]].SetToggle(True) return colorGrid def createThicknessGrid(self, buttonSize):#2 創建線條粗細網格 self.thicknessIdMap = {} self.thicknessButtons = {} thicknessGrid = wx.GridSizer(cols=self.NUM_COLS, hgap=2, vgap=2) for x in range(1, self.maxThickness + 1): b = buttons.GenToggleButton(self, -1, str(x), size=buttonSize) b.SetBezelWidth(1) b.SetUseFocusIndicator(False) self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b) thicknessGrid.Add(b, 0) self.thicknessIdMap[b.GetId()] = x self.thicknessButtons[x] = b self.thicknessButtons[1].SetToggle(True) return thicknessGrid def layout(self, colorGrid, thicknessGrid):#3 合并網格 box = wx.BoxSizer(wx.VERTICAL) box.Add(colorGrid, 0, wx.ALL, self.SPACING) box.Add(thicknessGrid, 0, wx.ALL, self.SPACING) self.SetSizer(box) box.Fit(self) def OnSetColour(self, event): color = self.colorMap[event.GetId()] if color != self.sketch.color: self.colorButtons[self.sketch.color].SetToggle(False) self.sketch.SetColor(color) def OnSetThickness(self, event): thickness = self.thicknessIdMap[event.GetId()] if thickness != self.sketch.thickness: self.thicknessButtons[self.sketch.thickness].SetToggle(False) self.sketch.SetThickness(thickness) ``` **#1**:`createColorGrid()`方法建造包含顏色按鈕的`grid` `sizer`。首先,我們創建`sizer`本身,指定列為4列。由于列數已被設定,所以按鈕將被從左到右的布局,然后向下。接下來我們要求顏色的列表,并為每種顏色創建一個按鈕。在`for`循環中,我們為每種顏色創建了一個方形的位圖,并使用`wxPython`庫中所定義的一般的按鈕窗口部件類創建了帶有位圖的切換按鈕。然后我們把按鈕與事件相綁定,并把它添加到`grid`。之后,我們把它添加到字典以便在以后的代碼中,易于關聯顏色、`ID`和按鈕。我們不必指定按鈕在網格中的位置;`sizer`將為我們做這件事。 **#2**:`createThicknessGrid()`方法基本上類似于`createColorGrid()`方法。實際上,一個有進取心的程序員可以把它們做成一個通用函數。`grid` `sizer`被創建,十六個按鈕被一次性添加,`sizer`確保了它們在屏幕上很好地排列。 **#3**:我們使用一個堅直的`box` `sizer`來放置網格(`grid)`。每個`grid`的第二個參數都是0,這表明`grid` `sizer`當`control` `panel`在垂直方向伸展時不改變尺寸。(由于我們已經知道`control` `panel`不在水平方向改變尺寸,所以我們不必指定水平方向的行為。)`Add()`的第四個參數是項目的邊框寬度,這里使用`self.SPACING`變量指定。第三個參數`wx.ALL`是一套標志中的一個,它控制那些邊套用第四個參數指定的邊框寬度,`wx.ALL`表明對象的四個邊都套用。最后,我們調用`box` `sizer`的`Fit()`方法,使用的參數是`control` `panel`。這個方法告訴`control` `panel`調整自身尺寸以匹配`sizer`認為所需要的最小化尺寸。通常這個方法在使用了`sizer`的窗口的構造中被調用,以確保窗口的大小足以包含`sizer`。 基類`wx.Sizer`包含了幾個通用于所有`sizer`的方法。表6.9列出了最常用的方法。 **表6.9** **`wx.Sizer`的方法** ``` Add(window, proportion=0, flag=0, border=0, userData=None) Add(sizer, proportion=0, flag=0, border=0, userData=None) ``` `Add(size`, `proportion`=0,`flag`=0, `border`=0,`userData`=`None)`:第一個添加一個`wxWindow`,第二個添加一個嵌套的`sizer`,第三個添加空的空間,用作分隔符。參數`proportion`管理窗口總尺寸,它是相對于別的窗口的改變而言的,它只對`wx.BoxSizer`有意義。參數`flag`是一個位圖,針對對齊、邊框位置,增長有許多不同的標志,完整的列表見第十一章。參數`border`是窗口或`sizer`周圍以像素為單位的空間總量。`userData`使你能夠將對象與數據關聯,例如,在一個子類中,可能需要更多的用于尺寸的信息。 `Fit(window)` `FitInside(window` ):調整`window`尺寸以匹配`sizer`認為所需要的最小化尺寸。這個參數的值通常是使用`sizer`的窗口。`FitInside()`是一個類似的方法,只不過將改變窗口在屏幕上的顯示替換為只改變它的內部實現。它用于`scroll` `panel`中的窗口以觸發滾動欄的顯示。 `GetSize()`:以`wx.Size`對象的形式返回`sizer`的尺寸。 `GetPosition()`:以`wx.Point`對象的形式返回`sizer`的位置。 `GetMinSize()`:以`wx.Size`對象的形式返回完全填充`sizer`所需的最小尺寸。 `Layout()`:強迫`sizer`去重新計算它的孩子的尺寸和位置。在動態地添加或刪除了一個孩子之后調用。 `Prepend(...)`:與`Add()`相同(只是為了布局的目的,把新的對象放在`sizer`列表的開頭)。 `Remove(window)` `Remove(sizer)` `Remove(nth)`:從`sizer`中刪除一個對象。 `SetDimension(x`, y, `width`,`height)`:強迫`sizer`按照給定的參數重新定位它的所有孩子。 有關`sizer`和嵌套`sizer`的更詳細的信息請參考第11章。 ### 如何建造一個關于(about)框? `about`框是顯示對話框的一個好的例子,它能夠顯示比純信息框更復雜的信息。這里,你可以使用`wx.html.HtmlWindow`作為一個簡單的機制來顯示樣式文本。實際上,`wx.html.HtmlWindow`遠比我們這里演示的強大,它包括了管理用戶交互以及繪制的方法。第16章涵蓋了`wx.html.HtmlWindow`的特性。例6.10展示了一個類,它使用`HTML` `renderer`創建一個`about`框。 **例6.10** **使用`wx.html.HtmlWindow`作為一個`about`框** ``` class SketchAbout(wx.Dialog): text = ''' <html> <body bgcolor="#ACAA60"> <center><table bgcolor="#455481" width="100%" cellspacing="0" cellpadding="0" border="1"> <tr> <td align="center"><h1>Sketch!</h1></td> </tr> </table> </center> <p><b>Sketch</b> is a demonstration program for <b>wxPython In Action</b> Chapter 6\. It is based on the SuperDoodle demo included with wxPython, available at http://www.wxpython.org/ </p> <p><b>SuperDoodle</b> and <b>wxPython</b> are brought to you by <b>Robin Dunn</b> and <b>Total Control Software</b>, Copyright &copy; 1997-2006.</p> </body> </html>s ''' def __init__(self, parent): wx.Dialog.__init__(self, parent, -1, 'About Sketch', size=(440, 400) ) html = wx.html.HtmlWindow(self) html.SetPage(self.text) button = wx.Button(self, wx.ID_OK, "Okay") sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5) sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5) self.SetSizer(sizer) self.Layout() ``` 上面的`HTML`字符串中,有一些布局和字體標記。這里的對話框合并了`wx.html.HtmlWindow`和一個`wx.ID_OK` `ID`按鈕。敲擊按鈕則自動關閉窗口,如同其它對話框一樣。一個垂直的`box` `sizer`用于管理這個布局。 圖6.8顯示了該對話框。 圖6.8 ![](https://box.kancloud.cn/2016-08-21_57b9960ab8fc6.gif) 把它作為一個菜單項(`About)`的處理器的方法如下: ``` def OnAbout(self, event): dlg = SketchAbout(self) dlg.ShowModal() dlg.Destroy() ``` ### 如何建造一個啟動畫面? 顯示一個好的啟動畫面,將給你的用戶一種專業化的感覺。在你的應用程序完成一個費時的設置的時候,它也可以轉移用戶的注意力。在`wxPython`中,使用類`wx.SplashScreen`建造一個啟動畫面是很容易的。啟動畫面可以保持顯示指定的時間,并且無論時間是否被設置,當用戶在其上敲擊時,它總是會關閉。 `wx.SplashScreen`類的構造函數如下: ``` wx.SplashScreen(bitmap, splashStyle, milliseconds, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.SIMPLE_BORDER|wx.FRAME_NO_TASKBAR|wx.STAY_ON_TOP) ``` 表6.10說明了`wx.SplashScreen`構造函數的參數 **表6.10** **`wx.SplashScreen`構造函數的參數** `bitmap`:一個`wx.Bitmap`,它被顯示在屏幕上。 `splashStyle`:另一個位圖樣式,可以是下列的結合:`wx.SPLASH_CENTRE_ON_PARENT`,`wx.SPLASH_CENTRE_ON_SCREEN`, `wx.SPLASH_NO_CENTRE`, `wx.SPLASH_TIMEOUT`, `wx.SPLASH_NO_TIMEOUT` `milliseconds`:如果`splashStyle`指定為`wx.SPLASH_TIMEOUT`,`milliseconds`是保持顯示的毫秒數。 `parent`:父窗口,通常為`None`。 `id`:窗口`ID`,通常使用-1比較好。 `pos`:如果`splashStyle`取值`wx.SPLASH_NO_CENTER`的話,`pos`指定畫面在屏幕上的位置。 `size`:尺寸。通常你不需要指定這個參數,因為通常使用位圖的尺寸。 `style`:常規的`wxPython`框架的樣式,一般使用默認值就可以了。 例6.11顯示了啟動畫面的代碼。這里我們用一具自定義的`wx.App`子類來替代了`wx.PySimpleApp`。 **例6.11** **一個啟動畫面的代碼** ``` class SketchApp(wx.App): def OnInit(self): bmp = wx.Image("splash.png").ConvertToBitmap() wx.SplashScreen(bmp, wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT, 1000, None, -1) wx.Yield() frame = SketchFrame(None) frame.Show(True) self.SetTopWindow(frame) return True ``` 通常,啟動畫面被聲明在應用程序啟動期間的`OnInit`方法中。啟動畫面將一直顯示直到它被敲擊或超時。這里,啟動畫面顯示在屏幕的中央,一秒后超時。`Yield()`的調用很重要,因為它使得在應用程序繼續啟動前,任何未被處理的事件仍可以被繼續處理。這里,`Yield()`的調用確保了在應用程序繼續啟動前,啟動畫面能夠接受并處理它的初始化繪制事件。 ## 本章小結 1. 大多數的應用程序使用了諸如菜單、工具欄和啟動畫面這樣的通常的元素。它們的使用不但對你程序的可用性有幫助,并且使你的應用程序看起來更專業。在這一章里,我們使用了一個簡單的`sketch`應用程序,并且使用了工具欄、狀態欄、菜單欄,通用對話框、復雜的布局、`about`框和啟動畫面來逐步對它作了改進。 2. 你可以使用一個設備上下文來直接對`wxPython`的顯示進行繪制。不同的顯示要求不同的設備上下文,然而它們共享一個通用`API`。為了平滑的顯示,設備上下文可以被緩存。 3. 一個狀態欄能夠被自動地創建在框架的底部。它可以包含一個或多個文本域,各文本域可被獨立調整尺寸和設置。 4. 菜單可以包含嵌套的子菜單,菜單項可以有切換狀態。工具欄產生與菜單欄同種的事件,并且被設計來更易于對工具按鈕分組布局。 5. 可以使用標準`wx.FileDialog`來管理打開和保存數據。可以使用`wx.ColourDialog`來選擇顏色。 6. 使用`sizer`可以進行窗口部件的復雜布局,而不用明確地指定具體位置。`sizer`根據規則自動放置它的孩子對象。`sizer`包括的`wx.GridSizer`按二維網格來布局對象。`wx.BoxSizer`在一條線上布局項目。`sizer`可以嵌套,并且當`sizer`伸展時可以,它可以控制其孩子的行為。 7. `about`框或別的簡單的對話框可以使用`wx.html.HtmlWindow`來創建。啟動畫面用`wx.SplashScreen`來創建。 在第一部分(`part` 1)中,我們已經涵蓋了`wxPython`的基本概念,并且我們已經涉及了一些最常見的任務。在接下來的第二部分(`part` 2)中,我們將使用目前常見的問答的格式,但是我們將涉及有關`wxPython`工具包的組成和功能方面的更詳細的問題。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看