本篇內容來自https://hekaiyou.blog.csdn.net/article/details/107360100
Python3 wxPython庫
這個第三方庫用於開發跨平台的 GUI 應用程序,可以輕松地創建健壯、功能強大的 GUI 程序。通過 pip install wxPython
命令下載 wxPython 庫。
Hello World
下面是業余版本的 Hello World:
# 導入wxPython庫 import wx # 創建一個應用程序對象 app = wx.App() # 創建一個框架 frm = wx.Frame(None, title="Hello World") # 展示框架 frm.Show() # 啟動事件循環 app.MainLoop()
下面是專業版本的 Hello World Pro:

import wx class HelloWorldPro(wx.Frame): """ Hello World Pro """ def __init__(self, *args, **kw): # 確保父類的 __init__ 被調用 super(HelloWorldPro, self).__init__(*args, **kw) # 在框架中創建一個面板 pnl = wx.Panel(self) # 在上面放一個大號的靜態文本 st = wx.StaticText(pnl, label="Hello World Pro!") font = st.GetFont() font.PointSize += 10 font = font.Bold() st.SetFont(font) # 創建一個大小調整器來管理子控件的布局 sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(st, wx.SizerFlags().Border(wx.TOP | wx.LEFT, 25)) pnl.SetSizer(sizer) # 創建菜單欄 self.make_menu_bar() # 創建狀態欄 self.CreateStatusBar() self.SetStatusText("狀態欄") def make_menu_bar(self): """ 菜單欄由菜單組成,菜單由菜單項組成。此方法將構建一組菜單,並綁定選擇菜單項時要調用的處理函數。 """ # 使用 "Hello" 和 "退出" 項目創建 "文件" 菜單 file_menu = wx.Menu() # 語法 "\t..." 定義了一個快捷鍵 hello_item = file_menu.Append(-1, "&Hello...\tCtrl-H", "此菜單項在狀態欄中顯示的幫助信息") file_menu.AppendSeparator() # 使用 Stock ID 時,無需指定菜單項的標簽 # https://docs.wxpython.org/stock_items.html exit_item = file_menu.Append(wx.ID_EXIT) # 只有一個 "關於" 項目的 "幫助" 菜單 help_menu = wx.Menu() about_item = help_menu.Append(wx.ID_ABOUT) # 制作菜單欄,然后向其中添加兩個菜單 menu_bar = wx.MenuBar() menu_bar.Append(file_menu, "&文件") menu_bar.Append(help_menu, "&幫助") # 將菜單欄移至框架 self.SetMenuBar(menu_bar) # 將每個菜單項的處理函數與 EVT_MENU 事件關聯 self.Bind(wx.EVT_MENU, self.on_hello, hello_item) self.Bind(wx.EVT_MENU, self.on_exit, exit_item) self.Bind(wx.EVT_MENU, self.on_about, about_item) def on_exit(self, event): """關閉框架,終止應用程序。""" self.Close(True) def on_hello(self, event): """顯示Hello對話框。""" wx.MessageBox("Hello World Pro!") def on_about(self, event): """顯示關於對話框""" wx.MessageBox("這是一個wxPython的演示Demo", "關於Hello World Pro", wx.OK | wx.ICON_INFORMATION) if __name__ == '__main__': # 創建應用和框架 app = wx.App() frm = HelloWorldPro(None, title='Hello World Pro') # 顯示框架並啟動事件循環 frm.Show() app.MainLoop()
布局管理
絕對定位
該定位是以像素為單位對控件進行定位,但是該定位方式在整窗口大小時,控件的尺寸和位置不會隨之改變,不推薦使用。
class Example(wx.Frame): def __init__(self, parent): super(Example, self).__init__(parent, title='絕對定位', size=(260, 180)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self, -1) # 使用絕對定位,x=3、y=3,寬度250px、高度150px wx.TextCtrl(panel, pos=(3, 3), size=(250, 150))
Sizers
該定位比使用絕對定位更通用更靈活,可供選擇的 Sizers 類型有:wx.BoxSizer
、wx.StaticBoxSizer
、wx.GridSizer
、wx.FlexGridSizer
、wx.GridBagSizer
。
class Example(wx.Frame): """ 把 wx.TextCtrl 放入 wx.Frame,它有一個內置的 sizer,但是只允許放置一個控件,多於一個的話會得到混亂的布局, 放入的子控件占用了所有剩余空間,除去邊框、菜單、工具欄和狀態欄 """ def __init__(self, parent): super(Example, self).__init__(parent, title='內置的Sizer', size=(260, 180)) self.InitUI() self.Centre() self.Show() def InitUI(self): menubar = wx.MenuBar() filem = wx.Menu() editm = wx.Menu() helpm = wx.Menu() menubar.Append(filem, '&文件') menubar.Append(editm, '&編輯') menubar.Append(helpm, '&幫助') self.SetMenuBar(menubar) # 沒有顯式的 sizer 定義 wx.TextCtrl(self)
BoxSizer
該定位按行或者列來排列多個控件,同時也允許 Sizer
的嵌套,這時可以構造出非常復雜的布局。
box = wx.BoxSizer(integer orient)
參數 orient 代表方向:
- wx.VERTICAL – 豎直
- wx.HORIZONTAL – 水平
box.Add(wx.Window window, integer proportion=0, integer flag=0, integer border=0)
參數 proportion
表示在給定的方向中,控件按照什么比例來調整大小:
- 0 – 表示默認,不改變控件大小
- 1 – 表示控件以1倍調整大小
- 大於1 – 表示控件以1的N倍調整大小
參數 flag
更具體的定義控件在 wx.BoxSizer
中的行為,通過它可以控制控件之間的距離,因而需要對不同方向的邊界進行定義,不同方向之間可以通過豎線符號 |
組合,可選的方向為:
- wx.LEFT – 左
- wx.RIGHT – 右
- wx.BOTTOM – 底部
- wx.TOP – 頂部
- wx.ALL – 周圍
如果使用 wx.EXPAND
標記,控件將使用所有剩余的空間。同樣也可以定義控件的對齊方式,可選以下選項:
- wx.ALIGN_LEFT – 左對齊
- wx.ALIGN_RIGHT – 右對齊
- wx.ALIGN_TOP – 頂部對齊
- wx.ALIGN_BOTTOM – 底部對齊
- wx.ALIGN_CENTER_VERTICAL – 豎直居中對齊
- wx.ALIGN_CENTER_HORIZONTAL – 水平居中對齊
- wx.ALIGN_CENTER – 居中對齊
Demo 0
class Example(wx.Frame): """在 Panel(面板)四周設置一些空間""" def __init__(self, parent): super(Example, self).__init__(parent, title='wx.BoxSizer', size=(260, 180)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self) panel.SetBackgroundColour('#4f5049') vbox = wx.BoxSizer(wx.VERTICAL) midPan = wx.Panel(panel) midPan.SetBackgroundColour('#ededed') # 使用 wx.EXPAND 標記,控件將使用所有剩余的空間 vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 20) panel.SetSizer(vbox)
Demo 1
class Example(wx.Frame): """創建豎直的 Sizer,並將5個水平 Sizer 放置其中""" def __init__(self, parent): super(Example, self).__init__(parent, title='wx.BoxSizer Pro', size=(390, 350)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self) # 系統默認的字體大小是10,這里顯示會過大,將其設置為9 font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT) font.SetPointSize(9) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) st1 = wx.StaticText(panel, label='類名') st1.SetFont(font) hbox1.Add(st1, flag=wx.RIGHT, border=8) tc = wx.TextCtrl(panel) hbox1.Add(tc, proportion=1) vbox.Add(hbox1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10) vbox.Add((-1, 10)) hbox2 = wx.BoxSizer(wx.HORIZONTAL) st2 = wx.StaticText(panel, label='匹配類') st2.SetFont(font) hbox2.Add(st2) vbox.Add(hbox2, flag=wx.LEFT | wx.TOP, border=10) vbox.Add((-1, 10)) hbox3 = wx.BoxSizer(wx.HORIZONTAL) tc2 = wx.TextCtrl(panel, style=wx.TE_MULTILINE) hbox3.Add(tc2, proportion=1, flag=wx.EXPAND) # 雖然可以通過結合邊框相關的 flag 參數來控制控件之間的距離,但問題是 Add() 函數只允許設置一個邊框數值, # 這意味着只能給幾個方向一樣大小的邊框,這種方法是無法做到左右邊框設置為 10px,而底部設置為 25px 的情況 vbox.Add(hbox3, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=10) # 如果需要不同的邊框值,可以增加額外的占位空間,如 vbox.Add((-1, 25)) 即是插入占位空間的語句, # (-1,25) 分別代表寬度和長度,如果某個數值為 -1 則表明不關注該方向 vbox.Add((-1, 25)) hbox4 = wx.BoxSizer(wx.HORIZONTAL) cb1 = wx.CheckBox(panel, label='區分大小寫') cb1.SetFont(font) hbox4.Add(cb1) cb2 = wx.CheckBox(panel, label='嵌套類') cb2.SetFont(font) hbox4.Add(cb2, flag=wx.LEFT, border=10) cb3 = wx.CheckBox(panel, label='非項目類') cb3.SetFont(font) hbox4.Add(cb3, flag=wx.LEFT, border=10) vbox.Add(hbox4, flag=wx.LEFT, border=10) vbox.Add((-1, 25)) hbox5 = wx.BoxSizer(wx.HORIZONTAL) btn1 = wx.Button(panel, label='確定', size=(70, 30)) hbox5.Add(btn1) btn2 = wx.Button(panel, label='關閉', size=(70, 30)) hbox5.Add(btn2, flag=wx.LEFT | wx.BOTTOM, border=5) # 在窗口的右側放置了兩個 Button,實現這個需求要三個參數:proportion、align flag 和 wx.EXPAND 標記, # proportion 必須設置為 0 ,表示在通過鼠標調整窗口大小時,Button 不允許更改大小, # 一定不能設置 wx.EXPAND 標記,因為 Button 只允許出現在被分配的位置上, # 最后必須設置 wx.ALIGN_RIGHT 標記,水平 Sizer 中的右對齊可以滿足要求 vbox.Add(hbox5, flag=wx.ALIGN_RIGHT | wx.RIGHT, border=10) panel.SetSizer(vbox)
GridSizer
該定位即網格布局,它可以在兩維的表格中放置控件。
class Example(wx.Frame): # 創建一個計算器的框架 def __init__(self, parent): super(Example, self).__init__(parent, title='wx.GridSizer', size=(300, 250)) self.InitUI() self.Centre() self.Show() def InitUI(self): vbox = wx.BoxSizer(wx.VERTICAL) self.display = wx.TextCtrl(self, style=wx.TE_RIGHT) vbox.Add(self.display, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=4) # 網格布局的構造函數中,我們可以定義表格的行列數,以及單元格之間的橫豎間距 # wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0) gs = wx.GridSizer(5, 4, 5, 5) # 使用 AddMany() 方法,便於一次性插入多個控件 gs.AddMany([(wx.Button(self, label='Cls'), 0, wx.EXPAND), (wx.Button(self, label='Bck'), 0, wx.EXPAND), # 在 Bck 和 Close 兩個按鈕之間放了一個空的 wx.StaticText,來達到分隔的目的 (wx.StaticText(self), wx.EXPAND), (wx.Button(self, label='Close'), 0, wx.EXPAND), (wx.Button(self, label='7'), 0, wx.EXPAND), (wx.Button(self, label='8'), 0, wx.EXPAND), (wx.Button(self, label='9'), 0, wx.EXPAND), (wx.Button(self, label='/'), 0, wx.EXPAND), (wx.Button(self, label='4'), 0, wx.EXPAND), (wx.Button(self, label='5'), 0, wx.EXPAND), (wx.Button(self, label='6'), 0, wx.EXPAND), (wx.Button(self, label='*'), 0, wx.EXPAND), (wx.Button(self, label='1'), 0, wx.EXPAND), (wx.Button(self, label='2'), 0, wx.EXPAND), (wx.Button(self, label='3'), 0, wx.EXPAND), (wx.Button(self, label='-'), 0, wx.EXPAND), (wx.Button(self, label='0'), 0, wx.EXPAND), (wx.Button(self, label='.'), 0, wx.EXPAND), (wx.Button(self, label='='), 0, wx.EXPAND), (wx.Button(self, label='+'), 0, wx.EXPAND)]) vbox.Add(gs, proportion=1, flag=wx.EXPAND) self.SetSizer(vbox)
FlexGridSizer
該定位與網格布局(wx.GridSizer
)類似,同樣以兩維的表格方式放置控件,但 wx.FlexGridSizer
更靈活一些。wx.GridSizer
的單元格大小都一樣,wx.FlexGridSizer
的單元格僅限制每行的單元格高度一致、每列的單元格寬度一致,不需要所有行列的寬高一致。
class Example(wx.Frame): """創建一個評論窗口""" def __init__(self, parent): super(Example, self).__init__(parent, title='wx.FlexGridSizer', size=(300, 250)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self) # 創建一個水平的 Sizer hbox = wx.BoxSizer(wx.HORIZONTAL) # wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0) # rows 和 cols 定義行數和列數,vgap 和 hgap 定義兩個方向的控件的間距 fgs = wx.FlexGridSizer(3, 2, 9, 25) title = wx.StaticText(panel, label="標題") author = wx.StaticText(panel, label="作者") review = wx.StaticText(panel, label="評論") tc1 = wx.TextCtrl(panel) tc2 = wx.TextCtrl(panel) tc3 = wx.TextCtrl(panel, style=wx.TE_MULTILINE) # 使用 AddMany() 添加控件到 Sizer 中,wx.FlexGridSizer 和 wx.GridSizer 都有這個方法 fgs.AddMany( [(title), (tc1, 1, wx.EXPAND), (author), (tc2, 1, wx.EXPAND), (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)]) # 讓第三行和第二列為可增長的,這使得窗口變化大小時,TextCtrl 也會跟着增長, # 前兩個 TextCtrl 的寬度會增長,第三個會在兩個方向都增長(注意:需要添加 wx.EXPAND 標記) fgs.AddGrowableRow(2, 1) fgs.AddGrowableCol(1, 1) # 在控件表格周圍放置 15px 的空間 hbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) panel.SetSizer(hbox)
GridBagSizer
該定位是 Sizer
布局中最復雜的,可實現精確定位,還可以跨行或者跨列。
wx.GridBagSizer(integer vgap, integer hgap)
豎直和水平的 gap
參數定義了所有子控件之間的間隔,使用 Add()
添加元素。
Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, integer border=0, userData=None)
- pos – 定義了位置,左上角的位置為 (0,0)
- span – 表示跨幾行或者列,比如 (3,2) 表示讓一個控件跨3行和2列
- flag – 更具體的定義控件在 Sizer 中的行為(參考 BoxSizer)
- border – 在控件周圍的空間
AddGrowableRow(integer row) AddGrowableCol(integer col)
在窗口大小改變時,Grid
中的控件可以保持大小不變,也可以隨窗口改變,如果想讓它增長或者收縮,可以使用上面的兩個方法。
Demo 0
class Example(wx.Frame): """創建一個大的 Grid 表""" def __init__(self, parent): super(Example, self).__init__(parent, title='wx.GridBagSizer', size=(320, 130)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self) sizer = wx.GridBagSizer(4, 4) # "重命名為" 文本將被放置在左上角,所以設置了 (0,0) 位置,另外在頂部、左邊和底部增加了 5px 的間隔空間 text = wx.StaticText(panel, label="重命名為") sizer.Add(text, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5) # wx.TextCtrl 從第二行開始,從0開始計數,它占據了1行和5列:(1,5),放置了 5px 的左右邊框空間 tc = wx.TextCtrl(panel) sizer.Add(tc, pos=(1, 0), span=(1, 5), flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) buttonOk = wx.Button(panel, label="Ok", size=(90, 28)) buttonClose = wx.Button(panel, label="Close", size=(90, 28)) # 在第四行放置了2個 Button,第三行是空的,所以 wx.TextCtrl 和 Button 之間留有間隔, # 把 OK 按鈕放在第四列,close 按鈕放在第五列。需要注意,一旦給一個控件應用了邊框,整行都會受到影響, # 這是沒有為 OK 按鈕設置底部邊框空間的原因。因為在 wx.GridBagSizer 的構造函數中,已經設置了所有控件之間的間隔, # 所以在兩個按鈕之間沒有放置任何空間。 sizer.Add(buttonOk, pos=(3, 3)) sizer.Add(buttonClose, pos=(3, 4), flag=wx.RIGHT | wx.BOTTOM, border=5) # 最后需要讓對話框可增長,讓第二列和第三行可增長,現在可以放大或者縮小窗口,注釋掉則不能自動縮放 sizer.AddGrowableCol(1) sizer.AddGrowableRow(2) panel.SetSizerAndFit(sizer)
Demo 1
class Example(wx.Frame): """創建一個更復雜的布局,同時使用了 wx.GridBagSizer 和 wx.StaticBoxSizer""" def __init__(self, parent): super(Example, self).__init__(parent, title='wx.GridBagSizer Pro', size=(450, 350)) self.InitUI() self.Centre() self.Show() def InitUI(self): panel = wx.Panel(self) sizer = wx.GridBagSizer(5, 5) text1 = wx.StaticText(panel, label="Java 類") sizer.Add(text1, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15) # 在第一行右側放了一個 wx.StaticBitmap icon = wx.StaticBitmap(panel, bitmap=wx.Bitmap('exec.png')) sizer.Add(icon, pos=(0, 4), flag=wx.TOP | wx.RIGHT | wx.ALIGN_RIGHT, border=5) # 創建一條分隔線,來分隔布局中不同組的控件 line = wx.StaticLine(panel) sizer.Add(line, pos=(1, 0), span=(1, 5), flag=wx.EXPAND | wx.BOTTOM, border=10) text2 = wx.StaticText(panel, label="名稱") sizer.Add(text2, pos=(2, 0), flag=wx.LEFT, border=10) tc1 = wx.TextCtrl(panel) sizer.Add(tc1, pos=(2, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND) text3 = wx.StaticText(panel, label="包") sizer.Add(text3, pos=(3, 0), flag=wx.LEFT | wx.TOP, border=10) tc2 = wx.TextCtrl(panel) sizer.Add(tc2, pos=(3, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND, border=5) button1 = wx.Button(panel, label="瀏覽...") sizer.Add(button1, pos=(3, 4), flag=wx.TOP | wx.RIGHT, border=5) text4 = wx.StaticText(panel, label="繼承") sizer.Add(text4, pos=(4, 0), flag=wx.TOP | wx.LEFT, border=10) combo = wx.ComboBox(panel) sizer.Add(combo, pos=(4, 1), span=(1, 3), flag=wx.TOP | wx.EXPAND, border=5) button2 = wx.Button(panel, label="瀏覽...") sizer.Add(button2, pos=(4, 4), flag=wx.TOP | wx.RIGHT, border=5) # wxStaticBoxSizer 和 wx.BoxSizer 類似,但它在 Sizer 周圍添加了一個靜態的盒子,在盒子中放入了 Check 選項 sb = wx.StaticBox(panel, label="可選屬性") boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL) boxsizer.Add(wx.CheckBox(panel, label="公有"), flag=wx.LEFT | wx.TOP, border=5) boxsizer.Add(wx.CheckBox(panel, label="生成默認構造函數"), flag=wx.LEFT, border=5) boxsizer.Add(wx.CheckBox(panel, label="生成 Main 方法"), flag=wx.LEFT | wx.BOTTOM, border=5) sizer.Add(boxsizer, pos=(5, 0), span=(1, 5), flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10) button3 = wx.Button(panel, label='幫助') sizer.Add(button3, pos=(7, 0), flag=wx.LEFT, border=10) button4 = wx.Button(panel, label="確定") sizer.Add(button4, pos=(7, 3)) button5 = wx.Button(panel, label="取消") sizer.Add(button5, pos=(7, 4), span=(1, 1), flag=wx.BOTTOM | wx.RIGHT, border=5) sizer.AddGrowableCol(2) panel.SetSizer(sizer)
控件
Button
該控件僅包含一個文本字符串,用來觸發某個動作。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # 創建一個 Close 按鍵,點擊 Close 即可關閉應用 cbtn = wx.Button(pnl, label='Close', pos=(20, 30)) # 按鈕的文本標簽以及它在面板上的位置 cbtn.Bind(wx.EVT_BUTTON, self.OnClose) self.SetSize((250, 200)) self.SetTitle('wx.Button') self.Centre() self.Show(True) def OnClose(self, e): # 調用 Close() 函數來關閉應用 self.Close(True)
ToggleButton
該控件也是一種按鈕,但它有兩個狀態:點擊和非點擊狀態。通過點擊按鍵可以在兩種狀態中切換,在特定場景中,這一功能將非常適用。
class Example(wx.Frame): """ 創建了紅色、綠色和藍色的 Toggle button 和一個 Panel, 點擊 toggle button的時候,可以改變 Panel 的顏色。 """ def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) self.col = wx.Colour(0, 0, 0) # 創建一個 wx.ToggleButton 控件 rtb = wx.ToggleButton(pnl, label='red', pos=(20, 25)) gtb = wx.ToggleButton(pnl, label='green', pos=(20, 60)) btb = wx.ToggleButton(pnl, label='blue', pos=(20, 100)) # 創建一個 Panel,顏色設置為 self.col self.cpnl = wx.Panel(pnl, pos=(150, 20), size=(110, 110)) self.cpnl.SetBackgroundColour(self.col) # 點擊 rtb 觸發這個 button 的時候 ToggleRed() 會被調用 rtb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleRed) gtb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleGreen) btb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleBlue) self.SetSize((300, 200)) self.SetTitle('Toggle buttons') self.Centre() self.Show(True) def ToggleRed(self, e): """對 rtb 按鈕是否被按下做出反應,來改變特定面板的顏色""" obj = e.GetEventObject() isPressed = obj.GetValue() green = self.col.Green() blue = self.col.Blue() if isPressed: self.col.Set(255, green, blue) else: self.col.Set(0, green, blue) self.cpnl.SetBackgroundColour(self.col) self.cpnl.Refresh() def ToggleGreen(self, e): obj = e.GetEventObject() isPressed = obj.GetValue() red = self.col.Red() blue = self.col.Blue() if isPressed: self.col.Set(red, 255, blue) else: self.col.Set(red, 0, blue) self.cpnl.SetBackgroundColour(self.col) self.cpnl.Refresh() def ToggleBlue(self, e): obj = e.GetEventObject() isPressed = obj.GetValue() red = self.col.Red() green = self.col.Green() if isPressed: self.col.Set(red, green, 255) else: self.col.Set(red, green, 0) self.cpnl.SetBackgroundColour(self.col) self.cpnl.Refresh()
StaticLine
該控件在窗口上展示一個簡單的直線,可以是豎直或水平的。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD) heading = wx.StaticText(self, label='The Central Europe', pos=(130, 15)) heading.SetFont(font) wx.StaticLine(self, pos=(25, 50), size=(300, 1)) wx.StaticText(self, label='Slovakia', pos=(25, 80)) wx.StaticText(self, label='Hungary', pos=(25, 100)) wx.StaticText(self, label='Poland', pos=(25, 120)) wx.StaticText(self, label='5 445 000', pos=(250, 80)) wx.StaticText(self, label='10 014 000', pos=(250, 100)) wx.StaticText(self, label='38 186 000', pos=(250, 120)) wx.StaticLine(self, pos=(25, 160), size=(300, 1)) tsum = wx.StaticText(self, label='164 336 000', pos=(240, 180)) sum_font = tsum.GetFont() sum_font.SetWeight(wx.BOLD) tsum.SetFont(sum_font) btn = wx.Button(self, label='Close', pos=(140, 210)) btn.Bind(wx.EVT_BUTTON, self.OnClose) self.SetSize((360, 280)) self.SetTitle('wx.StaticLine') self.Centre() self.Show(True) def OnClose(self, e): self.Close(True)
StaticText
該控件在窗口上展示展示一行或多行的只讀文本。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): # 要在 wx.StaticText 展示的字符串 txt1 = '''第一段第1行 第一段第2行 體現居中效果 第一段第3行''' txt2 = '''第二段第1行 第二段第2行 第二段第3行 體現居中效果''' pnl = wx.Panel(self) vbox = wx.BoxSizer(wx.VERTICAL) # 創建 wx.StaticText 控件,文字居中展示 st1 = wx.StaticText(pnl, label=txt1, style=wx.ALIGN_CENTRE) st2 = wx.StaticText(pnl, label=txt2, style=wx.ALIGN_CENTRE) vbox.Add(st1, flag=wx.ALL, border=5) vbox.Add(st2, flag=wx.ALL, border=5) pnl.SetSizer(vbox) self.SetSize((220, 180)) self.SetTitle('wx.StaticText') self.Centre() self.Show(True)
StaticBox
該控件是一個裝飾控件,被用來邏輯上將一組控件包括起來。必須在它所包含的控件創建之前創建,且那些被包含的控件是 wx.StaticBox
的兄弟控件而非子控件。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) wx.StaticBox(pnl, label='個人信息', pos=(5, 5), size=(240, 170)) wx.CheckBox(pnl, label='男', pos=(15, 30)) wx.CheckBox(pnl, label='已婚', pos=(15, 55)) wx.StaticText(pnl, label='年齡', pos=(15, 95)) wx.SpinCtrl(pnl, value='1', pos=(55, 90), size=(60, -1), min=1, max=120) btn = wx.Button(pnl, label='Ok', pos=(90, 185), size=(60, -1)) btn.Bind(wx.EVT_BUTTON, self.OnClose) self.SetSize((270, 250)) self.SetTitle('Static box') self.Centre() self.Show(True) def OnClose(self, e): self.Close(True)
ComboBox
該控件是由一行文本域、一個帶有下拉箭頭圖標的按鈕和一個列表框所構成的。當你按下按鈕時,將出現一個列表框,用戶只可選擇其中的一個選項。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # 被選擇的選項將會顯示在文本標簽上 distros = ['Ubuntu', 'Arch', 'Fedora', 'Debian', 'Mint'] # 單選框將包含以上列表的字符串 # 創建 wx.ComboBox,通過 choices 參數傳入一個字符串列表,wx.CB_READONLY 使得列表的字符串只讀,即不可編輯 cb = wx.ComboBox(pnl, pos=(50, 30), choices=distros, style=wx.CB_READONLY) self.st = wx.StaticText(pnl, label='', pos=(50, 140)) # 當從單選框選擇一個選項時,wx.EVT_COMBOBOX 事件將被觸發,綁定 OnSelect() 來處理該事件 cb.Bind(wx.EVT_COMBOBOX, self.OnSelect) self.SetSize((250, 230)) self.SetTitle('wx.ComboBox') self.Centre() self.Show(True) def OnSelect(self, e): i = e.GetString() self.st.SetLabel(i)
CheckBox
該控件只有兩個狀態:打開或關閉,它有一個框和文本標簽組成,文本標簽可以設置為放在框的左邊或者右邊。當 wx.CheckBox
被選擇之后,框里將出現一個對號√。
class Example(wx.Frame): """通過一個 wx.CheckBox 控件來決定是否顯示或隱藏窗口的標題""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # wx.CheckBox 控件的構造函數 cb = wx.CheckBox(pnl, label='顯示標題', pos=(20, 20)) # 由於窗口的標題應該是被默認顯示的,所以通過 SetValue() 方法默認選擇 wx.CheckBox cb.SetValue(True) # 當點擊 wx.CheckBox 控件時,wx.EVT_CHECKBOX 事件將被觸發,將其綁定至事件處理器 ShowOrHideTitle() 函數 cb.Bind(wx.EVT_CHECKBOX, self.ShowOrHideTitle) self.SetSize((270, 120)) self.SetTitle('wx.CheckBox') self.Centre() self.Show(True) def ShowOrHideTitle(self, e): """通過 wx.CheckBox 的狀態來決定是否隱藏或顯示窗口的標題""" sender = e.GetEventObject() isChecked = sender.GetValue() if isChecked: self.SetTitle('wx.CheckBox') else: self.SetTitle('')
StatusBar
該控件展示應用的狀態信息,可以被分成不同的部分來展示不同的信息。也可以把其他控件插入到 wx.StatusBar
中,它可以作為對話框的替代選擇,預防對話框被濫用。可以通過兩種方式新建 wx.StatusBar
,可以直接創建 wx.StatusBar
然后調用 SetStatusBar()
函數,也可以簡單的調用 CreateStatusBar()
函數即可,第二種方法創建了一個默認的 wx.StatusBar
。
class Example(wx.Frame): """ 創建 wx.Frame 和5個其他的控件, 如果把鼠標懸停在控件上面,控件的名字將會被顯示在 wx.StatusBar 上 """ def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) button = wx.Button(pnl, label='Button', pos=(20, 20)) text = wx.CheckBox(pnl, label='CheckBox', pos=(20, 90)) combo = wx.ComboBox(pnl, pos=(120, 22), choices=['Python', 'Ruby']) slider = wx.Slider(pnl, 5, 6, 1, 10, (120, 90), (110, -1)) # 當鼠標進入到控件的區域時,EVT_ENTER_WINDOW 事件將被觸發 pnl.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter) button.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter) text.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter) combo.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter) slider.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter) self.sb = self.CreateStatusBar() self.SetSize((250, 230)) self.SetTitle('wx.Statusbar') self.Centre() self.Show(True) def OnWidgetEnter(self, e): # 得到鼠標進入的控件的名字 name = e.GetEventObject().GetClassName() # 使用 SetStatusText() 方法設置狀態欄的文字 self.sb.SetStatusText(name + ' widget') e.Skip()
RadioButton
該控件讓用戶從一組選項中選擇一個唯一選項,通過對第一個 RadioButton
設置 wx.RB_GROUP
樣式標記,可以將緊隨其后的其他 RadioButton
囊括為一組,隨后的 RadioButton
如果也被設置了 wx.RB_GROUP
樣式標記,那表明將開始新的一組選擇框。
class Example(wx.Frame): """創建一組3個 RadioButton,每個按鈕的狀態被顯示在狀態欄上""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # 創建3個 RadioButton,其中第一個被設置了 wx.RB_GROUP 樣式,表明接下來的 RadioButton 都是同一組 self.rb1 = wx.RadioButton(pnl, label='Value A', pos=(10, 10), style=wx.RB_GROUP) self.rb2 = wx.RadioButton(pnl, label='Value B', pos=(10, 30)) self.rb3 = wx.RadioButton(pnl, label='Value C', pos=(10, 50)) # 將 wx.EVT_RADIOBUTTON 事件綁定至 SetVal() 事件處理函數上 self.rb1.Bind(wx.EVT_RADIOBUTTON, self.SetVal) self.rb2.Bind(wx.EVT_RADIOBUTTON, self.SetVal) self.rb3.Bind(wx.EVT_RADIOBUTTON, self.SetVal) # 創建分三部分的 狀態欄,並根據對應 RadioButton 的狀態設置了初始文字 self.sb = self.CreateStatusBar(3) self.sb.SetStatusText("True", 0) self.sb.SetStatusText("False", 1) self.sb.SetStatusText("False", 2) self.SetSize((210, 210)) self.SetTitle('wx.RadioButton') self.Centre() self.Show(True) def SetVal(self, e): """對狀態欄的文本進行了更新""" state1 = str(self.rb1.GetValue()) state2 = str(self.rb2.GetValue()) state3 = str(self.rb3.GetValue()) self.sb.SetStatusText(state1, 0) self.sb.SetStatusText(state2, 1) self.sb.SetStatusText(state3, 2)
Gauge
該控件用在時間較長的任務場景,用來顯示當前任務的狀態。
TASK_RANGE = 50 class Example(wx.Frame): """創建一個 進度條(Gauge)和兩個按鈕,一個按鈕開始走進度條,一個按鈕停止走進度條。""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): # 使用了 wx.Timer 來在特定的時間區間來執行代碼,我們將在定義好的時間來更新進度條 self.timer = wx.Timer(self, 1) # count 變量用來決定目前任務已經完成的比例 self.count = 0 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer) pnl = wx.Panel(self) vbox = wx.BoxSizer(wx.VERTICAL) hbox1 = wx.BoxSizer(wx.HORIZONTAL) hbox2 = wx.BoxSizer(wx.HORIZONTAL) hbox3 = wx.BoxSizer(wx.HORIZONTAL) # wx.Gauge 控件的構造函數,range 參數定義了該控件最大的整數區間 self.gauge = wx.Gauge(pnl, range=TASK_RANGE, size=(250, 25)) self.btn1 = wx.Button(pnl, wx.ID_OK) self.btn2 = wx.Button(pnl, wx.ID_STOP) self.text = wx.StaticText(pnl, label='Task to be done') self.Bind(wx.EVT_BUTTON, self.OnOk, self.btn1) self.Bind(wx.EVT_BUTTON, self.OnStop, self.btn2) hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE) hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10) hbox2.Add(self.btn2, proportion=1) hbox3.Add(self.text, proportion=1) vbox.Add((0, 30)) vbox.Add(hbox1, flag=wx.ALIGN_CENTRE) vbox.Add((0, 20)) vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE) vbox.Add(hbox3, proportion=1, flag=wx.ALIGN_CENTRE) pnl.SetSizer(vbox) self.SetSize((300, 200)) self.SetTitle('wx.Gauge') self.Centre() self.Show(True) def OnOk(self, e): # 檢查 count 變量是否還在任務的整數區間內,如果不在,我們直接返回 if self.count == TASK_RANGE: return # 如果還在,表明任務還在繼續,我們開始 timer 定時器並更新靜態文本 self.timer.Start(100) self.text.SetLabel('Task in Progress') def OnStop(self, e): # 檢查各種條件 if self.count == 0 or self.count == TASK_RANGE or not self.timer.IsRunning(): return # 符合的話停止定時器並更新靜態文本 self.timer.Stop() self.text.SetLabel('Task Interrupted') def OnTimer(self, e): """ 在 timer 開始后被周期調用,在該方法內,更新 count 參數和進度條部件, 如果 count 等於 TASK_RANGE,停止 timer 並更新靜態文本 """ self.count = self.count + 1 self.gauge.SetValue(self.count) if self.count == TASK_RANGE: self.timer.Stop() self.text.SetLabel('Task Completed')
Slider
該控件有一個簡單的操作柄,可以向前或向后滑動,可以使用它完成特定的任務。
class Example(wx.Frame): """在 Slider 中選擇的值將被顯示在下面的靜態文本中。""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # 創建了 wx.Slider,在構造函數中,提供了它的初始位置,以及最大、最小的滑動位置,還有設定了它的水平方向 sld = wx.Slider(pnl, value=200, minValue=150, maxValue=500, pos=(20, 20), size=(250, -1), style=wx.SL_HORIZONTAL) # wx.EVT_SCROLL 事件被觸發的時候,將調用 OnSliderScroll() 函數 sld.Bind(wx.EVT_SCROLL, self.OnSliderScroll) # 當前 Slider 的值將被顯示在下方的靜態文本中 self.txt = wx.StaticText(pnl, label='200', pos=(20, 90)) self.SetSize((290, 200)) self.SetTitle('wx.Slider') self.Centre() self.Show(True) def OnSliderScroll(self, e): # 得到了事件的發送者並得到其當前被選擇的值 obj = e.GetEventObject() val = obj.GetValue() # 將其值設置到靜態文本中 self.txt.SetLabel(str(val))
SpinCtrl
該控件對一個值進行增加或減少,它有兩個按鈕,一個帶向上箭頭,一個帶向下箭頭。用戶可以直接輸入數值,也可以通過兩個箭頭來對數值進行上下增減。
class Example(wx.Frame): """將華氏溫度轉變為攝氏度,使用 wx.SpinCtrl 控件供用戶來選擇華氏溫度的值。""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): wx.StaticText(self, label='將華氏度轉換為攝氏度', pos=(20, 20)) wx.StaticText(self, label='華氏度: ', pos=(20, 80)) wx.StaticText(self, label='攝氏度: ', pos=(20, 150)) self.celsius = wx.StaticText(self, label='', pos=(150, 150)) # 創建一個初始值為0的 wx.SpinCtrl 控件,並通過 SetRange() 方法設置了該控件的取值范圍 self.sc = wx.SpinCtrl(self, value='0', pos=(150, 75), size=(60, -1)) self.sc.SetRange(-459, 1000) btn = wx.Button(self, label='計算', pos=(70, 230)) btn.SetFocus() cbtn = wx.Button(self, label='Close', pos=(185, 230)) btn.Bind(wx.EVT_BUTTON, self.OnCompute) cbtn.Bind(wx.EVT_BUTTON, self.OnClose) self.SetSize((350, 310)) self.SetTitle('wx.SpinCtrl') self.Centre() self.Show(True) def OnClose(self, e): self.Close(True) def OnCompute(self, e): """獲取用戶設定的華氏溫度值,並計算對應的攝氏溫度值,將其更新在靜態文本上。""" fahr = self.sc.GetValue() cels = round((fahr - 32) * 5 / 9.0, 2) self.celsius.SetLabel(str(cels))
ScrolledWindow
該控件用來設置窗口可視面積的大小,單位是像素,帶有縱向、橫向的滾動條。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): # 創建一個滾動條控件 scroller = wx.ScrolledWindow(self, -1) # 設置滾動條控件的大小 scroller.SetScrollbars(pixelsPerUnitX=1, pixelsPerUnitY=1, noUnitsX=1000, noUnitsY=800) pnl = wx.Panel(scroller) ms = wx.BoxSizer(wx.VERTICAL) pnl.SetSizer(ms) self.SetSize((270, 250)) self.SetTitle('wx.ScrolledWindow') self.Centre() self.Show(True)
對話框
常用對話框類和函數封裝了常用對話框的需求,它們都是 模態
的,抓住了控制流,直到用戶關閉對話框。
MessageDialog
該對話框顯示單行或多行消息,並帶有 OK
、Cancel
、Yes
和 No
按鈕的選擇。在 Windows 下,可以顯示可選圖標,例如感嘆號或問號。
Demo 0
dlg = wx.MessageDialog(None, '消息對話框內容', '標題信息', wx.OK) dlg.ShowModal() dlg.Destroy()
Demo 1
dlg = wx.MessageDialog(None, '消息對話框內容', '標題信息', wx.YES_NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: print('是') dlg.Destroy()
ColourDialog
該對話框向用戶顯示顏色選擇器,並返回顏色信息。
dlg = wx.ColourDialog(self) dlg.GetColourData().SetChooseFull(True) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetColourData().GetColour()) dlg.Destroy()
FontDialog
該對話框向用戶顯示字體選擇器,並返回字體和顏色信息。
dlg = wx.FontDialog(self, wx.FontData()) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetFontData().GetChosenFont()) dlg.Destroy()
FileDialog
該對話框向用戶彈出文件選擇器框,在 Windows 和 GTK 2.4+ 上,這是公共文件選擇器對話框,在 MacOS 中,這是一個文件選擇器框,功能有所減少。
Demo 0
filesFilter = "Dicom (*.dcm)|*.dcm|" "All files (*.*)|*.*" dlg = wx.FileDialog(self, message="選擇單個文件", wildcard=filesFilter, style=wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetPath()) dlg.Destroy()
Demo 1
filesFilter = "Dicom (*.dcm)|*.dcm|" "All files (*.*)|*.*" dlg = wx.FileDialog(self, message="多文件選擇", wildcard=filesFilter, style=wx.FD_OPEN | wx.FD_MULTIPLE) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetPaths()) dlg.Destroy()
Demo 2
filesFilter = "Dicom (*.dcm)|*.dcm|" "All files (*.*)|*.*" dlg = wx.FileDialog(self, message="保存文件", wildcard=filesFilter, style=wx.FD_SAVE) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetPath()) dlg.Destroy()
DirDialog
該對話框向用戶顯示一個目錄選擇器對話框,允許用戶選擇一個目錄。
dlg = wx.DirDialog(None, "選擇一個目錄:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetPath()) dlg.Destroy()
TextEntryDialog
該對話框是一個帶有文本輸入字段的對話框,使用 wx.TextEntryDialog.GetValue()
獲得用戶輸入的值。
dlg = wx.TextEntryDialog(None, "請在下面文本框中輸入內容:", "文本輸入框標題", "默認內容") if dlg.ShowModal() == wx.ID_OK: print(dlg.GetValue()) dlg.Destroy()
PasswordEntryDialog
該對話框是是一個帶有密碼輸入字段的對話框,使用 wx.TextEntryDialog.GetValue()
獲得用戶輸入的值。
dlg = wx.PasswordEntryDialog(None, "請輸入密碼:", "密碼輸入框標題", "默認密碼") if dlg.ShowModal() == wx.ID_OK: print(dlg.GetValue()) dlg.Destroy()
SingleChoiceDialog
該對話框顯示選項列表,以及 OK
和(可選)Cancel
,用戶可以選擇其中之一,可以從對話框中獲得索引,字符串或客戶數據的選擇。
dlg = wx.SingleChoiceDialog(None, "請選擇你喜歡的水果:", "列表選擇框標題", ["蘋果", "西瓜", "草莓"]) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetStringSelection()) dlg.Destroy()
MultiChoiceDialog
該對話框顯示選項列表,以及 OK
和(可選)Cancel
,用戶可以選擇其中一個或多個。
dlg = wx.MultiChoiceDialog(None, "請選擇幾種你喜歡的水果:", "列表多選框標題", ["蘋果", "西瓜", "草莓"]) if dlg.ShowModal() == wx.ID_OK: print(dlg.GetSelections()) dlg.Destroy()
表格
使用 Grid
及其相關類可以顯示和編輯表格數據,而且支持表單元格的自定義屬性,從而可以完全自定義其外觀,並使用單獨的網格表(GridTableBase
派生)類進行數據管理,這意味着它可用於顯示任意數量的數據。

import wx import wx.grid class GridFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent) # 創建一個 wxGrid 對象 grid = wx.grid.Grid(self, -1) # 調用 CreateGrid 設置網格的尺寸(在此示例中為50行和8列) grid.CreateGrid(100, 10) # 設置單個行和列的大小(以像素為單位) grid.SetRowSize(0, 60) grid.SetColSize(0, 120) # 將網格單元格內容設置為字符串 grid.SetCellValue(0, 0, 'wxGrid很好') # 指定某些單元格為只讀 grid.SetCellValue(0, 3, '這是只讀的') grid.SetReadOnly(0, 3) # 為網格單元格內容指定顏色 grid.SetCellValue(3, 3, '灰綠色') grid.SetCellTextColour(3, 3, wx.GREEN) grid.SetCellBackgroundColour(3, 3, wx.LIGHT_GREY) # 指定一些單元格將存儲數字值而不是字符串, # 在這里,將網格列5設置為保留以6的寬度和2的精度顯示的浮點值 grid.SetColFormatFloat(5, 6, 2) grid.SetCellValue(0, 6, '3.1415') self.Show() if __name__ == '__main__': app = wx.App(0) frame = GridFrame(None) app.MainLoop()
菜單
菜單是 GUI 應用中的通用部件,菜單欄由多項菜單組成,頂級菜單在菜單欄上顯示標簽,菜單包含菜單項,菜單項在應用中執行特定的命令,菜單也可以包含子菜單,子菜單自身又包含菜單項。
圖標與快捷鍵
class Example(wx.Frame): def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): # 新建一個 MenuBar 對象 menubar = wx.MenuBar() # 新建一個 Menu 對象 fileMenu = wx.Menu() # 創建一個 wx.MenuItem 對象,"&" 符號申明了快捷鍵,但是真正的快捷鍵由 "\t" 后面的字母組合定義, # 這里定義了 Ctrl+Q,如果用戶按下這一快捷鍵,應用就會退出 qmi = wx.MenuItem(fileMenu, id=1, text='&退出\tCtrl+Q') # 使用 SetBitmap() 函數,為菜單項提供圖標 qmi.SetBitmap(wx.Bitmap('exit.png')) # AppendItem 則將菜單項添加到菜單中 fileMenu.Append(qmi) # 綁定了菜單項的 wx.EVT_MENU 事件到自定義的 OnQuit() 函數(這里通過 id 來綁定,也可以通過菜單項對象來綁定) self.Bind(wx.EVT_MENU, self.OnQuit, id=1) # 將菜單加入到菜單欄中,"&" 符號創建了一個快捷鍵 menubar.Append(fileMenu, '&文件') # 調用 SetMenuBar() 方法,這一方法屬於 wx.Frame,它為 Frame 設定菜單欄 self.SetMenuBar(menubar) self.SetSize((250, 200)) self.SetTitle('圖標和快捷鍵') self.Centre() self.Show(True) def OnQuit(self, e): self.Close()
子菜單與分隔符
每個菜單可以包含子菜單,這樣可以把相似的命令放到同一組中,還可以通過分隔符來分割不同的命令,其實就是簡單的一條線。
class Example(wx.Frame): def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): menubar = wx.MenuBar() fileMenu = wx.Menu() # 創建 新建 菜單項 new = wx.MenuItem(fileMenu, wx.ID_NEW, '&新建\tCtrl+N') new.SetBitmap(wx.Bitmap('folder_new.png')) fileMenu.Append(new) # 創建 打開 菜單項 open = wx.MenuItem(fileMenu, wx.ID_OPEN, '&打開\tCtrl+O') open.SetBitmap(wx.Bitmap('fileopen.png')) fileMenu.Append(open) # 創建 保存 菜單項 save = wx.MenuItem(fileMenu, wx.ID_SAVE, '&保存\tCtrl+S') save.SetBitmap(wx.Bitmap('save_all.png')) fileMenu.Append(save) # AppendSeparator() 函數添加了分隔符 fileMenu.AppendSeparator() # 子菜單同樣也是 wx.Menu 對象,三個菜單項被添加到該菜單對象 imp = wx.Menu() # wx.ID_ANY 是獲取id的一種方法 imp.Append(wx.ID_ANY, '導入RSS源列表...') imp.Append(wx.ID_ANY, '導入書簽...') imp.Append(wx.ID_ANY, '導入郵件...') # 子菜單通過 AppendSubMenu() 方法被添加到文件菜單中 fileMenu.AppendSubMenu(imp, '導入') qmi = wx.MenuItem(fileMenu, wx.ID_EXIT, '&退出\tCtrl+Q') qmi.SetBitmap(wx.Bitmap('exit.png')) fileMenu.Append(qmi) self.Bind(wx.EVT_MENU, self.OnQuit, qmi) menubar.Append(fileMenu, '&文件') self.SetMenuBar(menubar) self.SetSize((350, 250)) self.SetTitle('子菜單和分隔符') self.Centre() self.Show(True) def OnQuit(self, e): self.Close()
Check菜單項
class Example(wx.Frame): """創建一個view菜單,它包括兩個 Check 菜單項,這兩個菜單項可以控制顯示或隱藏狀態欄和工具欄""" def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): menubar = wx.MenuBar() fileMenu = wx.Menu() viewMenu = wx.Menu() # 如果要添加一個 Check 菜單項,可以設定 kind 參數為 wx.ITEM_CHECK,該參數默認為 wx.ITEM_NORMAL, # Append()方法返回一個 wx.MenuItem self.shst = viewMenu.Append(wx.ID_ANY, '顯示狀態欄', '幫助:顯示狀態欄', kind=wx.ITEM_CHECK) self.shtl = viewMenu.Append(wx.ID_ANY, '顯示工具欄', '幫助:顯示工具欄', kind=wx.ITEM_CHECK) # 當應用開始的時候,狀態欄和工具欄都應可見,所以使用 Check() 方法勾選 Check 菜單項 viewMenu.Check(self.shst.GetId(), True) viewMenu.Check(self.shtl.GetId(), True) self.Bind(wx.EVT_MENU, self.ToggleStatusBar, self.shst) self.Bind(wx.EVT_MENU, self.ToggleToolBar, self.shtl) menubar.Append(fileMenu, '&文件') menubar.Append(viewMenu, '&視圖') self.SetMenuBar(menubar) self.toolbar = self.CreateToolBar() self.toolbar.AddTool(1, '', wx.Bitmap('folder_new.png')) self.toolbar.Realize() self.statusbar = self.CreateStatusBar() self.statusbar.SetStatusText('狀態欄') self.SetSize((350, 250)) self.SetTitle('Check菜單項') self.Centre() self.Show(True) def ToggleStatusBar(self, e): """通過 Check 菜單項的狀態來顯示或隱藏狀態欄""" # 通過 ISChecked() 函數來獲取check菜單項的狀態 if self.shst.IsChecked(): self.statusbar.Show() else: self.statusbar.Hide() def ToggleToolBar(self, e): if self.shtl.IsChecked(): self.toolbar.Show() else: self.toolbar.Hide()
Radio菜單項
class Example(wx.Frame): """創建一個view菜單,它包括兩個 Radio 菜單項,並只能選擇其中一個""" def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): menubar = wx.MenuBar() viewMenu = wx.Menu() # 如果要添加一個 Radio 菜單項,可以設定 kind 參數為 wx.ITEM_RADIO,該參數默認為 wx.ITEM_NORMAL self.shst = viewMenu.Append(wx.ID_ANY, '夜間視圖', kind=wx.ITEM_RADIO) self.shtl = viewMenu.Append(wx.ID_ANY, '日間視圖', kind=wx.ITEM_RADIO) menubar.Append(viewMenu, '&視圖') self.SetMenuBar(menubar) self.SetSize((350, 250)) self.SetTitle('Radio菜單項') self.Centre() self.Show(True)
上下文菜單
class MyPopupMenu(wx.Menu): """為主窗口創建上下文菜單,有兩個菜單項,一個用來最小化應用,另一個結束應用""" def __init__(self, parent): # 創建一個單獨的類叫做MyPopupMenu,它繼承自wx.Menu super(MyPopupMenu, self).__init__() self.parent = parent # 創建菜單項、添加到上下文菜單並綁定了事件處理函數 mmi = wx.MenuItem(self, wx.NewIdRef(), '最小化') self.Append(mmi) self.Bind(wx.EVT_MENU, self.OnMinimize, mmi) cmi = wx.MenuItem(self, wx.NewIdRef(), '關閉') self.Append(cmi) self.Bind(wx.EVT_MENU, self.OnClose, cmi) def OnMinimize(self, e): self.parent.Iconize() def OnClose(self, e): self.parent.Close() class Example(wx.Frame): def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): # 如果用戶在 Frame 中點擊右鍵,將調用 OnRightDown() 方法,這是通過綁定 wx.EVT_RIGHT_DOWN 事件來實現的 self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.SetSize((250, 200)) self.SetTitle('上下文菜單') self.Centre() self.Show(True) def OnRightDown(self, e): # 調用了 PopupMenu() 方法,這個方法來自於 wx.Frame,第一個參數是要顯示的菜單,第二個參數為顯示的位置, # 為了讓上下文菜單顯示在鼠標光標處,這里需要得到鼠標位置,事件對象的 GetPosition() 方法可以得到這一信息 self.PopupMenu(MyPopupMenu(self), e.GetPosition())
工具欄
單行工具欄
class Example(wx.Frame): """創建一個工具的工具欄,當用戶點擊這一工具時,程序將退出""" def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): # 創建 ToolBar,默認情況下,工具欄是水平、無邊框且顯示圖標的 toolbar = self.CreateToolBar() # 調用 AddTool() 方法來創建工具欄的工具。第一個參數為ID,第二個參數為工具的標簽,第三個為工具的圖標 qtool = toolbar.AddTool(wx.ID_ANY, '退出', wx.Bitmap('undo.png')) # 把工具項放到工具欄之后,調用 Realize() 方法。在 Linux 中,該方法的調用不是必須的,但在 Windows 卻是必須的 toolbar.Realize() # 使用 wx.EVT_TOOL 事件綁定器 self.Bind(wx.EVT_TOOL, self.OnQuit, qtool) self.SetSize((250, 200)) self.SetTitle('單行工具欄') self.Centre() self.Show(True) def OnQuit(self, e): self.Close()
多行工具欄
class Example(wx.Frame): """創建兩個水平的工具欄""" def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): vbox = wx.BoxSizer(wx.VERTICAL) # 第一個工具欄對象 toolbar1 = wx.ToolBar(self) toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('folder.png')) toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('folder_new.png')) toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('folder_sent_mail.png')) toolbar1.Realize() # 第二個工具欄對象 toolbar2 = wx.ToolBar(self) qtool = toolbar2.AddTool(wx.ID_EXIT, '', wx.Bitmap('undo.png')) toolbar2.Realize() vbox.Add(toolbar1, 0, wx.EXPAND) vbox.Add(toolbar2, 0, wx.EXPAND) self.Bind(wx.EVT_TOOL, self.OnQuit, qtool) self.SetSizer(vbox) self.SetSize((300, 250)) self.SetTitle('多行工具欄') self.Centre() self.Show(True) def OnQuit(self, e): self.Close()
禁用工具欄
class Example(wx.Frame): """有三個工具欄按鈕,一個按鈕用來退出應用,其余兩個按鈕的功能為撤銷和反撤銷,在程序中模擬了4次改變,撤銷和反撤銷時對應的啟用或禁用""" def __init__(self, *args, **kwargs): super(Example, self).__init__(*args, **kwargs) self.InitUI() def InitUI(self): self.count = 5 self.toolbar = self.CreateToolBar() tundo = self.toolbar.AddTool(wx.ID_UNDO, '', wx.Bitmap('previous.png')) tredo = self.toolbar.AddTool(wx.ID_REDO, '', wx.Bitmap('next.png')) # 程序開始時,撤銷按鈕是禁用的,調用 EnableTool() 函數,並傳遞 False 參數來實現 self.toolbar.EnableTool(wx.ID_REDO, False) # 調用 AddSeparator() 函數來分隔不同的工具項 self.toolbar.AddSeparator() texit = self.toolbar.AddTool(wx.ID_EXIT, '', wx.Bitmap('undo.png')) self.toolbar.Realize() self.Bind(wx.EVT_TOOL, self.OnQuit, texit) self.Bind(wx.EVT_TOOL, self.OnUndo, tundo) self.Bind(wx.EVT_TOOL, self.OnRedo, tredo) self.SetSize((250, 200)) self.SetTitle('禁用工具欄') self.Centre() self.Show(True) def OnUndo(self, e): """模擬了撤銷和反撤銷的功能,如果沒有什么可以撤銷的,撤銷按鈕就會禁用""" if self.count > 1 and self.count <= 5: self.count = self.count - 1 if self.count == 1: self.toolbar.EnableTool(wx.ID_UNDO, False) if self.count == 4: self.toolbar.EnableTool(wx.ID_REDO, True) def OnRedo(self, e): if self.count < 5 and self.count >= 1: self.count = self.count + 1 if self.count == 5: self.toolbar.EnableTool(wx.ID_REDO, False) if self.count == 2: self.toolbar.EnableTool(wx.ID_UNDO, True) def OnQuit(self, e): self.Close()
高級控件
ListBox
該控件用來展示和操作一組列表項,它有一個矩形框,里面有一組字符串,通過它,可以展示一列 MP3文件、書名或者一堆朋友的名字。wx.ListBox
可以有兩種形式,單選和多選,默認為單選。該控件有兩個可觸發事件。
- wx.EVT_COMMAND_LISTBOX_SELECTED – 當用戶單擊一個條目時觸發
- wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED – 當用戶雙擊一個條目時觸發
根據文檔,wx.ListBox
中條目的個數在 GTK 平台上限制為 2000 個,需要滾動時會自動展示滾動條。
class Example(wx.Frame): """創建1個 ListBox 和4個 Button,每個 Button 對應一個不同的方法,即增刪改查""" def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'wx.ListBox', size=(350, 220)) panel = wx.Panel(self, -1) hbox = wx.BoxSizer(wx.HORIZONTAL) # 創建一個空的 wx.ListBox,邊框是 20px self.listbox = wx.ListBox(panel, -1) hbox.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 20) btnPanel = wx.Panel(panel, -1) vbox = wx.BoxSizer(wx.VERTICAL) new = wx.Button(btnPanel, id=1, label='增加', size=(90, 30)) ren = wx.Button(btnPanel, id=2, label='修改', size=(90, 30)) dlt = wx.Button(btnPanel, id=4, label='刪除', size=(90, 30)) clr = wx.Button(btnPanel, id=3, label='清空', size=(90, 30)) self.Bind(wx.EVT_BUTTON, self.NewItem, id=1) self.Bind(wx.EVT_BUTTON, self.OnRename, id=2) self.Bind(wx.EVT_BUTTON, self.OnDelete, id=4) self.Bind(wx.EVT_BUTTON, self.OnClear, id=3) # 使用 wx.EVT_LISTBOX_DCLICK 綁定器將 wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED 事件綁定至 OnRename(), # 當用戶雙擊特定元素時將彈出一個重命名對話框 self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename) vbox.Add((-1, 20)) vbox.Add(new) vbox.Add(ren, 0, wx.TOP, 5) vbox.Add(dlt, 0, wx.TOP, 5) vbox.Add(clr, 0, wx.TOP, 5) btnPanel.SetSizer(vbox) hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20) panel.SetSizer(hbox) self.Centre() self.Show(True) def NewItem(self, event): """點擊 增加 按鈕時,NewItem() 被調用,將展示一個 wx.GetTextFromUser 對話框,該對話框返回用戶的輸入""" text = wx.GetTextFromUser('輸入一個新條目', '插入對話框') # 如果輸入非空,使用 Append() 將其添加至 ListBox if text != '': self.listbox.Append(text) def OnRename(self, event): """wx.ListBox 控件沒有 Rename() 方法,只能刪除之前選擇的條目,然后在原來的地方插入一個新的條目""" sel = self.listbox.GetSelection() text = self.listbox.GetString(sel) renamed = wx.GetTextFromUser('重命名條目', '重命名對話框', text) if renamed != '': self.listbox.Delete(sel) self.listbox.Insert(renamed, sel) def OnDelete(self, event): """分兩步刪除一個 Item""" # 使用 GetSelection() 獲取被選擇條目的 index sel = self.listbox.GetSelection() if sel != -1: # 將 index 作為參數傳入 Delete() 方法刪除該元素 self.listbox.Delete(sel) def OnClear(self, event): """調用 Clear() 清空 ListBox""" self.listbox.Clear()
ListCtrl
該控件是展示多列條目的圖形展示控件,常用於文件管理器、CD刻錄的文件等。它有三種不同的使用格式:列表視圖、報告視圖和圖標視圖,分別通過 style
參數:wx.LC_REPORT
、wx.LC_LIST
和 wx.LC_ICON
來控制。
packages = [('1', '2012', '1054.74'), ('2', '2013', '1062.89'), ('3', '2014', '1077.89'), ('4', '2015', '1137.87'), ('5', '2016', '1190.84'), ('6', '2017', '1252.83'), ('7', '2018', '1302.66'), ('8', '2019', '1343.88')] class Example(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, '深圳市年末常住人口', size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) # 使用 wx.LC_REPORT style 創建一個 wx.ListCtrl self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT) # 插入 3 列,可以單獨控制每一列的寬度和格式,默認的格式是 wx.LIST_FORMAT_LEFT self.list.InsertColumn(0, '序號', width=140) self.list.InsertColumn(1, '統計時間', width=130) self.list.InsertColumn(2, '年末常住人口(萬人)', wx.LIST_FORMAT_RIGHT, 90) # 使用兩種方法將數據插入到 wx.ListCtrl 中去 for i in packages: # 對每一行,調用 InsertItem() 方法,第一個參數為行號,使用2000保證每次調用時插入的行在上次插入行之后,該方法返回行的索引值 index = self.list.InsertItem(index=2000, label=i[0]) # 通過 SetItem() 方法在當前行的后續列中插入數據 self.list.SetItem(index, 1, i[1]) self.list.SetItem(index, 2, i[2]) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True)
Mixins
該控件增強了 wx.ListCtrl
的功能,位於 wx.lib.mixins.listctrl
模塊,必須繼承這些類才可使用它們。
- wx.ColumnSorterMixin – 允許排序
- wx.ListCtrlAutoWidthMixin – 自動調整最后一列的寬度(占滿)
- wx.ListCtrlSelectionManagerMix – 定義獨立於平台的選擇策略
- wx.TextEditMixin – 允許文本編輯
- wx.CheckListCtrlMixin – 為每一行添加一個選擇框
wx.ListCtrlAutoWidthMixin
import wx # 導入 Mixins 的 wx.ListCtrlAutoWidthMixin from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin actresses = [('1', '2012', '1054.74'), ('2', '2013', '1062.89'), ('3', '2014', '1077.89'), ('4', '2015', '1137.87'), ('5', '2016', '1190.84'), ('6', '2017', '1252.83'), ('7', '2018', '1302.66'), ('8', '2019', '1343.88')] class AutoWidthListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): """ 創建一個新的 AutoWidthListCtrl 類,該類繼承自 wx.ListCtrl 和 ListCtrlAutoWidthMixin(多重繼承), 最后一列會自動改變寬度來占用剩余的全部空間。 """ def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) ListCtrlAutoWidthMixin.__init__(self) class Example(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'wx.ListCtrlAutoWidthMixin', size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) self.list = AutoWidthListCtrl(panel) self.list.InsertColumn(0, '序號', width=140) self.list.InsertColumn(1, '統計時間', width=130) self.list.InsertColumn(2, '年末常住人口(萬人)', wx.LIST_FORMAT_RIGHT, 90) for i in actresses: index = self.list.InsertItem(2000, i[0]) self.list.SetItem(index, 1, i[1]) self.list.SetItem(index, 2, i[2]) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True)
wx.ColumnSorterMixin
import wx from wx.lib.mixins.listctrl import ColumnSorterMixin actresses = { 1: ('2012', '1054.74'), 2: ('2013', '1062.89'), 3: ('2014', '1077.89'), 4: ('2015', '1137.87'), 5: ('2016', '1190.84'), 6: ('2017', '1252.83'), 7: ('2018', '1302.66'), 8: ('2019', '1343.88') } class SortedListCtrl(wx.ListCtrl, ColumnSorterMixin): def __init__(self, parent): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT) # ColumnSorterMixin 接受一個參數,即要排序的列的個數 ColumnSorterMixin.__init__(self, len(actresses)) # 必須將數據匹配到 itemDataMap 屬性中,且數據類型為字典 self.itemDataMap = actresses def GetListCtrl(self): """必須創建一個 GetListCtrl() 方法,它會返回一個將被排序的 wx.ListCtrl 控件""" return self class Example(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'wx.ColumnSorterMixin', size=(380, 230)) hbox = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(self, -1) self.list = SortedListCtrl(panel) self.list.InsertColumn(0, '統計時間', width=130) self.list.InsertColumn(1, '年末常住人口(萬人)', wx.LIST_FORMAT_RIGHT, 90) items = actresses.items() for key, data in items: index = self.list.InsertItem(2000, data[0]) self.list.SetItem(index, 1, data[1]) # 需要使用 SetItemData() 方法將每一行與一個 index 關聯起來 self.list.SetItemData(index, key) hbox.Add(self.list, 1, wx.EXPAND) panel.SetSizer(hbox) self.Centre() self.Show(True)
HtmlWindow
該控件可以展示HTML頁面,但是它不是一個完整的瀏覽器,只能展示一些基本的標簽。
import wx import wx.html as html page = """<html><body bgcolor="#8e8e95"><table cellspacing="5" border="0" width="250"> <tr width="200" align="left"><td bgcolor="#e7e7e7">最大值</td><td bgcolor="#F0FFFF"><b>9000</b></td></tr> <tr align="left"><td bgcolor="#e7e7e7">平均值</td><td bgcolor="#F0F8FF"><b>6076</b></td></tr> <tr align="left"><td bgcolor="#e7e7e7">最小值</td><td bgcolor="#E6E6FA"><b>3800</b></td></tr> <tr align="left"><td bgcolor="#e7e7e7">中位數</td><td bgcolor="#FFF0F5"><b>6000</b></td></tr> <tr align="left"><td bgcolor="#e7e7e7">標准偏差</td><td bgcolor="#FFE4E1"><b>6076</b></td></tr> </table></body></html>""" class Example(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'html.HtmlWindow', size=(400, 290)) panel = wx.Panel(self, -1) v_box = wx.BoxSizer(wx.HORIZONTAL) # 創建一個HTML窗口 html_win = html.HtmlWindow(panel, -1, style=wx.NO_BORDER) html_win.SetBackgroundColour(wx.RED) html_win.SetStandardFonts() html_win.SetPage(page) v_box.Add(html_win, 1, wx.EXPAND | wx.ALL, 9) panel.SetSizer(v_box) self.Centre() self.Show(True)
拖拽
拖拽操作將一些數據從一個源位置移動到目標位置,實現拖拽需要有:一些數據、一個數據來源和一個數據目標。
TextDropTarget
import wx import os class MyTextDropTarget(wx.TextDropTarget): """繼承wx.TextDropTarget類,這是 wxPython 中兩個預定義的數據目標之一""" def __init__(self, object): wx.TextDropTarget.__init__(self) self.object = object def OnDropText(self, x, y, data): self.object.InsertItem(0, data) return True class Example(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'wx.TextDropTarget', size=(750, 450)) # 創建兩個分隔窗口(wx.SplitterWindow)控件 splitter1 = wx.SplitterWindow(self, -1, style=wx.SP_3D) splitter2 = wx.SplitterWindow(splitter1, -1, style=wx.SP_3D) # 創建一個目錄樹(wx.GenericDirCtrl)控件 self.dir = wx.GenericDirCtrl(splitter1, -1, dir='C:/Users/hekaiyou/Downloads', style=wx.DIRCTRL_DIR_ONLY) # 分別創建數據來源(lc1)列表與數據目標(lc2)列表 self.lc1 = wx.ListCtrl(splitter2, -1, style=wx.LC_LIST) self.lc2 = wx.ListCtrl(splitter2, -1, style=wx.LC_LIST) # 綁定數據目標列表的數據 dt = MyTextDropTarget(self.lc2) self.lc2.SetDropTarget(dt) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId()) # 獲取樹控件的節點 tree = self.dir.GetTreeCtrl() # 設置上下布局的分隔窗口,window1為上窗口,window2為下窗口,sashPosition是窗口的位置 splitter2.SplitHorizontally(window1=self.lc1, window2=self.lc2) # 設置最小窗口尺寸,上下布局是指上窗口的最小尺寸 splitter2.SetMinimumPaneSize(200) # 設置左右布局的分隔窗口,window1為左窗口,window2為右窗口,sashPosition是窗口的位置 splitter1.SplitVertically(window1=self.dir, window2=splitter2) # 設置最小窗口尺寸,左右布局是指左窗口的最小尺寸 splitter1.SetMinimumPaneSize(320) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelect, id=tree.GetId()) self.OnSelect(0) self.Centre() self.Show(True) def OnSelect(self, event): list = os.listdir(self.dir.GetPath()) self.lc1.ClearAll() self.lc2.ClearAll() for i in range(len(list)): if list[i][0] != '.': self.lc1.InsertItem(0, list[i]) def OnDragInit(self, event): text = self.lc1.GetItemText(event.GetIndex()) tdo = wx.TextDataObject(text) tds = wx.DropSource(self.lc1) tds.SetData(tdo) tds.DoDragDrop(True)
FileDropTarget
class FileDrop(wx.FileDropTarget): def __init__(self, window): wx.FileDropTarget.__init__(self) self.window = window def OnDropFiles(self, x, y, filenames): for name in filenames: try: file = open(name, 'r') text = file.read() self.window.WriteText(text) file.close() except IOError as error: dlg = wx.MessageDialog(None, '打開文件時出錯', str(error)) dlg.ShowModal() except UnicodeDecodeError as error: dlg = wx.MessageDialog(None, '無法打開非ASCII文件', str(error)) dlg.ShowModal() return True class Example(wx.Frame): """可以將文件拖至編輯器,編輯器將顯示其內容""" def __init__(self, parent): wx.Frame.__init__(self, parent, -1, 'wx.FileDropTarget', size=(450, 400)) self.text = wx.TextCtrl(self, -1, style=wx.TE_MULTILINE) dt = FileDrop(self.text) self.text.SetDropTarget(dt) self.Centre() self.Show(True)
事件
事件是來源於底層框的應用層信息,事件循環主要用來分發事件和等待信息。事件分配器將事件匹配到對應的事件處理器,事件處理器即用來對響應事件做出特定反應的函數。
移動事件
當移動窗口到一個新位置時,會產生一個移動事件,它的類型是 wx.MoveEvent
,該事件的綁定器是 wx.EVT_MOVE
。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): wx.StaticText(self, label='x:', pos=(10, 10)) wx.StaticText(self, label='y:', pos=(10, 30)) self.st1 = wx.StaticText(self, label='', pos=(30, 10)) self.st2 = wx.StaticText(self, label='', pos=(30, 30)) # 將 wx.EVT_MOVE 事件綁定到 OnMove() 方法上 self.Bind(wx.EVT_MOVE, self.OnMove) self.SetSize((250, 180)) self.SetTitle('窗口移動事件') self.Centre() self.Show(True) def OnMove(self, e): """這里的 e 是 wx.MoveEvent 類的一個實例,它包含了該 event 的一些信息,包括事件對象和窗口位置等""" # 通過事件(即是 wx.Frame 控件)的 GetPosition() 函數來得到當前位置 x, y = e.GetPosition() self.st1.SetLabel(str(x)) self.st2.SetLabel(str(y))
停止事件
有時需要停止某個事件的繼續處理,這時可以調用 Veto()
方法。
class Example(wx.Frame): """處理了一個 wx.CloseEvent 事件,當點擊窗口的 X關閉按鈕、按下 Alt+F4 或者從菜單選擇退出應用時,這個事件會被觸發""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): # 綁定 wx.EVT_CLOSE 事件處理 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) self.SetTitle('wx.EVT_CLOSE') self.Centre() self.Show(True) def OnCloseWindow(self, e): """在處理關閉事件時,顯示一個消息對話框""" dial = wx.MessageDialog(None, '你確定要退出嗎?', '提示', wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) ret = dial.ShowModal() # 根據對話框的返回值,可以銷毀窗口或者停止這一事件 if ret == wx.ID_YES: # 必須使用 Destroy() 來關閉窗口,如果調用 Close() 函數,程序將陷入死循環 self.Destroy() else: e.Veto()
事件傳播
事件分兩種:基礎事件與命令事件,兩者在事件傳播上存在不同,事件傳播是指將事件從子控件傳播至父控件乃至更上層控件,基礎事件不傳播,而命令事件會傳播。例如:wx.CloseEvent
是一個基礎事件,這意味着它不會向上傳播。默認情況下,如果事件被事件處理函數捕獲,那么就會停止后續的傳播,如果要讓它繼續傳播,需要調用 Skip()
函數。
class MyPanel(wx.Panel): def __init__(self, *args, **kw): super(MyPanel, self).__init__(*args, **kw) self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) def OnButtonClicked(self, e): print('事件抵達 Panel 類') e.Skip() class MyButton(wx.Button): """處理了按鈕點擊事件,Skip() 函數使得事件繼續向上層傳播""" def __init__(self, *args, **kw): super(MyButton, self).__init__(*args, **kw) self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) def OnButtonClicked(self, e): print('事件抵達 Button 類') e.Skip() class Example(wx.Frame): """在 Frame 上的 Panel 中放置了一個 Button,並對所有的控件定義了事件處理函數""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): mpnl = MyPanel(self) MyButton(mpnl, label='Ok', pos=(15, 15)) self.Bind(wx.EVT_BUTTON, self.OnButtonClicked) self.SetTitle('傳播事件') self.Centre() self.Show(True) def OnButtonClicked(self, e): print('事件抵達 Frame 類') e.Skip()
控件標識符
控件標識符是指在事件系統中,找到控件的唯一整數標記,創建窗口標識符的方法有三種:系統自動創建ID、使用標准標識符、創建自定義ID。
自動創建ID
在不需要修改控件狀態的時候,一般選擇由系統自動創建ID,可以將 -1
或者 wx.ID_ANY
賦值給 id
參數,但是系統自動創建的ID總是負值(用戶創建的必須是正值),可以通過 GetId()
來獲取ID。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) exitButton = wx.Button(pnl, wx.ID_ANY, '退出', (10, 10)) # 通過 GetId() 函數直接獲取自動生成的ID self.Bind(wx.EVT_BUTTON, self.OnExit, id=exitButton.GetId()) self.SetTitle("系統自動創建ID") self.Centre() self.Show(True) def OnExit(self, event): self.Close()
標准標識符
使用標准標識符可以在不同平台提供標准的圖形或行為。
class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) grid = wx.GridSizer(cols=2) # 將6個按鈕加入到一個網格布局中 grid.AddMany([ (wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9), # 標准標識符-取消 (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9), # 標准標識符-刪除 (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9), # 標准標識符-保存 (wx.Button(pnl, wx.ID_EXIT)), # 標准標識符-退出 (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9), # 標准標識符-停止 (wx.Button(pnl, wx.ID_NEW)) # 標准標識符-新建 ]) # 將事件綁定到 OnQuitAPP() 處理函數,使用 id 參數來區分不同的 Button,並唯一標識事件的來源 self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT) pnl.SetSizer(grid) self.SetSize((220, 180)) self.SetTitle("使用標准標識符") self.Centre() self.Show(True) def OnQuitApp(self, event): self.Close()
自定義標識符
# 定義全局的自定義的唯一ID ID_MENU_NEW = 1 ID_MENU_OPEN = 2 ID_MENU_SAVE = 3 class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): self.CreateMenuBar() self.CreateStatusBar() self.SetSize((250, 180)) self.SetTitle('自定義控件標識符') self.Centre() self.Show(True) def CreateMenuBar(self): mb = wx.MenuBar() fMenu = wx.Menu() fMenu.Append(ID_MENU_NEW, '新建') fMenu.Append(ID_MENU_OPEN, '打開') fMenu.Append(ID_MENU_SAVE, '保存') mb.Append(fMenu, '文件') self.SetMenuBar(mb) # 通過唯一ID可以識別前面創建的三個菜單項 self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW) self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN) self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) def DisplayMessage(self, e): sb = self.GetStatusBar() # 從 e(事件對象)中得到ID,根據ID的不同,准備不同的信息,並輸出在狀態欄 eid = e.GetId() if eid == ID_MENU_NEW: msg = '選擇 新建 菜單項' elif eid == ID_MENU_OPEN: msg = '選擇 打開 菜單項' elif eid == ID_MENU_SAVE: msg = '選擇 保存 菜單項' else: msg = '' sb.SetStatusText(msg)
繪制事件
當窗口重繪時會觸發繪制事件,比如調整窗口大小或者最大化的時候。也可以程序化的觸發繪制事件,比如,調用 SetLabel()
函數來修改 wx.StaticText
組件的文字時,就會觸發繪制事件。
class Example(wx.Frame): """對繪制事件進行計數,並將當前數目設置為 Frame 窗口的標題""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): self.count = 0 # 將 wx.EVT_PAINT 事件綁定至 OnPaint 函數 self.Bind(wx.EVT_PAINT, self.OnPaint) self.SetSize((250, 180)) self.Centre() self.Show(True) def OnPaint(self, e): """增加計數器,並設置新的窗口標題""" self.count += 1 self.SetTitle(str(self.count))
焦點事件
焦點是指當前應用中被選擇的控件,從鍵盤輸入或剪切板拷入的文本將被發送到該控件,有兩個事件與焦點有關:wx.EVT_SET_FOCUS
和 wx.EVT_KILL_FOCUS
。當一個控件獲得焦點時,會觸發 wx.EVT_SET_FOCUS
,當控件丟失焦點時,會觸發 wx.EVT_KILL_FOCUS
。通過點擊或者鍵盤按鍵,比如 Tab 鍵或 Shift+Tab 鍵可以改變焦點。
class MyPanel(wx.Panel): def __init__(self, parent): super(MyPanel, self).__init__(parent) self.color = '#b3b3b3' self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_SIZE, self.OnSize) # 把兩個焦點事件綁定至事件處理函數 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) def OnPaint(self, e): """在面板上進行了繪制,外框的顏色取決於面板是否獲得焦點,如果獲得焦點,則使用藍色""" dc = wx.PaintDC(self) dc.SetPen(wx.Pen(self.color)) x, y = self.GetSize() dc.DrawRectangle(0, 0, x, y) def OnSize(self, e): self.Refresh() def OnSetFocus(self, e): # 設置 self.color 為藍色 self.color = '#0099f7' # 刷新 Frame 窗口,這會觸發所有子控件的繪制事件,面板會被重繪,獲取焦點的面板將得到一個藍色外框 self.Refresh() def OnKillFocus(self, e): self.color = '#b3b3b3' self.Refresh() class Example(wx.Frame): """創建4個 Panel,獲得當前焦點的 Panel 會被高亮顯示""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): grid = wx.GridSizer(2, 2, 10, 10) grid.AddMany([ (MyPanel(self), 0, wx.EXPAND | wx.TOP | wx.LEFT, 9), (MyPanel(self), 0, wx.EXPAND | wx.TOP | wx.RIGHT, 9), (MyPanel(self), 0, wx.EXPAND | wx.BOTTOM | wx.LEFT, 9), (MyPanel(self), 0, wx.EXPAND | wx.BOTTOM | wx.RIGHT, 9) ]) self.SetSizer(grid) self.SetSize((350, 250)) self.SetTitle('焦點事件') self.Centre() self.Show(True)
鍵盤事件
在鍵盤上按下按鈕時,一個鍵盤事件會被觸發,並被發送到當前焦點控件,有三種不同的鍵盤事件:wx.EVT_KEY_DOWN
、wx.EVT_KEY_UP
和 wx.EVT_CHAR
。
class Example(wx.Frame): """當按下 Esc 時,會彈出對話框詢問,是否關閉應用""" def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): pnl = wx.Panel(self) # 將 wx.EVT_KEY_DOWN 事件綁定至 self.OnKeyDown() 函數 pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) pnl.SetFocus() self.SetSize((250, 180)) self.SetTitle('鍵盤事件') self.Centre() self.Show(True) def OnKeyDown(self, e): # 通過 e(事件對象)得到按下鍵的編號 key = e.GetKeyCode() # 檢查鍵編號,判斷按下的鍵是否是 Esc,它的鍵編號是 wx.WXK_ESCAPE if key == wx.WXK_ESCAPE: ret = wx.MessageBox('你確定要關閉應用嗎?', '提醒', wx.YES_NO | wx.NO_DEFAULT, self) if ret == wx.YES: self.Close()
自定義控件
一般通過兩種方式創建自定義控件:修改或增強已有控件、從零開始創建。
import wx # 導入 wx.lib.stattext.GenStaticText 控件 from wx.lib.stattext import GenStaticText # 導入 webbrowser 標准模塊 import webbrowser class Link(GenStaticText): """基於 wx.lib.stattext.GenStaticText 控件構建 超鏈接控件""" def __init__(self, *args, **kw): super(Link, self).__init__(*args, **kw) self.font1 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana') self.font2 = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana') # 修改字體和文本的顏色 self.SetFont(self.font2) self.SetForegroundColour('#0000ff') self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent) self.Bind(wx.EVT_MOTION, self.OnMouseEvent) def SetUrl(self, url): self.url = url def OnMouseEvent(self, e): # 如果鼠標移到鏈接上方時,顯示文本下划線,並將鼠標設置為手型 if e.Moving(): self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) self.SetFont(self.font1) # 如果點擊鏈接,在默認瀏覽器打開它 elif e.LeftUp(): webbrowser.open_new(self.url) else: self.SetCursor(wx.NullCursor) self.SetFont(self.font2) e.Skip() class Example(wx.Frame): def __init__(self, *args, **kw): super(Example, self).__init__(*args, **kw) self.InitUI() def InitUI(self): panel = wx.Panel(self) lnk = Link(panel, label='百度', pos=(10, 60)) lnk.SetUrl('https://www.baidu.com') motto = GenStaticText(panel, label='通過系統默認瀏覽器打開', pos=(10, 30)) motto.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana')) self.SetSize((220, 150)) self.SetTitle('超鏈接控件') self.Centre() self.Show(True)