wxPython入門中文版 (Getting Started with wxPython)

本文翻譯自http://wiki.wxpython.org/Getting%20Started
首先聲明:本人仍是個菜鳥,翻譯只是爲了學習,就看成記筆記了。水平有限,錯誤和疏漏在所不免,但願各路高手可以給予指導。並且簡單查了一下,好像中文世界目前尚未完整的翻譯 Getting Started with wxPython 的。html

wxPython入門

第一個應用程序:」Hello, World!」

按慣例,咱們先來寫一個 「Hello, World!」 小程序。這是代碼:python

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """
import wx

app = wx.App(False) #建立1個APP,禁用stdout/stderr重定向
frame = wx.Frame(None, wx.ID_ANY, "Hello, World!")  #這是一個頂層的window
frame.Show(True)    #顯示這個frame
app.MainLoop()

解釋:web

代碼 說明
app = wx.App(False) 每個 wxPython 應用程序都是一個 wx.App 實例。對於大多數的簡單程序,直接實例化 wx.App 便可。但若是你但願建立一個複雜的應用程序,那麼能夠對 wx.App class 作一些擴展。」False」 參數意味着「不要把 stdout 和 stderr 信息重定向到窗口」,固然也能夠不加 「False」 參數。
frame = wx.Frame(None, wx.ID_ANY, 「Hello, World!」) 完整的語法是 x.Frame(Parent, Id, Title)。在本例中,咱們使用 「None」 來表示這個frame是頂層的框架,沒有父框架;使用 「wx.ID_ANY」 讓 wxWidgets 來給咱們挑選一個ID。
frame.Show(True) 顯示這個Frame
app.MainLoop() 運行這個應用程序

Note1: 你還能夠用 -1 來替代wx.ID_ANY-1 就是默認值的意思。另外 wxWidgets 還提供了其它的標準 ID(v2.8)。 你也能夠自定義一個ID,但 Getting Started with wxPython 認爲,沒有理由那樣作,用標準ID更好。
Note2: 實際上,wx.Frame的完整語法是(詳細的參數介紹):小程序

wx.Frame(Parent, ID, Title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="frame")

最後運行程序,咱們能夠看到相似這樣的窗口:
Hello Worldwindows

Windows 仍是 Frames?

當人們談論GUI的時候,他們一般指的是windows,menus和icons。那麼天然地,你可能會認爲應該用wx.Window來表明屏幕上的一個window。但實際上不是這樣的。wx.Window 是一個基礎的class,全部的可視化元素,例如buttons, menus等等,都起源於wx.Window 類。而程序窗口則是一個wx.Frame 。新手常常把這2個概念搞混,須要特別留心。api

建立一個簡單的記事本

如今咱們來寫一個簡單的記事本。在這個例子中,咱們會用到幾個組件,來理解一些特性或功能,例如事件(events)和回調(callbacks)。app

第1步

首先,咱們須要建立1個frame,而且這個frame包含1個可編輯的文本框(text box)。文本框須要用wx.TextCtrl 來建立。默認狀況下,文本框只能編輯1行文字——不管文字有多長,都不會換行。因此,咱們須要用wx.TE_MULTILINE 參數來容許多行編輯。框架

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (200, 100))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.Show(True)

app = wx.App(False)
frame = MainWindow(None, 'Small editor')
app.MainLoop()

在這個例子中,咱們生成一個wx.Frame 的子類,並重寫它的__init__ 方法。咱們用wx.TextCtrl 來聲明一個簡單的文本編輯器。注意,由於在MyFrame.__init__ 中已經運行了self.Show() ,因此在建立MyFrame的實例以後,就不用再調用frame.Show() 了。編輯器

添加一個菜單欄MenuBar

全部的應用程序都會有一個菜單欄,和一個狀態欄。讓咱們來給這個記事本程序添加一個:svg

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (200, 100))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    #建立位於窗口的底部的狀態欄

        #設置菜單
        filemenu = wx.Menu()

        #wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的標準ID
        filemenu.Append(wx.ID_ABOUT, u"關於", u"關於程序的信息")
        filemenu.AppendSeparator()
        filemenu.Append(wx.ID_EXIT, u"退出", u"終止應用程序")

        #建立菜單欄
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, u"文件")
        self.SetMenuBar(menuBar)
        self.Show(True)

app = wx.App(False)
frame = MainWindow(None, title = u"記事本")
app.MainLoop()

TIP: wx.ID_ABOUTwx.ID_EXIT 是wxWidgets提供的標準ID(查看所有標準ID)。若是有一個現成的標準ID,最好仍是使用它,而不要自定義。由於這樣可讓wxWidgets知道,在不一樣的平臺怎樣去顯示這個組件,使它看起來更美觀。

事件處理event handling

咱們已經建立了1個記事本,雖然它有菜單,可是什麼都作不了。咱們但願點擊菜單以後,程序可以作出反應,例如退出,或者保存文件。在Python中,點擊菜單,點擊按鈕,輸入文本,鼠標移動等等,都被稱爲事件event,而對event作出反應,則被稱爲event handling。對不一樣的event作出不一樣的響應,這是GUI程序的根本。咱們可使用Bind() 方法,將1個對象Object和1個時間event創建綁定關係。

class MainWindow(wx.Frame):
   def __init__(self, parent, title):
   wx.Frame.__init__(self,parent, title=title, size=(200,100))
   ...
   menuItem = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
   self.Bind(wx.EVT_MENU, self.OnAbout, menuItem)

這段代碼意味着:從如今開始,一旦用戶點擊了菜單中的 「About」 項目,self.OnAbout 就會被執行。

Note: Bind()以後,運行個人程序就提示編碼錯誤,不能再使用中文了,因此下面的代碼示例都是全英文的。不知道這是否是python(x,y)獨有的問題。誰能幫我解答一下?

wx.EVT_MENU 指代「選擇菜單中的項目」這個事件。wxWidgets 提供了不少的事件,能夠點這裏查看不完整的列表,也可使用下面的代碼打印完整的列表。全部的事件都是wx.Event 的子類。

import wx

for x in dir(wx):
    if x.startswith('EVT_'):
        print x

若是直接運行上面的Bind程序,會提示不存在OnAbout這個attribute。還須要在Class中聲明self.OnAbout 方法:

def OnAbout(self, event):
         ...

這裏的event參數是wx.Event 的子類的一個實例。

當event發生的時候,method就會被執行。默認狀況下,這個method會處理event,而且當callback完成以後,event也會中止。可是在一些結構化的事件處理器event handlers中,咱們可使用event.Skip() 來跳過一個event。例如

def OnButtonClick(self, event):
    if (某種條件):
        作某事()
    else:
        event.Skip()

def OnEvent(self, event):
    ...

當一個點擊按鈕的事件發生時,OnButtonClick會被調用。若是「某種條件」爲真,咱們就會「作某事()」。不然咱們就會讓其它的event handler來處理這個事件。

如今來看看咱們的程序:

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """
import wx
class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title = title, size = (600, 400))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    # 建立位於窗口的底部的狀態欄

        # 設置菜單
        filemenu = wx.Menu()

        # wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的標準ID
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
            " Information about this program")    # (ID, 項目名稱, 狀態欄信息)
        filemenu.AppendSeparator()
        menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
            " Terminate the program")    # (ID, 項目名稱, 狀態欄信息)

        # 建立菜單欄
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")    # 在菜單欄中添加filemenu菜單
        self.SetMenuBar(menuBar)    # 在frame中添加菜單欄

        # 設置events
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

        self.Show(True)

    def OnAbout(self, e):
        # 建立一個帶"OK"按鈕的對話框。wx.OK是wxWidgets提供的標準ID
        dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 語法是(self, 內容, 標題, ID)
        dlg.ShowModal()    # 顯示對話框
        dlg.Destroy()    # 當結束以後關閉對話框

    def OnExit(self, e):
        self.Close(True)    # 關閉整個frame


app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()

Note1: 上述代碼的菜單項目名稱」&About」, 「E&xit」, 「&File」 中的 「&」是作什麼用的? 「&」 的位置也不同,分別意味着什麼?若是直接print "&About" ,會把 「&」 打印出來。可是在上面的應用程序菜單中看不到 「&」。並且我試過把 「&」去掉,沒有任何變化。誰能幫我解答一下?

Note2: 下面代碼中的wx.OK 能夠省略,此時等於wx.ID_ANY

dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 語法是(self, 內容, 標題, ID)

這是帶wx.OK的對話框:

這是省略wx.OK的對話框:
這裏寫圖片描述

對話Dialogs

固然,一個文本編輯器不可以沒有打開或保存文檔的功能——這些功能是由對話來實現的。通常對話由底層平臺提供,這樣你的應用程序看上去就像是一個原生程序。在本例中,對話由 MainWindowOnOpen 方法來實施:

def OnOpen(self,e):
         """ Open a file"""
         self.dirname = ''
         dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
         if dlg.ShowModal() == wx.ID_OK:
             self.filename = dlg.GetFilename()
             self.dirname = dlg.GetDirectory()
             f = open(os.path.join(self.dirname, self.filename), 'r')
             self.control.SetValue(f.read())
             f.close()
         dlg.Destroy()

解釋:

  • 首先,咱們經過調用適當的構造函數來建立對話
  • 而後,咱們調用ShowModal 打開對話框 - 「Modal」 的意思是,在用戶點擊 OK 或 Cancel 以前,不能作任何的操做。
  • ShowModal 的返回值是一個被點擊按鈕的 ID, 若是用戶點擊了 OK 按鈕,程序就讀取文件

如今,你能夠向菜單中添加相應的條目,並把它連接到OnOpen 方法。若是你遇到了問題,請向下滾動頁面,查閱下文的完整代碼。

擴展功能

固然,目前這個程序還遠不是一個合格的文本編輯器。可是,添加其它的功能並不比咱們剛纔所完成的內容更難,你能夠從 wxPython 提供的 Demo 獲取靈感(點此下載Demo,選擇版本後,下載 wxPython-demo-x.x.x 文件):

  • Drag and Drop.
  • MDI
  • Tab view/multiple files
  • Find/Replace dialog
  • Print dialog (Printing)
  • Macro-commands in python ( using the eval function)
  • etc …

操做窗口

主題:

  • Frames
  • Windows
  • Controls/Widgets
  • Sizers
  • Validators

在這個章節,咱們將會講解 wxPython 處理窗口和窗口內容的方法,包括建立輸入組件,使用各類工具和控件 widgets/controls。 咱們將會建立一個計算股票價格的小程序。若是你已是個有經驗的 GUI 開發者,這部分的內容對你來講太簡單了,你能夠直接閱讀下文的 Boa-Constructor 章節。

概述

可見元素的佈局

在 frame 裏面,你可使用若干個 wxWindow 子類來充實 frame 的內容,經常使用的元素有如下幾種:

  • wx.MenuBar, 在 frame 的頂部填加菜單欄
  • wx.StatusBar, 在 frame 的底部填加狀態欄,顯示狀態信息
  • wx.ToolBar, 在 frame 中添加工具欄
  • wx.Control 的子類,它們表明用戶接口的widgets (例如顯示數據 and/or 處理用戶輸入的可見元素). 常見的wx.Control 對象包括 wx.Button, wx.StaticText, wx.TextCtrlwx.ComboBox.
  • wx.Panel, 它是容納各類wx.Control 對象的容器。把wx.Control 對象放入wx.Panel, 用戶就能夠操做它們。

全部的可見元素 (wxWindow 對象和它們的子類) 都可以容納子元素。例如,一個wx.Frame 能夠容納若干個wx.Panel 對象,而這些wx.Panel 又能夠容納若干wx.Button, wx.StaticTextwx.TextCtrl 對象,就像這樣:
這裏寫圖片描述

注意,這僅僅是描述可見元素的相關性,而不是描述應該怎樣佈局它們。若是要處理元素的佈局,有如下幾種選擇:

  • 能夠手工的爲每個元素指定它在父窗口中的像素座標,可是不一樣平臺的顯示效果可能會有差異,例如字體的大小會不同,因此不推薦此方法
  • 可使用wx.LayoutConstraints, 可是很複雜
  • 可使用 Delphi-like LayoutAnchors, 比wx.LayoutConstraints 簡單些
  • 使用 wxSizer 的子類,這也是本文將要講解的。

Sizers

做爲wx.Sizer 的子類,Sizer 可以被用來在 frame 或 window 中佈置可見元素。它的做用包括:

  • 爲每一個可見元素計算合適的尺寸
  • 參照必定的尺度爲元素定位
  • 當 frame 的尺寸變化時,動態的對元素的尺寸和(或)位置作出調整

一些常見的 Sizer 包括:

  • wx.BoxSizer, 基於水平線或垂直線佈置可見元素
  • wx.GridSizer, 按照網格結構來佈置元素
  • wx.FlexGridSizer, 與wx.GridSizer 相似,但更加靈活

經過調用sizer.Add(window, options...) 或者 sizer.AddMany(...) 來給出一個wx.Window 對象的列表,sizer 就可以佈置它們. Sizer 還可以嵌套,你能夠把 1 個 sizer 放進另 1 個 sizer 裏面,例如把 2 個按水平線佈置按鈕的wx.BoxSizer 放進另 1 個按垂直線佈置元素的wx.BoxSizer 裏面,就像這樣:
這裏寫圖片描述

NOTE: 在上面的例子中,6 個按鈕並非按照 2 行 3 列來作陣列式佈局的,若是要那樣作,你必須使用wx.GridSizer

接下來,咱們給咱們的文本編輯器增長 2 個嵌套的 sizer,把 1 個水平佈局的 sizer 嵌入到 1 個垂直佈局的 sizer 裏面:

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """
import wx
import os

class MainWindow(wx.Frame):
    """We simply derive a new class of Frame."""
    def __init__(self, parent, title):
        self.dirname = ''

        # "-1"這個尺寸參數意味着通知wxWidget使用默認的尺寸
        # 在這個例子中,咱們使用200像素的寬度,和默認的高度
        wx.Frame.__init__(self, parent, title = title, size = (200, -1))
        self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        self.CreateStatusBar()    # 建立位於窗口的底部的狀態欄

        # 設置菜單
        filemenu = wx.Menu()

        # wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的標準ID
        menuOpen = filemenu.Append(wx.ID_OPEN, "&Open", " Open a file")
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
            " Information about this program")    # (ID, 項目名稱, 狀態欄信息)
        filemenu.AppendSeparator()
        menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
            " Terminate the program")    # (ID, 項目名稱, 狀態欄信息)

        # 建立菜單欄
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")    # 在菜單欄中添加filemenu菜單
        self.SetMenuBar(menuBar)    # 在frame中添加菜單欄

        # 設置events
        self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)
        self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
        self.Bind(wx.EVT_MENU, self.OnExit, menuExit)

        # 設置sizers
        self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
        self.buttons = []
        for i in range(0, 6):
            self.buttons.append(wx.Button(self, -1, "Button &" + str(i)))
            self.sizer2.Add(self.buttons[i], 1, wx.SHAPED)    

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.control, 1, wx.EXPAND)    
        self.sizer.Add(self.sizer2, 0, wx.GROW)    

        # 激活sizer
        self.SetSizer(self.sizer)
        self.SetAutoLayout(True)
        self.sizer.Fit(self)     
        self.Show(True)

    def OnAbout(self, e):
        # 建立一個帶"OK"按鈕的對話框。wx.OK是wxWidgets提供的標準ID
        dlg = wx.MessageDialog(self, "A small text editor.", \
            "About Sample Editor", wx.OK)    # 語法是(self, 內容, 標題, ID)
        dlg.ShowModal()    # 顯示對話框
        dlg.Destroy()    # 當結束以後關閉對話框

    def OnExit(self, e):
        self.Close(True)    # 關閉整個frame

    def OnOpen(self, e):
        """ open a file. """
        # wx.FileDialog語法:(self, parent, message, defaultDir, defaultFile, 
        # wildcard, style, pos)
        dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*",
                            wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            f = open(os.path.join(self.dirname, self.filename), 'r') # 暫時只讀
            self.control.SetValue(f.read())
            f.close()
        dlg.Destroy()

app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()

sizer.Add 方法有 3 個參數(語法):

  • 第 1 個參數把 1 個控件添加到 sizer 裏面
  • 第 2 個參數是 proportion 權重因子,表明着這個控件相對於其它控件所佔有的空間比例。例如,若是你有 3 個編輯控件,你但願它們的空間比例是 3:2:1,那麼在把它們加到 sizer 裏面的時候,就按照這個比例數值來指定權重因子。若是權重因子爲 「0」,意味着這個控件或者 sizer,在它的父 sizer 的佈局方向上的尺寸,不會隨着 frame 的增大(縮小)而增大(縮小)。在上面的例子中,
  • 第 3 個參數 flag 一般用wx.GROW 或者wx.EXPAND, 它們的做用是同樣的,這意味着控件能夠調整本身的尺寸以適應 frame 尺寸的變化。若是使用wx.SHAPED 來充當第 3 個參數,那麼控件的尺寸雖然能夠變化,可是形狀會保持不變。

在上面的例子中,self.sizer.Add(self.sizer2, 0, wx.GROW) 權重因子是 0,因此咱們能夠看到不管 frame 的形狀怎麼變,self.sizer2 的高度是一直不變的,由於它的父 sizer self.sizer 是按照垂直線來佈置元素的。而self.sizer2 的寬度能夠變,由於第 3 個參數是wx.GROW

另外,self.sizer2.Add(self.buttons[i], 1, wx.SHAPED) 第 3 個參數是wx.SHAPED,因此不管 frame 的形狀和尺寸怎樣變化,按鈕的形狀都不會變,長度和寬度一直保持着相同的比率。

flag 參數也可使用wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERTICAL, 或wx.ALIGN_CENTER (for both) 來設置元素的居中方式,還可使用wx.ALIGN_LEFT, wx.ALIGN_TOP, wx.ALIGN_RIGHT, wx.ALIGN_BOTTOM 中的 1 個或 2 個組合,來設置元素的對齊方式。默認的對齊方式是wx.ALIGN_LEFT | wx.ALIGN_TOP.

wx.Sizer 和它的子類有一個可能會讓人感到困惑的地方,就是 sizer 和父窗口之間的區別。當你把一個對象添加到 sizer 裏面時,不須要指定這個對象的父窗口。sizer 只是對窗口布局的方式,它自己並非窗口。可是在建立對象的時候就須要指定父窗口。在上面的例子中,使用wx.Button (語法)建立按鈕的時候就須要指定 frame 或 window 做爲按鈕的父窗口,而不是指定 sizer 來當父窗口。

一旦你完成可見元素的設置,並把它們加入到 sizer(或者嵌套的 sizer),下一步就是告訴 frame 或 window 來使用 sizer。用如下 3 個必要的步驟來完成這項工做:

window.SetSizer(sizer)
window.SetAutoLayout(True)
sizer.Fit(window)
  • SetSizer() 告訴你的 window (or frame) 應該使用哪一個sizer。
  • SetAutoLayout() 告訴你的 window 使用 sizer 來佈局組件
  • sizer.Fit() 告訴 sizer 計算它所容納的元素的初始化尺寸和位置

菜單Menus

咱們已經在上文講解過菜單的設置和使用方法,再也不累述。

驗證器Validators

當你建立一個對話框或者輸入控件的時候,可使用wx.Validator 來簡化控件加載數據的進程,對輸入的數據進行驗證,或從中摘錄數據。wx.Validator 還能夠被用來截取控件域內發生的一些事件,例如敲擊鍵盤的動做。要使用驗證器,你必須先定義一個wx.Validator 的子類 (既不是wx.TextValidator 也不是wx.GenericValidator),而後再調用myInputField.SetValidator (myValidator) 把它關聯到你的控件域。

NOTE1: 你定義的wx.Validator 子類必須覆蓋wxValidator.Clone() 方法。
NOTE2: 原文並無進一步的講解 Validators 的設置和使用方法,不過你能夠參考這個 bing.com 的網頁快照

實例講解

在 Panel 中建立第 1 個 Label

如今咱們來寫一個小程序,這個程序很簡單,frame 中只有一個包含有一個標籤label[7] 的面板panel[8]:

# -*- coding: utf-8 -*-
""" Created on Sun Dec 20 20:46:32 2015 @author: chenghit """
import wx
class ExampleFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        panel = wx.Panel(self)
        self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))
        self.Show()

app = wx.App(False)
ExampleFrame(None)
app.MainLoop()

若是你讀完前面怎樣編寫文本編輯器的部分,你必定會以爲這個程序很是簡單。但須要注意的是,在這裏應該用一個 sizer 來佈置組件,而不該該手工的一一指定它們的位置。注意這行代碼:

self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))

wxStaticText 的 parent 參數是一個 panel。咱們的靜態文本將陳列在咱們剛剛建立的 panel 上面,並使用了wxPoint 參數來定義位置。根據wx.StaticText語法,還能夠定義一個wxSize 參數,可是在這個例子中並無採用。

[7] 根據 wxPython 的文檔:

Panel 就是放置組件的窗口,它一般被放置在 frame 裏面。在繼承它的父類 wxWindow 的基礎上,Panel 還含有一些額外的,細微的功能性。Panel 的主要目的是在功能性和外觀上和對話框類似,可是又有做爲父窗口的靈活性。

事實上, 對於那些處理文字錄入的對象(一般被稱做控件或組件)來講,Panel 就是個灰色的背景。

[8] label 的做用僅僅是顯示文本,並不和用戶進行交互。

添加更多的控件

你能夠在 wxPython 的 demo 和 docs 中種類繁多的控件,可是本文將只會講解其中最經常使用的幾種:

  • wxButton 是最基本的控件: 它是一個你能夠點擊的按鈕,並帶有文字。下面是一個 「Clear」 按鈕的例子(比方說,你點擊以後會清空文字):
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
  • wxTextCtrl 這個控件可讓用戶輸入文字,它產生 2 種主要的事件:若是文字被改變了,它會調用 EVT_TEXT ;若是鍵盤被按下,它會調用 EVT_CHAR。根據下面的例子,若是你按下了 「Clear」 按鈕,將只會產生一個 EVT_TEXT 事件,而不會產生 EVT_CHAR 事件。
textField = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnChange, textField)
self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
  • wxComboBox 下拉菜單,和 wxTextCtrl 很像,可是除了 EVT_TEXT 和 EVT_CHAR 以外,wxComboBox 還可以生成 EVT_COMBOBOX 事件. ComboBox 能夠是 「下拉菜單+複選框」 , 能夠是 「下拉菜單+表格」…能夠點擊這裏查看 ComboBox 的示例,雖然是 C# 寫的,但 ComboBox 的概念是相同的。
  • wxCheckBox 複選框,可讓用戶作出 true/false 的選擇
  • wxRadioBox 單選框,可讓用戶從一個列表中作出選擇

如今讓咱們來豐富咱們的程序:

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """

import wx
class ExamplePanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))

        # 這個多行的文本框只是用來記錄並顯示events,不要糾結之
        self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), 
                                  style=wx.TE_MULTILINE | wx.TE_READONLY)

        # 一個按鈕 
        self.button = wx.Button(self, label='Save', pos=(200, 325))
        self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)

        # 僅有1行的編輯控件
        self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
        self.editname = wx.TextCtrl(self, value='Enter here your name:',
                                    pos=(150, 60), size=(140, -1))
        self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
        self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)

        # 一個ComboBox控件(下拉菜單)
        self.sampleList = ['friends', 'advertising', 'web search', \
                           'Yellow Pages']
        self.lblhear = wx.StaticText(self, label="How did you hear from us ?", 
                                     pos=(20, 90))
        self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), 
                                    choices=self.sampleList, 
                                    style=wx.CB_DROPDOWN)
        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
        # 注意ComboBox也綁定了EVT_TEXT事件
        self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)

        # 複選框
        self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?",
                                  pos=(20,180))
        self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)

        # 單選框
        radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', \
                     'navy blue', 'black', 'gray']
        self.rb = wx.RadioBox(label="What color would you like ?", 
                              pos=(20, 210), choices=radioList, \
                              majorDimension=3, style=wx.RA_SPECIFY_COLS)
        self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)

    def OnClick(self, event):
        self.logger.AppendText('Click on object with Id %d\n' % \
                               event.GetId())
    def EvtText(self, event):
        self.logger.AppendText(self, 'EvtText: %s\n' % event.GetString())
    def EvtChar(self, event):
        self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
        event.Skip()
    def EvtComboBox(self, event):
        self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
    def EvtCheckBox(self, event):
        self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
    def EvtRadioBox(self, event):
        self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())


app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
app.MainLoop()

如今咱們的 class 變得很複雜,咱們添加了不少的控件,而且它們是能夠交互的。咱們還添加了一個 wxTextCtrl 控件來顯示其它控件產生的事件:
這裏寫圖片描述

The notebook

有時候,一個表單(form)太大了,沒法在一頁內完整的顯示。這時候就要用到wxNoteBook,它容許用戶經過點擊標籤在幾個頁面之間快速的瀏覽。咱們先把wxNoteBook 放進 frame,而後再用AddPage 把上面的 Panel 放進wxNoteBook:

app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)

# 這裏把ExamplePanel重複3次放進Notebook
nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")

frame.Show()
app.MainLoop()

NOTE: 如今 ExamplePanel 的父窗口是 Notebook 了,這很關鍵。

使用 sizer 佈局元素

嚴格的定義每一個元素的位置並不會帶來理想的顯示效果,由於老是有不少緣由致使 frame 的尺寸並非咱們但願的那樣的大小。上文咱們已經講解過wx.BoxSizer, wx.GridSizer, 和wx.FlexGridSizer, 如今咱們再介紹一種:wx.GridBagSizer. 你必定用過Excel,必定作過「合併單元格」的操做吧?對了,wxGridBagSizer 就是合併單元格以後的wxGridSizerGridBagSizer介紹GridBadSizer教程

在下面採用了 GridBagSizer 的例子中,」pos」 參數控制組件放置的座標位置,(0, 0) 意味着組件緊貼在左上角,而 (3, 5) 則意味着組件要再向下 3 行,再向右 5 列。」span」 就是合併單元格的參數:

# -*- coding: utf-8 -*-
""" http://blog.csdn.net/chenghit """

import wx
class ExamplePanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)

        # 建立一些Sizer
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        grid = wx.GridBagSizer(hgap=5, vgap=5)    # 行和列的間距是5像素
        hSizer = wx.BoxSizer(wx.HORIZONTAL)

        self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))
        grid.Add(self.quote, pos=(0,0))    # 加入GridBagSizer

        self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)

        self.button = wx.Button(self, label='Save', pos=(200, 325))
        self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)

        self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
        grid.Add(self.lblname, pos=(1,0))
        self.editname = wx.TextCtrl(self, value='Enter here your name:', pos=(150, 60), size=(140, -1))
        grid.Add(self.editname, pos=(1,1))
        self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
        self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)

        # 向GridBagSizer中填充空白的空間
        grid.Add((10, 40), pos=(2,0))


        self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
        self.lblhear = wx.StaticText(self, label="How did you hear from us ?", pos=(20, 90))
        grid.Add(self.lblhear, pos=(3,0))
        self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), choices=self.sampleList, style=wx.CB_DROPDOWN)
        grid.Add(self.edithear, pos=(3,1))
        self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
        self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)

        self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?", pos=(20,180))
        # 加入Sizer的同時,設置對齊方式和邊距
        grid.Add(self.insure, pos=(4,0), span=(1,2), flag=wx.BOTTOM, border=5)
        self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)

        radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
        self.rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList, 
                              majorDimension=3, style=wx.RA_SPECIFY_COLS)
        grid.Add(self.rb, pos=(5,0), span=(1,2))    # 合併了1行2列的單元格
        self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)

        hSizer.Add(grid, 0, wx.ALL, 5)
        hSizer.Add(self.logger)
        mainSizer.Add(hSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.button, 0, wx.CENTER)
        # 能夠把SetSizer()和sizer.Fit()合併成一條SetSizerAndFit()語句
        self.SetSizerAndFit(mainSizer)

    def OnClick(self, event):
        self.logger.AppendText('Click on object with Id %d\n' % event.GetId())
    def EvtText(self, event):
        self.logger.AppendText('EvtText: %s\n' % event.GetString())
    def EvtChar(self, event):
        self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
        event.Skip()
    def EvtComboBox(self, event):
        self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
    def EvtCheckBox(self, event):
        self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
    def EvtRadioBox(self, event):
        self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())


app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)

nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")

frame.Show()
app.MainLoop()

對用戶的動做進行回饋

原文基本上到這裏就結束了,後面的 Drawing 相關的內容只是列出了標題,卻沒有介紹。若是要編寫小遊戲,這部份內容是很關鍵的,太遺憾了。

先發出來,附加的內容再慢慢補充。