第一個應用程序:“Hello World”
作為傳統,我們首先將要寫一個小的“Hello World”程序,下面是他的代碼:
#!/usr/bin/env python import wx app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window. frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window. frame.Show(True) # Show the frame. app.MainLoop()
解釋:
App = wx.App(False)
每一個wxPython應用程序都是wx.App這個類的一個實例。對於大多數簡單的應用程序來說,你可以直接使用wx.App這個類。當你需要更復雜的功能的時候,你也許就需要去擴展wx.App類。參數“False”,意味着不重定向標准輸出和錯誤輸出到窗口上。
wx.Frame(None, wx.ID_ANY, “Hello”)
wx.Frame類是一個頂層窗口。它的用法是wx.Frame(Parent, Id, Title)。對於大對數的構造函數來說,都有這種通用的形式(一個父窗口名,后面緊隨着它的Id)。在這個例子當中,我們使用None ,來說明沒用父窗口,並且使用ID_ANY,來擁有一個wxWidgets分配給我們的ID 號。
frame.Show(True)
我們使一個窗口可見,通過這個函數。如果將參數改為False,你會發現程序真的在運行,但是我們看不到。
app.MainLoop()
最后,我們開始應用程序的MainLoop函數,它用來處理各種事件。
Note:你應該使用wx.ID_ANY或者wxWidgets提供的其他標准ID。你也可以使用自己創建的ID,但是沒有理由那么做。
運行程序,然后你應該看到一個類似與這樣的一個窗口:
(在不同的系統平台下,這個窗口的樣子可能差距很大)
窗口 還是 框架?
當人們談論GUI 的時候,他們通常會說窗口,菜單和圖標。然后自然而然地,你期望應該使用wx.Window來表示一個窗口。不幸的是,情況並不是這樣的。wx.Window是一個基類,所有的可視化控件都是從這個類派生出來的(比如說按鈕,菜單等等)。我們平時想到的程序窗口是一個wx.Frame類。對於許多初學者來說,這是一個不幸的矛盾。
構建一個簡單的文本編輯器
在這個教程中,我們將要建立一個簡單的文本編輯器。在這個過程中,我們將會探索許多的widgets知識,並且學習到關於事件處理和回調函數的一些知識。
第一步:
第一步先編寫一個簡單的框架,里面包含一個可編輯的文本框。文本框可以通過wx.TextCtrl類進行創建。默認情況下,文本框是單行的,但是使用wx.TE_MULTILINE參數可以允許你在文本框中輸入多行文本。
#!/usr/bin/env python import wx class MyFrame(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 = MyFrame(None, 'Small editor') app.MainLoop()
在這個例子當中,我們派生了wx.Frame類,並且重寫了它的__init__方法。在這個方法中,我們聲明了一個新的wx.TextCtrl實例,它是一個簡單的文本編輯控件。注意:因為MyFrame類運行了self.Show()在它的__init__方法中,所以我們不需要再顯式地調用frame.Show()。
添加一個菜單欄
每一個程序應該用一個菜單欄和一個狀態欄。讓我們添加它們到我們的程序當中:
import wx class MainWindow(wx.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() # A Statusbar in the bottom of the window # Setting up the menu. filemenu= wx.Menu() # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets. filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program") filemenu.AppendSeparator() filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program") # Creating the menubar. menuBar = wx.MenuBar() menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content. self.Show(True) app = wx.App(False) frame = MainWindow(None, "Sample editor") app.MainLoop()
提示:注意那個wx.ID_ABOUT和wx.ID_EXIT。它們是wxWidgets提供的標准ID(查看全部的ID列表)。使用標准ID是一個好的習慣,如果它存在的話。這有助於讓wxWidgets在不同的平台上使每一個控件的ID都看起來更加自然。
事件處理
在wxPython中,對事件的響應,稱作事件處理。事件就是指發生在你的程序當中的某些事情(一個按鈕被按下,文本輸入,鼠標移動等等)。GUI編程的很大一部分是由事件的響應組成的。你可以使用Bind()方法,將一個控件和事件綁定到一起。
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方法將會被執行。wx.EVT_MENU是“選擇菜單項”事件。wxWidgets也會處理許多其他的事件(查看全部列表)。self.OnAbout方法一般是這樣定義的:
def OnAbout(self, event): ...
這里的event是從wx.Event派生出一個子類的實例。比如說按鈕點擊事件-wx.EVT_BUTTON就是wx.Event的一個子類。
當事件發生的時候,這個方法被執行。默認情況下,這個方法將會處理事件,並且在回調函數結束后,事件終止。但是,你可以跳過一個事件通過event.Skip()方法。這將會導致事件直接跨過事件用戶處理層。比如說這樣:
def OnButtonClick(self, event): if (some_condition): do_something() else: event.Skip() def OnEvent(self, event): ...
當一個按鈕點擊事件發生時,OnButtonClick方法被調用。如果some_condition是真,我們就執行do_something(),否則我們讓這個事件用系統默認方式所處理。現在讓我們看看我們的程序:
import os import wx class MainWindow(wx.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() # A StatusBar in the bottom of the window # Setting up the menu. filemenu= wx.Menu() # wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets. menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program") menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program") # Creating the menubar. menuBar = wx.MenuBar() menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content. # Set events. self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) self.Bind(wx.EVT_MENU, self.OnExit, menuExit) self.Show(True) def OnAbout(self,e): # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets. dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK) dlg.ShowModal() # Show it dlg.Destroy() # finally destroy it when finished. def OnExit(self,e): self.Close(True) # Close the frame. app = wx.App(False) frame = MainWindow(None, "Sample editor") app.MainLoop()
Note:
wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor", wx.OK)
在這個例子中,我們可以忽略ID,wxWidget會自動使用一個默認的ID(就像我們指定了wx.ID_ANY一樣)。
wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor")
對話框
當然,如果一個編輯器沒用打開和保存功能,那么它幾乎是沒用的。那就到了展現通用對話框的時候了。通用對話框是由底層平台提供的,通過它可以是你的程序看起來更像是一個完整的程序。這里實現了MainWindow中的OnOpen方法。
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(模式/模態)”意味着在用戶點擊了確定按鈕或者取消按鈕之前,他不能在該程序中做任何事情。
ShowModal的返回值是被按下的按鈕的ID。如果用戶點擊了確定按鈕,我們就讀文件。
你現在應該可以向菜單中添加相應的內容,並且將它和OnOpen方法鏈接起來。如果你有問題,可以翻到下面的附錄,去查看完整的代碼。
可能的擴展
當然,這個程序距離一個像樣的編輯器還差的很遠。但是添加其他的功能不會比我們已經完成的部分難。你也許會從這些和wxPython綁定的樣例代碼中得到靈感。
拖放
MDI
選項卡視圖/多文件
查找/替換對話框
打印對話框(印刷)
Python中的宏命令(使用eval函數)
等等...
在窗口中工作
標題:
框架
窗口
控件/工具
布局管理
驗證器
在這段中,我們將學習如何使用wxPython處理窗口和它們的組件,包括構建輸入框和使用各種各種的控件。我們將創建一個小的應用程序,用來顯示一個標簽。如果你是一個有GUI編程經驗的開發者,這將是很簡單的,你可以直接查看后面高級章節的Boa-Constructor子段落。
總覽
可視化控件的布局
在一個框架當中,你將會使用很多wxPython的子類去充實框架里面的內容。這里是一些你將會在你的框架中使用到的常見的控件。
wx.MenuBar,在你的框架的頂部放一個菜單欄。
wx.Statusbar,在你的框架底部設置一個區域,來顯示狀態信息等等。
wx.ToolBar,在你的框架中放置一個工具欄
wx.Control的子類,這里面提供了一些控件的用戶接口(比如說用來顯示數據或者用戶輸入的可視化控件),常見的wx.Control對象包括wx.Button,wx.StaticText,wx.TextCtrl和wx.ComboBox。
wx.Panel,它是一個容器,可以用來包含你的許多wx.Control對象。將你的wx.Control對象放入一個wx.Panel中,意味着用戶可以直接將一對控件從一個可視化器件移動到另一個可視化器件上。
所有的可視化控件(wx.Window對象和它們的子類)可以包含許多子控件。比如說,wx.Frame可以包含很多個wx.Panel對象,wx.Panel對象中又可以包含多個wx.Button,wx.StaticText和wx.TextCtrl對象,給大家一個完整的控件層次結構:注意:這個僅僅只是描述了可視化控件之間的相關關系,不是說明它們在框架中的布局情況。處理框架中的控件布局,你有以下幾個選擇:
- 你可以手動的指定每一個控件在父窗口中的像素坐標。由於字體大小的不同等等的問題,如果想要程序在不同的平台之間可以通用的話,這個選擇一般不被考慮。
- 你可以使用wx.LayoutContains,它一般用起來很復雜。
- 你可以使用像Delphi一樣的LayoutAnchors,它比wx.LayoutContains更加簡單一點。
- 你可以使用一個wx.Sizer的子類。
在這個文檔中將會集中使用wx.Sizers,因為是大家最常用的解決方案。
Sizer
Sizer用來解決在窗口和框架中可視化控件的放置問題。Sizer可以做下列事情:
為每個控件計算合適的大小。
通過一些包含規則放置每一個控件。
當一個框架的大小被改變后,自動的重新計算每個控件的大小並且改變其坐標。
一些常見的sizers類包括:
wx.BoxSizer,以水平或垂直的方式將控件放置在一條線上。
wx.GridSizer,將控件以網狀結構放置。
wx.FlexGridSizer,它和wx.GridSizer相似,但是它允許以更加靈活的方式放置可視化控件。
可以向Sizer添加一組wx.Window對象,可以通過調用sizer.Add(window, options...),或者調用sizer.AddMany(...)這兩個方法向sizer中添加控件。每一個Sizer只處理那些被添加進來的控件。Sizer可以被嵌套。也就是說,你可以將一個Sizer添加到另一個Sizer當中。舉個例子來說有兩排按鈕(每一排按鈕通過一個水平的wx.BoxSizer進行布局),它可以包含在另一個wx.BoxSizer中,將一排按鈕放在另一排按鈕的上面,就像這樣:
注意:注意上面那個例子不是將6個按鈕布局成2排3列,如果要那么做的話,那就應該使用wx.GridSizer。
在下面這個例子當中,我們使用2個嵌套的sizer,主Sizer是垂直布局,嵌套的Sizer是水平布局:
import wx import os class MainWindow(wx.Frame): def __init__(self, parent, title): self.dirname = '' # A "-1" in the size parameter instructs wxWidgets to use the default size. # In this case, we select 200px width and the default height. wx.Frame.__init__(self, parent, title=title, size=(200, -1)) self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE) self.CreateStatusBar() # A Statusbar in the bottom of the window # Setting up the menu. filemenu = wx.Menu() menuOpen = filemenu.Append(wx.ID_OPEN, "&Open", " Open a file to edit") menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", " Information about this program") menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", " Terminate the program") # Creating the menubar. menuBar = wx.MenuBar() menuBar.Append(filemenu, "&File") # Adding the "filemenu" to the MenuBar self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content. # Events. self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen) self.Bind(wx.EVT_MENU, self.OnExit, menuExit) self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) 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.EXPAND) # Use some sizers to see layout options self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.control, 1, wx.EXPAND) self.sizer.Add(self.sizer2, 0, wx.EXPAND) # Layout sizers self.SetSizer(self.sizer) self.SetAutoLayout(1) self.sizer.Fit(self) self.Show() def OnAbout(self, e): # Create a message dialog box dlg = wx.MessageDialog(self, " A sample editor \n in wxPython", "About Sample Editor", wx.OK) dlg.ShowModal() # Shows it dlg.Destroy() # finally destroy it when finished. def OnExit(self, e): self.Close(True) # Close the frame. def OnOpen(self, e): """ Open a file""" 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, "Sample editor") app.MainLoop()
sizer.Add這個方法有三個參數。第一個參數指定了放入sizer的控件名。第二個參數是一個寬度因子,它用來表示這個控件相對於其他控件的比例大小,比如說你有3個編輯框並且你希望它們的大小比例是3:2:1,那么當你添加這些控件的時候,你就可以將這些比例指定為這個參數。0意味着這個控件或者這個sizer將不會發生大小的變化。第3個參數通常使用wx.GROW(和wx.EXPAND一樣),它意味着這個控件當在需要的時候可以改變大小。如果你使用wx.SHAPED來替代wx.GROW,那么控件的縱橫比將不會發生變化。
如果第二個參數是0,即控件將不會改變大小,第三個參數如果使用wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERICAL這兩個參數替代wx.GROW或者wx.SHARPED,那么控件將會被定位在垂直或者水平方向的中間位置。如果使用wx.ALIGN_CENTER那么控件將會被定位在程序窗口的正中間。
你可以在wx.ALIGN_LEFT,wx.ALIGN_TOP,wx.ALIGN_RIGHT和wx.ALIGN_BOTTOM中選擇幾個作為一個組合。默認的行為是wx.ALIGN_LEFT | wx.ALIGN_TOP。
大家可能對wx.Sizer和它的子類存在關於sizer和父窗口之間的差別的困惑。當你創建了一個對象放到了sizer里面,你並沒有指定sizer的父窗口。Sizer只是一種窗口布局的方式,它本身並不是一個窗口,所以不需要指定sizer的父窗口。在上面的例子當中,六個按鈕被創建的時候指定的父窗口是框架--不是sizer。如果你嘗試創建一個可視化控件並且用sizer作為它的父窗口,那么你的程序將會崩潰。
當你建立了你的可視化控件之后並將它們添加到sizer(或者嵌套的sizer)里面,下一步就是告訴你的框架或者窗口去調用這個sizer,你可以完成這個,用以下三步:
window.SetSizer(sizer)
window.SetAutoLayout(True)
sizer.Fit(window)
調用SetSizer()方法告訴你的窗口或框架去使用這個sizer。調用SetAutoLayout()方法告訴你的窗口使用sizer去為你的控件計算位置和大小。最后,調用sizer.Fit()告訴sizer去計算所有控件的初始位置和大小。如果你使用sizer進行布局,在第一次顯示你窗口中所有的控件之前,這都是一個常見的步驟。
Validator/驗證器
當你創建了一個對話框或者一個其他的輸入窗體,你可以使用wx.Validator來載入數據到你的輸入窗體,驗證輸入的數據,再次從窗體中提取數據。wx.Validator也可以被用來截獲鍵盤按鍵和其他的發生在輸入區域框中的事件。如果要使用一個Validator,你應該創建一個你自己的wx.Validator的子類(wx.TextValidator和wx.GenericValidator都沒有被wxPython實現)。通過調用myInputField.SetValidator(myValidator)來使你的這個子類和你的文字輸入框聯系起來。
注意:你的wx.Validator子類必須實現wx.Validator.Clone()方法。
一個可以運行的例子
我們的第一個程序包含一個panel(面板),panel中包含一個label(標簽)
讓我們開始一個例子。我們的程序將有一個框架,它有一個panel包含一個label:
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))
使用了panel作為我們的wx.StaticText的父窗口參數。我們的靜態文本將出現在我們剛剛創建的面板上。wx.Point被用來做位置參數。還有一個可選參數wx.Size但是如果它在這里使用,將不會被對齊。
根據wxPython文檔中的描述:
“面板(panel)是一個窗口,可以將其他的控件放到它的上面。它通常放置在框架中。在它父類(wx.Window)的基礎上,它包含了最小的額外的功能。它的主要是用在功能和外觀相似的對話框上,它要比任何的父窗口有更強的靈活性。”,事實上,它是一個簡單的窗口,被用來做其他對象的背景。它作為一個控件,這些一般都是被大家所知道的。
標簽(label)用來顯示一些不能被用戶所改變的文本。
添加更多的控件
你可以在wxPython的樣例代碼和幫助文檔中發現全部控件的列表,但是我們在這里只展示幾個最常用的控件:
wx.Button,最基礎的控件:一個按鈕上面顯示文本,你可以點擊它。比如說這里有一個“Clear”按鈕:
-
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
-
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
wx.TextCtrl,文本框,這個控件可以讓用戶輸入文本。它觸發2個主要的事件。EVT_TEXT,只要文本被改變了,它就會被調用。EVT_CHAR,只要一個按鍵被按下,它就會被調用。
textField = wx.TextCtrl(self) self.Bind(wx.EVT_TEXT, self.OnChange, textField) self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
比如說:如果用戶按下了“Clear”按鈕並且文字區域被清空,將會觸發EVT_TEXT事件,但是不會觸發EVT_CHAR事件。
wx.ComboBox, 組合框和文本框很相似,但是它除了包含wx.TextCtrl會觸發的2個事件,它還有EVT_COMBOBOX事件。
wx.CheckBox, 復選框是提供給用戶選擇真假的控件。
wx.RadioBox, 單選框可以讓用戶從一組選項中選擇一個。
現在讓我們看看這個Form1全部的代碼:
-
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))
-
-
# A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
-
self.logger = wx.TextCtrl(self, pos=( 300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
-
-
# A button
-
self.button =wx.Button(self, label= "Save", pos=(200, 325))
-
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
-
-
# the edit control - one line version.
-
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)
-
-
# the combobox Control
-
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)
-
self.Bind(wx.EVT_TEXT, self.EvtText,self.edithear)
-
-
# Checkbox
-
self.insure = wx.CheckBox(self, label= "Do you want Insured Shipment ?", pos=(20,180))
-
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
-
-
# Radio Boxes
-
radioList = [ 'blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
-
rb = wx.RadioBox(self, 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, rb)
-
-
def EvtRadioBox(self, event):
-
self.logger.AppendText( 'EvtRadioBox: %d\n' % event.GetInt())
-
def EvtComboBox(self, event):
-
self.logger.AppendText( 'EvtComboBox: %s\n' % event.GetString())
-
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 EvtCheckBox(self, event):
-
self.logger.AppendText( 'EvtCheckBox: %d\n' % event.Checked())
-
-
-
app = wx.App( False)
-
frame = wx.Frame( None)
-
panel = ExamplePanel(frame)
-
frame.Show()
-
app.MainLoop()
我們的類現在變得更大了;它現在有了許多控件並且這些控件都是有響應的。我們添加了一個特別的wx.TextCtrl控件去顯示控件發出的各種各樣的事件。
標簽頁
有時候一個窗體變得太大了,以至於不能在一個單個的頁面中顯示。wx.NoteBook就是用來解決這個問題的:它允許用戶通過點擊相關的標簽頁,在多個頁面中快速瀏覽。我們實現了這個,通過將窗體放入wx.NoteBook,而不是放入主框架中,並且通過使用AddPage方法將Form1添加到標簽頁中。
注意ExamplePanel的父窗口是NoteBook。這是很重要的。
-
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()
加強布局-使用Sizer
使用一個絕對的坐標位置經常是不能讓人滿意的:如果窗口不是一個正確的大小,那么窗口最后的樣子將會是很丑陋的。wxPython中有非常豐富的詞匯來定義對象的位置。
wx.BoxSizer是一個最常用的並簡單的布局對象,它只是將控件放在一個大概的位置。它的功能是很粗魯地安排一系列的控件在一行或一列上,並且在需要的時候(比如說窗口的整體大小改變的時候)重新安排它們的位置。
wx.GridSizer和wx.FlexGridSizer是兩個非常重要的布局工具。它們安排控件以表格的形式布局。
這里將上面的代碼簡單的重寫了一下:
-
class ExamplePanel(wx.Panel):
-
def __init__(self, parent):
-
wx.Panel.__init__(self, parent)
-
-
# create some sizers
-
mainSizer = wx.BoxSizer(wx.VERTICAL)
-
grid = wx.GridBagSizer(hgap= 5, vgap=5)
-
hSizer = wx.BoxSizer(wx.HORIZONTAL)
-
-
self.quote = wx.StaticText(self, label= "Your quote: ")
-
grid.Add(self.quote, pos=( 0,0))
-
-
# A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
-
self.logger = wx.TextCtrl(self, size=( 200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
-
-
# A button
-
self.button =wx.Button(self, label= "Save")
-
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
-
-
# the edit control - one line version.
-
self.lblname = wx.StaticText(self, label= "Your name :")
-
grid.Add(self.lblname, pos=( 1,0))
-
self.editname = wx.TextCtrl(self, value= "Enter here your name", 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)
-
-
# the combobox Control
-
self.sampleList = [ 'friends', 'advertising', 'web search', 'Yellow Pages']
-
self.lblhear = wx.StaticText(self, label= "How did you hear from us ?")
-
grid.Add(self.lblhear, pos=( 3,0))
-
self.edithear = wx.ComboBox(self, 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)
-
-
# add a spacer to the sizer
-
grid.Add(( 10, 40), pos=(2,0))
-
-
# Checkbox
-
self.insure = wx.CheckBox(self, label= "Do you want Insured Shipment ?")
-
grid.Add(self.insure, pos=( 4,0), span=(1,2), flag=wx.BOTTOM, border=5)
-
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
-
-
# Radio Boxes
-
radioList = [ 'blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
-
rb = wx.RadioBox(self, label= "What color would you like ?", pos=(20, 210), choices=radioList, majorDimension=3,
-
style=wx.RA_SPECIFY_COLS)
-
grid.Add(rb, pos=( 5,0), span=(1,2))
-
self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, 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)
-
self.SetSizerAndFit(mainSizer)
在這個例子當中使用GridBagSizer去放置控件。Grid對象的“pos”參數決定控件在grid中的位置。在這個實例中(0,0)是左上角,(3,5)指的是第三排第五列,span參數允許控件被強行擴展成多行多列。
用戶行為的響應
標題:
事件
彈出菜單
總覽
概念
一個可以運行的例子
例子
繪圖
標題
設備環境
字體
顏色
onPaint()方法
總覽
在這段中,我們將介紹在窗口中畫圖的方法。我們也將會展示如何在主窗口中按下鼠標右鍵,出現彈出菜單。
一個可以運行的例子
使用wxPython
調試技術
當在你的程序中遇到一個不能被處理的異常時(bug!),程序被異常終止,那么使用追蹤技術去定位問題代碼是很有用的。wxPython程序也是一樣的,但是它是扭曲的(twist)。wxPython中的追蹤是重新定向標准輸入輸出。它是獨立於你程序的GUI窗口的。如果一個異常發生在事件處理階段,追蹤信息會被顯示出來,並且你的程序會盡力地繼續執行。但是,如果異常發生在你程序的初始化階段,追蹤信息會被顯示出來,然后你的程序會被異常終止,查看標准輸入輸出窗口(或者你重定向的位置),可以讓讀者盡快知道問題所在。你可以截獲標准輸入輸出,通過wxPython提供的兩個可選參數,在你實例化你的wx.App對象的時候,可以指定這兩個參數。這個例子可以很好的說明:
-
class MyApp (wx.App):
-
#...
-
#...
-
#...
-
myapp = MyApp() # functions normally. Stdio is redirected to its own window
-
myapp = MyApp( 0) #does not redirect stdout. Tracebacks will show up at the console.
-
myapp = MyApp( 1, 'filespec') #redirects stdout to the file 'filespec'
-
# NOTE: These are named parameters, so you can do this for improved readability:
-
myapp = MyApp(redirect = 1, filename = 'filespec') # will redirect stdout to 'filespec'
-
myapp = MyApp(redirect = 0) #stdio will stay at the console...
你也可以使用Widget Inspection Tool來幫你調試大多數的布局問題。
這里討論代碼調試問題,事件循環問題,等等。
PyCrust交互控制台
wxPython發布一個漂亮的PyCrust控制台。使用它,你可以以交互的方式測試你的布局。這里有一段簡單的代碼。
部署你的wxPython應用
下一步
事件
事件處理是wxPython很關鍵的一部分。我們知道所有的GUI系統都是依賴於事件去在各種應用程序之間分發消息。GUI程序的任務是當接收到一個特定的消息的時候,自己決定去做什么,並且調用事件處理。在面向對象編程以前,想要處理事件就意味着必須有一個“switch”操作,根據事件的類型來決定去做什么事。現在在面向對象編程中這就不再是那么簡單的事情了。現在有2種事件處理的方式:
一種方法(像java)是依賴於事件處理器。事件處理器和一個特定的對象鏈接並且和一個回調函數/方法綁定。當對象接收到一個特定類型的事件,事件處理器就會觸發回調函數。
另一種方法是預先給一個方法起一個名字,假定用這個方法處理某一種特定的事件。使用這種方法的話,如果你想要改變某個類對某個事件的響應,那么你就必須在原有的類的基礎上進行派生,並且重載響應的方法。
wxPython結合了這兩種方法。你可以定義事件處理器與派生類去實現新的行為。
所以“self.Bind(wx.EVT_SOMETHING,ACallable)”的意思是:當EVT_SOMETHING事件被發送到窗口的時候,它將會調用ACallable。ACallable可以是任何函數,通常程序員的選擇是讓它實現上一個類的功能。
第二個版本是“self.Bind(wx.EVT_SOMETHING,ACallable,srcWin)”的意思是:當源窗口發出SOMETHING事件,這個事件到達窗口,然后調用ACallable。
但是很多事件只能被發出事件的窗口所捕獲(這意味着第二個方式不能做任何事情),所以最好無論在什么情況下都使用第一種方法,第一種方法可以用在任何地方,除了菜單項,因為它沒有Bind()方法。
在這里:創建一個自定義的事件處理程序
二,wxPython實現畫板.
class DoodleWindow(wx.Window): colours = ['Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple', 'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod', 'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey'] thicknesses = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128] def __init__(self, parent): super(DoodleWindow, self).__init__(parent, style=wx.NO_FULL_REPAINT_ON_RESIZE) self.initDrawing() self.makeMenu() self.bindEvents() self.initBuffer() def initDrawing(self): self.SetBackgroundColour('WHITE') self.currentThickness = self.thicknesses[0] self.currentColour = self.colours[0] self.lines = [] self.previousPosition = (0, 0) def bindEvents(self): for event, handler in [ \ (wx.EVT_LEFT_DOWN, self.onLeftDown), # Start drawing (wx.EVT_LEFT_UP, self.onLeftUp), # Stop drawing (wx.EVT_MOTION, self.onMotion), # Draw (wx.EVT_RIGHT_UP, self.onRightUp), # Popup menu (wx.EVT_SIZE, self.onSize), # Prepare for redraw (wx.EVT_IDLE, self.onIdle), # Redraw (wx.EVT_PAINT, self.onPaint), # Refresh (wx.EVT_WINDOW_DESTROY, self.cleanup)]: self.Bind(event, handler) def initBuffer(self): ''' Initialize the bitmap used for buffering the display. ''' size = self.GetClientSize() self.buffer = wx.EmptyBitmap(size.width, size.height) dc = wx.BufferedDC(None, self.buffer) dc.SetBackground(wx.Brush(self.GetBackgroundColour())) dc.Clear() self.drawLines(dc, *self.lines) self.reInitBuffer = False def makeMenu(self): ''' Make a menu that can be popped up later. ''' self.menu = wx.Menu() self.idToColourMap = self.addCheckableMenuItems(self.menu, self.colours) self.bindMenuEvents(menuHandler=self.onMenuSetColour, updateUIHandler=self.onCheckMenuColours, ids=self.idToColourMap.keys()) self.menu.Break() # Next menu items go in a new column of the menu self.idToThicknessMap = self.addCheckableMenuItems(self.menu, self.thicknesses) self.bindMenuEvents(menuHandler=self.onMenuSetThickness, updateUIHandler=self.onCheckMenuThickness, ids=self.idToThicknessMap.keys()) @staticmethod def addCheckableMenuItems(menu, items): ''' Add a checkable menu entry to menu for each item in items. This method returns a dictionary that maps the menuIds to the items. ''' idToItemMapping = {} for item in items: menuId = wx.NewId() idToItemMapping[menuId] = item menu.Append(menuId, str(item), kind=wx.ITEM_CHECK) return idToItemMapping def bindMenuEvents(self, menuHandler, updateUIHandler, ids): ''' Bind the menu id's in the list ids to menuHandler and updateUIHandler. ''' sortedIds = sorted(ids) firstId, lastId = sortedIds[0], sortedIds[-1] for event, handler in \ [(wx.EVT_MENU_RANGE, menuHandler), (wx.EVT_UPDATE_UI_RANGE, updateUIHandler)]: self.Bind(event, handler, id=firstId, id2=lastId) # Event handlers: def onLeftDown(self, event): ''' Called when the left mouse button is pressed. ''' self.currentLine = [] self.previousPosition = event.GetPositionTuple() self.CaptureMouse() def onLeftUp(self, event): ''' Called when the left mouse button is released. ''' if self.HasCapture(): self.lines.append((self.currentColour, self.currentThickness, self.currentLine)) self.currentLine = [] self.ReleaseMouse() def onRightUp(self, event): ''' Called when the right mouse button is released, will popup the menu. ''' self.PopupMenu(self.menu) def onMotion(self, event): ''' Called when the mouse is in motion. If the left button is dragging then draw a line from the last event position to the current one. Save the coordinants for redraws. ''' if event.Dragging() and event.LeftIsDown(): dc = wx.BufferedDC(wx.ClientDC(self), self.buffer) currentPosition = event.GetPositionTuple() lineSegment = self.previousPosition + currentPosition self.drawLines(dc, (self.currentColour, self.currentThickness, [lineSegment])) self.currentLine.append(lineSegment) self.previousPosition = currentPosition def onSize(self, event): ''' Called when the window is resized. We set a flag so the idle handler will resize the buffer. ''' self.reInitBuffer = True def onIdle(self, event): ''' If the size was changed then resize the bitmap used for double buffering to match the window size. We do it in Idle time so there is only one refresh after resizing is done, not lots while it is happening. ''' if self.reInitBuffer: self.initBuffer() self.Refresh(False) def onPaint(self, event): ''' Called when the window is exposed. ''' # Create a buffered paint DC. It will create the real # wx.PaintDC and then blit the bitmap to it when dc is # deleted. Since we don't need to draw anything else # here that's all there is to it. dc = wx.BufferedPaintDC(self, self.buffer) def cleanup(self, event): if hasattr(self, "menu"): self.menu.Destroy() del self.menu # These two event handlers are called before the menu is displayed # to determine which items should be checked. def onCheckMenuColours(self, event): colour = self.idToColourMap[event.GetId()] event.Check(colour == self.currentColour) def onCheckMenuThickness(self, event): thickness = self.idToThicknessMap[event.GetId()] event.Check(thickness == self.currentThickness) # Event handlers for the popup menu, uses the event ID to determine # the colour or the thickness to set. def onMenuSetColour(self, event): self.currentColour = self.idToColourMap[event.GetId()] def onMenuSetThickness(self, event): self.currentThickness = self.idToThicknessMap[event.GetId()] # Other methods @staticmethod def drawLines(dc, *lines): ''' drawLines takes a device context (dc) and a list of lines as arguments. Each line is a three-tuple: (colour, thickness, linesegments). linesegments is a list of coordinates: (x1, y1, x2, y2). ''' dc.BeginDrawing() for colour, thickness, lineSegments in lines: pen = wx.Pen(wx.NamedColour(colour), thickness, wx.SOLID) dc.SetPen(pen) for lineSegment in lineSegments: dc.DrawLine(*lineSegment) dc.EndDrawing() class DoodleFrame(wx.Frame): def __init__(self, parent=None): super(DoodleFrame, self).__init__(parent, title="Doodle Frame", size=(800,600), style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) doodle = DoodleWindow(self) if __name__ == '__main__': app = wx.App() frame = DoodleFrame() frame.Show() app.MainLoop()
參考鏈接:https://www.yiibai.com/wxpython