[譯] 開始使用 wxPython [Getting started with wxPython]


 原文:http://wiki.wxpython.org/Getting%20Started

 

1. 第一個應用程序:Hello World

  按照“國際慣例”,我們先寫一個“Hello World”的應用程序,下面是代碼:

1 #!/usr/bin/env python
2 import wx
3 
4 app = wx.App(False)  # Create a new app, don't redirect stdout/stderr to a window.
5 frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.
6 frame.Show(True)     # Show the frame.
7 app.MainLoop()

[批注:

  1. 在期望運行該程序前,需要安裝 Python 和 wxPython,否則,我想它不能正常運行

  2. 如果需要在代碼的添加中文,需要在代碼前面添加編碼格式,否則,我想它也不能正常運行。

         范例代碼如下:

  

1 #coding=utf-8
2 #!/usr/bin/env python
3 import wx
4 
5 # 這是一個簡單的 "Hello World" 應用程序
6 app = wx.App(False)  # Create a new app, don't redirect stdout/stderr to a window.
7 frame = wx.Frame(None, wx.ID_ANY, "Hello World") # A Frame is a top-level window.
8 frame.Show(True)     # Show the frame.
9 app.MainLoop()

 

 ]

說明:

+-------------------------------------------------------------------------------------------------------------------

 app = wx.App(False)

  # 該 app 是 wx.App 的一個實例,對於大多數簡單的應用程序就是一個 wx.App 對象,

  # 在需要創建復雜的應用程序時需要使用繼承 wx.App 類,參數 “False”表示不將標准輸出和標准錯誤重定向到窗口。

+-------------------------------------------------------------------------------------------------------------------

 frame = wx.Frame(None, wx.ID_ANY, "Hello World")

  # 一個 wx.Frame 是一個最頂層的窗口,語法是:wx.Frame(Parent, Id, Title)

  # 大多數的使用傳遞的參數是(一個父對象,一個ID號)。

  # 在該例子中,“None” 代表沒有父對象,“wx.ID_ANY” 表示由 wxWidgets 自動為我們選擇一個 ID 號。

+-------------------------------------------------------------------------------------------------------------------

 frame.Show(True)

  # 使用 Show 方法,使窗口生效並顯示(True)或隱藏(False)

+-------------------------------------------------------------------------------------------------------------------

 app.MainLoop()

  # 最后,我們啟動應用程序的主循環來處理事件

+-------------------------------------------------------------------------------------------------------------------

注意:你大多數時候都希望使用 wx.ID_ANY 或者其他的標准 ID 值(參見:標准ID值)。你可以自己定義 ID 值,但是沒有必要。

執行程序,然后你將看到類似下面的一個窗口:

 

1.1. “是胡不是霍,是霍躲不過” [Windows 還是 Frames] ?

  當大家討論界面 GUI 時,經常會說:窗口windows、菜單memus、圖標icons。自然而然,就會認為 wx.Window 表示

屏幕上的窗口。不幸的是,卻不是這么回事。一個 wx.Window 是一個基本的類對象,用戶衍生出可見的原始對象(如:按鈕、

菜單),而且一個程序的窗口是一個 wx.Frame 實例。對新手來說,這寫不一致可能導致一些困惑。

 

2. 創建一個簡單的文本編輯器

  我們將在這部分教程中學習創建一個簡單的編輯器。在這個過程中,我們將研究幾個配件(widgets,如:按鈕,編輯框)

的使用,並且會學到它們的一些特性,比如:事件以及事件的回調處理(events and callbacks)。

 

 2.1 第一步

  第一步,我們創建一個帶有編輯框的窗口。編輯框通過 wx.TextCtrl 創建。默認情況,創建的編輯框只能單行輸入,可以

使用 wx.TE_MULTLINE 來允許輸入多行。

 1 #!/usr/bin/env python
 2 import wx
 3 
 4 class MyFrame(wx.Frame):
 5     """ We simply derive a new callss of Frame. """
 6     def __init__(self, parent, title):
 7         wx.Frame.__init__(self, parent, title = title)
 8         self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
 9         self.Show(True)
10 
11 app = wx.App(False)
12 frame = MyFrame(None, 'Small Editor')
13 app.MainLoop()

  在這個例子中,我繼承了 wx.Frame 並且重寫了它的 __init__ 方法。我們使用了 wx.TextCtrl 創建了一個編輯框。因為

我們在 MyFrame 的 __init__ 方法里運行了 self.Show(),所以不需要顯式的調用 frame.Show()。

 

2.2 添加菜單欄

每一個應用程序都應該有一個菜單欄和狀態欄。來,我們一起來添加這些功能:

 1 import wx
 2 
 3 class MainWindow(wx.Frame):
 4     def __init__(self, parent, title):
 5         wx.Frame.__init__(self, parent, title=title, size=(200,100))
 6         self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
 7         self.CreateStatusBar() # A Statusbar in the bottom of the window
 8 
 9         # Setting up the menu.
10         filemenu= wx.Menu()
11 
12         # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
13         filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
14         filemenu.AppendSeparator()
15         filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
16 
17         # Creating the menubar.
18         menuBar = wx.MenuBar()
19         menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
20         self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.
21         self.Show(True)
22 
23 app = wx.App(False)
24 frame = MainWindow(None, "Sample editor")
25 app.MainLoop()

[批注:

  1. 想使用中文?Of course!

          兩個地方需要修改:

    1. 代碼開始地方添加代碼的編碼格式注釋

    2. 非注釋的中文表明傳遞的是 utf-8 編碼

    示例代碼如下:

 1 #coding=utf-8
 2 #!/usr/bin/env python
 3 
 4 import wx
 5 
 6 class MainWindow(wx.Frame):
 7     def __init__(self, parent, title):
 8         wx.Frame.__init__(self, parent, title = title, size = (300, 400))
 9         self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
10         self.CreateStatusBar() # A Statusbar in the botton of the window
11 
12         # Setting up the menu.
13         filemenu = wx.Menu()
14 
15         # wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
16         filemenu.Append(wx.ID_ABOUT, "&About", u"關於簡易編輯器")
17         filemenu.AppendSeparator()
18         filemenu.Append(wx.ID_EXIT, "E&xist", u"退出應用程序")
19 
20         # Creating the menubar.
21         menuBar = wx.MenuBar()
22         menuBar.Append(filemenu, "&File")   # Adding the "filemenu" to the MenuBar
23         self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
24         self.Show(True)
25 
26 app = wx.App(False)
27 frame = MainWindow(None, "Sample editor")
28 app.MainLoop()

  2. 參數 "&About","E&xist",符號 "&" 代表可以使用快捷鍵 "Alt + A" 和 "Alt + X" 的意思。

         同理 "&File" 代表可以使用快捷鍵 "Alt + F" 來激活 "File" 菜單欄

]

小建議:注意 wx.ID_ABOUT 和 wx.ID_EXIT 這個兩個 ID 值沒!?它們是 wxWidgets 提供的標准 ID 值。使用標准 ID 值

是一個好習慣。對於這些標准 ID 值, wxWidgets 能夠根據不同的平台展示出更好的效果。

 

2.3 添加事件處理

  在 wxPython 編程中,對事件作出反應叫做事件處理。一個事件代表某種操作(如:點擊按鈕、輸入文本、移動鼠標)

發生在了你的應用程序上面。大多數界面程序都會有事件處理機制。你可以使用 Bind() 方法將一個對象和一個事件關聯起來:

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

  現在,但你選擇了 "About" 這個菜單選項,self.OnAbout 將會被執行。wx.EVT_MENU 代表:選擇了一個菜單選項。

wxWidgets 支持很多這樣的事件(參見:事件列表)。self.OnAbout 定義格式如下:

1     def OnAbout(self, event):
2         ...

  參數 "event" 回一個 wx.Event 的實例。例如:按鈕點擊事件 - wx.EVT_BUTTON 是 wx.Event 的一個子類。

  當事件發生時,函數方法將被調用執行。默認情況下,函數會處理事件,函數執行完畢后事件會停止。然后,你可以使用

event.Skip() 來跳過該層的函數處理。如下:

1 def OnButtonClick(self, event):
2     if (some_condition):
3         do_something()
4     else:
5         event.Skip()
6 
7 def OnEvent(self, event):
8     ...

  當點擊按鈕的事件發生時,方法 OnButtonClick 將被調用。如果 "some_condition" 為真,我們就 do_something(),

否則,我們就不用處理該事件,而讓較外層的事件處理機制捕獲事件並處理。

[批注:

  1. 此處描述的 "較外層的事件處理機制捕獲事件並處理" 類似 C++/Java 的異常處理機制一樣。

   例如:某一件事情發生了,需要你第一時間處理,而你使用了 wx.Skip(),就相當於你告知了你所知道的上一級的

部門你不處理該事情,然后該部門會處理這個事件,因為該部門使用了 do_something()。

]

  來,讓我們來看看我們的編輯器:

 1 import os
 2 import wx
 3 
 4 
 5 class MainWindow(wx.Frame):
 6     def __init__(self, parent, title):
 7         wx.Frame.__init__(self, parent, title=title, size=(200,100))
 8         self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
 9         self.CreateStatusBar() # A StatusBar in the bottom of the window
10 
11         # Setting up the menu.
12         filemenu= wx.Menu()
13 
14         # wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets.
15         menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
16         menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
17 
18         # Creating the menubar.
19         menuBar = wx.MenuBar()
20         menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
21         self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.
22 
23         # Set events.
24         self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
25         self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
26 
27         self.Show(True)
28 
29     def OnAbout(self,e):
30         # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
31         dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)
32         dlg.ShowModal() # Show it
33         dlg.Destroy() # finally destroy it when finished.
34 
35     def OnExit(self,e):
36         self.Close(True)  # Close the frame.
37 
38 app = wx.App(False)
39 frame = MainWindow(None, "Sample editor")
40 app.MainLoop()

注意:對於代碼

1         dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)

  我們可以省略最后的一個參數。那樣的話, wxWidget 會自動產生一個 ID 值。和使用參數 wx.ID_ANY 一樣的。

1      dlg = wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor")

 

2.4. 對話框

  當然,一個不能保存或打開文檔的編輯器是沒有太多用處的。所以引入通用對話框。這些對話框是由底層平台提供,

所以它們看起來會自然。讓我們來看看 MainWindow 的 OnOpen 方法:

 1     def OnOpen(self, e):
 2         """ Open a file """
 3         self.dirname = '';
 4         dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
 5         if dlg.ShowModal() == wx.ID_OK:
 6             self.filename = dlg.GetFilename()
 7             self.dirname = dlg.GetDirectory()
 8             f = open(os.path.join(self.dirname, self.filename), 'r')
 9             self.control.SetValue(f.read())
10             f.close()
11         dlg.Destroy()

說明:

  1. 我們使用文件對話框的構造函數來創建對話框

  2. 我們調用 ShowModal,將會創建一個對話框,Modal 表示這個對話框不會做任何事情,直到“OK”或“Cancel”

      被點擊。

  3. ShowModal 的返回值是被點擊的按鈕的 ID 值。如果用戶點擊了 "OK" ,我們就讀取文件。

  現在你可以添加對應的菜單欄,並且和 OnOpen 方法綁定起來。如果你有什么問題,可以參考最后的程序的所有

代碼。

[批注:

  1. 有的中文文檔打不開?有可能是文檔的編碼格式不是 python 默認采用的解碼格式。

    可參考如下代碼:

 1 #encoding=utf-8
 2 #!/usr/bin/env python
 3 
 4 import os
 5 import wx
 6 
 7 class MainWindow(wx.Frame):
 8     def __init__(self, parent, title):
 9         wx.Frame.__init__(self, parent, title = title, size = (400, 300))
10         self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
11         self.CreateStatusBar()
12 
13         filemenu = wx.Menu()
14 
15         menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", "Information about this programe.")
16         menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", "Terminate the programe.")
17         menuOpenfile = filemenu.Append(wx.ID_OPEN, "&Open", "Open a file")
18 
19         menuBar = wx.MenuBar()
20         menuBar.Append(filemenu, "&File")
21         self.SetMenuBar(menuBar)
22 
23         self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
24         self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
25         self.Bind(wx.EVT_MENU, self.OnOpen, menuOpenfile)
26 
27         self.Show(True)
28 
29     def OnAbout(self, e):
30         dlg = wx.MessageDialog(self, "A small text editor", "About Sample Editor", wx.OK)
31         dlg.ShowModal()
32         dlg.Destroy()
33 
34     def OnExit(self, e):
35         self.Close()
36 
37     def OnOpen(self, e):
38         """ Open a file """
39         self.dirname = '';
40         dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
41         if dlg.ShowModal() == wx.ID_OK:
42             self.filename = dlg.GetFilename()
43             self.dirname = dlg.GetDirectory()
44             f = open(os.path.join(self.dirname, self.filename), 'r')
45             self.control.SetValue(f.read().decode("utf-8"))     # 將讀到的數據轉碼
46             f.close()
47         dlg.Destroy()
48 
49 app = wx.App(False)
50 frame = MainWindow(None, "Sample Editor")
51 app.MainLoop()

]

2.5. 擴展

  當然,這個程序和正式的編輯器相比,還差太多。但是,添加其他的功能不會比我們剛學習的困難。也許你

可以從下面這些 wxPyton 的演示程序受到啟發:

  * Drag and Drop ( 拖放 )

  * MDI ( 多文檔界面 )

  * Tab view/multiple files

  * Find/Replase dialog

  * Print dialog ( Printing )

  * Macro-commands in python ( using the eval function )

[批注:

  1. 更多演示程序可以從 wxPython 的官網下載 www.wxpython.org 

]

  

3. 使用窗口

  Topics:

    * Frame

    * Windows

    * Controls/Widgets

    * Sizers

    * Validators

  這個部分,我們將展示如何使用 wxPython 的窗口和內容,包括構建輸入表單和多種小工具。我們將創造

一個小的計算報價的應用程序。如果你有界面開發方面的經驗,這將會非常簡單,而且你可能想要使用界面構造

器 - Boa-Constructor 的高級功能。

 

3.1. 概述

  3.1.1. 制作課件的元素

  在窗口里,你將使用一系列的 wxWindow 的子類來布局窗口。下面是一些你可能用得上的小工具:

  * 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 可以用來

   形成 TAB 頁。

  所有的圖形化工具對象(wxWindow 對象以及他們的子類)可以包含多個元素。例如:一個 wx.Frame 包含多個

wx.Panel, 反過來, wx.Panel 包含多個 wx.Button、wx.StaticText、wx.TextCtrl 對象。而對於如何對這些工具

進行布局將有多種方法:

  1. 你可以手動設置每一個對象相對於父對象的像素值。不同的系統,可能因為字體大小不一等而效果不一樣。一般

      不推薦使用這種方法;

  2. 你可以使用 wx.LayoutConstraints,但它稍微有一點復雜;

  3. 你可以使用 Delphi-like LayoutAnchors,那樣可以較容易的使用 wx.LayoutCOnstraints;

  4. 你可以使用 wxSizer 的子類

這篇文字將會使用 wxSizer 這種方式,因為這是我最熟悉的一種結構。

 

 3.1.2 Sizer

  Sizer 是 wx.Sizer 的一個子類,可以用來處理窗口里的各個工具,Sizer 可以:

  * 對每個工具類計算一個合適的大小

  * 每一個元素的放置都遵循統一的規則

  * 當窗口大小變化時,自動調整工具的大小和位置

  一些經常使用的布局工具(sizer):

  * wx.BoxSizer:水平或垂直布局

  * wx.GridSizer:以網格的方式布局

  * wx.FlexGridSizer:和 wx.GridSizer 類似,但更靈活

  一個 sizer 是對一系列給定的 wx.Window 對象進行設置規格。可以調用 sizer.Add,或者sizer.AddMany。

Sizer 必須要有給定的對象。Sizers 可以嵌套。

 1 import wx
 2 import os
 3 
 4 class MainWindow(wx.Frame):
 5     def __init__(self, parent, title):
 6         self.dirname=''
 7 
 8         # A "-1" in the size parameter instructs wxWidgets to use the default size.
 9         # In this case, we select 200px width and the default height.
10         wx.Frame.__init__(self, parent, title=title, size=(200,-1))
11         self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
12         self.CreateStatusBar() # A Statusbar in the bottom of the window
13 
14         # Setting up the menu.
15         filemenu= wx.Menu()
16         menuOpen = filemenu.Append(wx.ID_OPEN, "&Open"," Open a file to edit")
17         menuAbout= filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
18         menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
19 
20         # Creating the menubar.
21         menuBar = wx.MenuBar()
22         menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
23         self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.
24 
25         # Events.
26         self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)
27         self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
28         self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
29 
30         self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
31         self.buttons = []
32         for i in range(0, 6):
33             self.buttons.append(wx.Button(self, -1, "Button &"+str(i)))
34             self.sizer2.Add(self.buttons[i], 1, wx.EXPAND)
35 
36         # Use some sizers to see layout options
37         self.sizer = wx.BoxSizer(wx.VERTICAL)
38         self.sizer.Add(self.control, 1, wx.EXPAND)
39         self.sizer.Add(self.sizer2, 0, wx.EXPAND)
40 
41         #Layout sizers
42         self.SetSizer(self.sizer)
43         self.SetAutoLayout(1)
44         self.sizer.Fit(self)
45         self.Show()
46 
47     def OnAbout(self,e):
48         # Create a message dialog box
49         dlg = wx.MessageDialog(self, " A sample editor \n in wxPython", "About Sample Editor", wx.OK)
50         dlg.ShowModal() # Shows it
51         dlg.Destroy() # finally destroy it when finished.
52 
53     def OnExit(self,e):
54         self.Close(True)  # Close the frame.
55 
56     def OnOpen(self,e):
57         """ Open a file"""
58         dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
59         if dlg.ShowModal() == wx.ID_OK:
60             self.filename = dlg.GetFilename()
61             self.dirname = dlg.GetDirectory()
62             f = open(os.path.join(self.dirname, self.filename), 'r')
63             self.control.SetValue(f.read())
64             f.close()
65         dlg.Destroy()
66 
67 app = wx.App(False)
68 frame = MainWindow(None, "Sample editor")
69 app.MainLoop()

  方法 sizer.Add 有三個參數。第一個參數  control 將被添加到 sizer。第二個參數表示占用的比例因子。比如:

你希望使其成為 3:2:1 的比例關系,第二個參數你需要填寫該比例。0 表示不會隨着增長而變化。第三個參數通常

為 wx.GROW(等同於 wx.EXPAND),表示如果需要,將被重新設置大小,如果使用 wx.SHAPED,小工具的大

小將保持不變,第二個參數將表示各個小工具的間隔。

  如果第二個參數為 0,control 對象不會被重置大小,對於第三個參數,wx.ALIGN_CENTER_HORIZONTAL,

wx.ALIGN_CENTER_VERTICAL,wx.ALIGN_CENTER (both) 分別表示水平居中,垂直居中,水平垂直居中。

可以使用這些值來替代 wx.GROW,wx.SHAPED。

  你還可以選擇使用 wx.ALIGN_LEFT,wx.ALIGN_TOP,wx.ALIGN_RIGHT,wx.ALIGN_BOTTOM。默認

使用 wx.ALIGN_LEFT | wx.ALIGN_TOP。

  你將這些控件添加到 sizer 里后,下一步就是告訴 frame 或 window 使能 sizer,使用如下方式使能:

1 window.SetSizer(sizer)
2 window.SetAutoLayout(True)
3 sizer.Fit(window)

  SetSizer() 調用將告知 window(frame) 使用哪個 sizer,SetAutoLayout() 告知窗口如何放置控件和設置

控件大小,sizer.Fit() 告知 sizer 計算所有控件的位置和大小。如果你使用 sizer 這種方式設置控件位置和大小,

這是一種通用的處理過程。

 

3.1.3 菜單

  我想你會能搞一個菜單出來的,如果你看了前面關於菜單欄的實現。

3.1.4 Validators 校驗器

  當你創建一個對話框或者其他的輸入表格,你可以使用 wx.Validator 來進行簡單處理加載數據到你的表格

中,校驗輸入的數據,從表單中導出數據。wx.Validator 也可以用來處理按鍵事件和其他一些輸入框的事件。

要使用校驗器,你必須自己創建 wx.Validator 的子類(wxPython 沒有實現類似如 wx.TextValidator、

wx.GenericValidator 的類),子類通過輸入框對象 myInputField.SetValidator(myValidator) 的方式將事

件和輸入框關聯起來。

  注意:你的 wx.Validator 子類必須實現 wxValidator.Clone() 方法

 

3.2 一個例子

3.2.1 嘗試在面板 panel 中使用標簽 label

   來,我們以一個例子開始。我們的程序將會有一個 Frame 窗口,Frame 里面有一個 Panel 面板, Panel 里面

再包含一個 Label 標簽。

 1 import wx
 2 class ExampleFrame(wx.Frame):
 3     def __init__(self, parent):
 4         wx.Frame.__init__(self, parent)
 5         panel = wx.Panel(self)
 6         self.quote = wx.StaticText(panel, label="Your quote: ", pos = (20, 30))
 7         self.Show()
 8 
 9 app = wx.App(False)
10 ExampleFrame(None)
11 app.MainLoop()

  這個設計應該是非常的清晰,如果你看了 “簡易編輯器” 的實現,你應該不會有任何問題。注意,使用 sizer 比使用

pos 更合適,對於控件來說。

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

  使用 Panel 作為我們的 wx.StaticText(parent, ...) 的 parent 參數。這樣,我們的 StaticText 將顯示在我們創建

的 Panel 面板上。wx.Point 用來作為 pos 參數,wx.Size 也是一個可選的參數值,但用在這里有些不合適。

  

3.2.2 添加更多的控件

  在 wxPython 的演示程序和幫助文檔里,你會發現一些列的控件,我們將展示經常使用的控件:

  · wxButton 可點擊的按鈕

1 clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
2 self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)

  · wxTextCtrl  用戶輸入框,當輸入框內容被改變時觸發 EVT_TEXT,按鍵被按下時觸發 EVT_CHAR

1         textField = wx.TextCtrl(self)
2         self.Bind(wx.EVT_TEXT, self.OnChange, textField)
3         self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)

  例如:點擊清除按鈕時,將文本清空,將觸發 EVT_TEXT。

  · wxComboBox 下拉列表框,事件 EVT_COMBOBOX

  · wxCheckBox  校驗框,有 True/False 連個選擇

  · wxRadioBox   單選框

  來,讓我們來看看我們現在的程序是怎樣的了:

 1 import wx
 2 
 3 class ExamplePanel(wx.Panel):
 4     def __init__(self, parent):
 5         wx.Panel.__init__(self, parent)
 6 
 7         self.quote = wx.StaticText(self, label = "Your quote : ", pos = (20, 30))
 8 
 9         # A multiline TextCtrl -
10         # This is here to show how the events work in this program, don't pay too much attention to it
11         self.logger = wx.TextCtrl(self, pos = (500, 20), size= (250, 350), style = wx.TE_MULTILINE | wx.TE_READONLY)
12 
13         # A button
14         self.button = wx.Button(self, label = "Save", pos = (200, 350))
15         self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
16 
17         # the edit control - one line version
18         self.lblname = wx.StaticText(self, label = "Your name : ", pos = (20, 60))
19         self.editname = wx.TextCtrl(self, value = "Enter here your name", pos = (200, 60), size = (140, -1))
20         self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
21         self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
22 
23         # the combobox control
24         self.sampleList = ['friends', 'adverising', 'web search', 'Yellow Pages']
25         self.lblhear = wx.StaticText(self, label = "How did you hear from us ?", pos = (20, 90))
26         self.edithear = wx.ComboBox(self, pos = (200, 90), size = (95, -1), choices = self.sampleList, style = wx.CB_DROPDOWN)
27         self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
28         self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)
29 
30         # Checkbox
31         self.insure = wx.CheckBox(self, label = "Do you want Insured Shipment ?", pos = (20, 180))
32         self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
33 
34         # Radio Boxes
35         radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
36         rb = wx.RadioBox(self, label = "What color would you like ?", pos = (20, 210), choices = radioList, majorDimension = 3,
37                          style = wx.RA_SPECIFY_COLS)
38         self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)
39 
40     def EvtRadioBox(self, event):
41         self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
42 
43     def EvtComboBox(self, event):
44         self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
45 
46     def OnClick(self, event):
47         self.logger.AppendText(' Click on object with Id %d\n' % event.GetInt())
48 
49     def EvtText(self, event):
50         self.logger.AppendText('EvtText: %s\n' % event.GetString())
51 
52     def EvtChar(self, event):
53         self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
54 
55     def EvtCheckBox(self, event):
56         self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
57 
58 app = wx.App(False)
59 frame = wx.Frame(None, size = (800, 600))
60 panel = ExamplePanel(frame)
61 frame.Show()
62 app.MainLoop()

將看到類似這樣的窗口:

   我們的類變得越來越充實了,里面有很多的控件和控件事件的處理。我們添加了一個 wxTextCtrl 編輯框控件用來顯示

控件接收到的事件。

 

3.2.3 標簽頁 notebook

  wxNoteBook ,TAB 頁

 1 app = wx.App(False)
 2 frame = wx.Frame(None, title="Demo with Notebook")
 3 nb = wx.Notebook(frame)
 4
 5 
 6 nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
 7 nb.AddPage(ExamplePanel(nb), "Page Two")
 8 nb.AddPage(ExamplePanel(nb), "Page Three")
 9 frame.Show()
10 app.MainLoop()

 [批注:該程序添加到上一個程序中的]

 

3.2.4. 改善布局 - 使用 sizers

  使用絕對的位置值經常不能達到滿意的效果:如果窗口不是合適的大小,那就非常的丑陋。wxPython 有很豐富的

資料可以用來進行布局。

  · wx.BoxSizer  是最常用,也是最簡單易用的布局神器,但是它會出現很多種布局組織的可能性。它的主要功能是

大致的將控件組成列或行的方式,也會自動重組(比如窗口大小改變的時候)。

  · wx.GridSizer  wx.FlexGridSizer 也是兩個重要的布局神器,采用表格式的布局。

  來看看例子:

 1 import wx
 2 
 3 class ExamplePanel(wx.Panel):
 4     def __init__(self, parent):
 5         wx.Panel.__init__(self, parent)
 6 
 7         # create some sizers
 8         mainSizer = wx.BoxSizer(wx.VERTICAL)
 9         grid = wx.GridBagSizer(hgap = 5, vgap=5)
10         hSizer = wx.BoxSizer(wx.HORIZONTAL)
11 
12         self.quote = wx.StaticText(self, label = "Your quote: ")
13         grid.Add(self.quote, pos = (0, 0))
14 
15         # A multiline TextCtrl
16         self.logger = wx.TextCtrl(self, size = (200, 300), style = wx.TE_MULTILINE | wx.TE_READONLY)
17 
18         # A button
19         self.button = wx.Button(self, label = "Save")
20         self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
21 
22         # the edit control
23         self.lblname = wx.StaticText(self, label = "Your name: ")
24         grid.Add(self.lblname, pos = (1, 0))
25         self.editname = wx.TextCtrl(self, value = "Enter here your name: ", size = (140, -1))
26         grid.Add(self.editname, pos = (1,1))
27         self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
28         self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
29 
30         # the combobox control
31         self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
32         self.lblhear = wx.StaticText(self, label = "How did you hear from us ?")
33         grid.Add(self.lblhear, pos = (3,0))
34         self.edithear = wx.ComboBox(self, size = (95, -1), choices = self.sampleList, style = wx.CB_DROPDOWN)
35         grid.Add(self.edithear, pos = (3, 1))
36         self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
37         self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)
38 
39         # add a spacer to the sizer
40         grid.Add((10, 40), pos = (2, 0))
41 
42         # checkbox
43         self.insure = wx.CheckBox(self, label = "Do you want Insured Shipment ?")
44         grid.Add(self.insure, pos = (4, 0), span = (1, 2), flag = wx.BOTTOM, border = 5)
45         self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
46 
47         # radio boxes
48         radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
49         rb = wx.RadioBox(self, label = "What color would you link ?", pos = (20, 210), choices = radioList, majorDimension = 3,
50                          style = wx.RA_SPECIFY_COLS)
51         grid.Add(rb, pos = (5, 0), span = (1, 2))
52         self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)
53 
54         hSizer.Add(grid, 0, wx.ALL, 5)
55         hSizer.Add(self.logger)
56         mainSizer.Add(hSizer, 0, wx.ALL, 5)
57         mainSizer.Add(self.button, 0, wx.CENTER)
58         self.SetSizerAndFit(mainSizer)
59 
60 
61     def EvtRadioBox(self, event):
62         self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
63 
64     def EvtComboBox(self, event):
65         self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
66 
67     def OnClick(self, event):
68         self.logger.AppendText(' Click on object with Id %d\n' % event.GetInt())
69 
70     def EvtText(self, event):
71         self.logger.AppendText('EvtText: %s\n' % event.GetString())
72 
73     def EvtChar(self, event):
74         self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
75 
76     def EvtCheckBox(self, event):
77         self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
78 
79 
80 app = wx.App(False)
81 frame = wx.Frame(None, title = "Layout EX")
82 panel = ExamplePanel(frame)
83 frame.Show()
84 app.MainLoop()

  這個例子使用一個 GridBagSizer 來放置控件。參數 'pos' 控制如何布局控件,(x, y) 表示位置。例如:(0, 0)表示

左上角位置,(3, 5) 表示第三行,第五列。參數 'span' 運行控件跨越多行

 

 4. 響應用戶動作

  我想你現在已經知道如何搞定用戶動作的事件響應,如果你看了前面的事件處理章節的內容。

  參考: Events, Pop-up Menus

 

5. 畫圖

  · Device Contexts

  · Fonts

  · Colours

  · onPaint() methos

5.1 概述

  在這部分,我們將介紹在窗口里面畫畫。我們也將會展示如何創建右鍵彈出式菜單(pop-up menus)。

5.2 例子

  [官方暫未寫該部分的內容]

6. 使用 wxPython 

6.1 調試技術

  當一個 python 程序遇到一個未處理的異常( BUG),它會異常退出,並且會有 traceback 調用關系用來定位問

題出現的地方。wxPython 也有這樣的機制,但是有一點讓人苦惱。taceback 信息被定向標准 IO,好的 GUI 應用程

序都會是獨立的。下面有幾種方法,設置標准 IO:

 1 class MyApp (wx.App):
 2 #...
 3 #...
 4 #...
 5 myapp = MyApp() # functions normally. Stdio is redirected to its own window
 6 myapp = MyApp(0) #does not redirect stdout. Tracebacks will show up at the console.
 7 myapp = MyApp(1, 'filespec') #redirects stdout to the file 'filespec'
 8 # NOTE: These are named parameters, so you can do this for improved readability:
 9 myapp = MyApp(redirect = 1, filename = 'filespec') # will redirect stdout to 'filespec'
10 myapp = MyApp(redirect = 0) #stdio will stay at the console...

  你可以使用 Widget Inspection Tool 來調試大多數的布局事件。

6.2 PyCrust 交互式終端

  wxPython 發布了一個 PyCrust 交互式終端。你可以使用它來進行交互式測試你的布局程序。

7. 下一步

7.1 Events

  事件處理機制是 wxPython 的一個重要特性。所有的 GUI 系統都是依賴於事件處理機制的。有兩種處理機制:

  · 定義一個方法來處理事件,將一個控件的事件和方法綁定起來,當事件觸發是,回調方法,實現事件的處理

  · 使用一個預先設定的方法來處理事件。這樣的話,如果你想修改某一個控件的事件處理,需要重寫該方法

  所以, 'self.Bind(wx.EVT_SOMETHING, ACallable)' 表示:

  當 SOMETHING 事件告知了窗口 Window(self),該事件可能來自窗口的任意一個子窗口或自己,然后調用 

ACallable, self 必須是一個 wxPython 窗口的的類對象(Button、Dialog、Frame)。

  另一個 'self.Bind(wx.EVT_SOMETHING, ACallable, srcWin)' 表示:

  當 SOMETHING 事件由 'srcWin' 產生時,通過窗口的層級關系遞送到了本窗口 Window(self),然后調用

ACallable。

  但是有些事件不會被本窗口捕獲(代表着第二種方式將不起作用),所以最好使用第一種方式。這是一種基本的方式

除了菜單欄,因為他們沒有 Bind 方法。

 

7.2 Scintilla

  Scintilla 是一個基本的組件被 wxStyledTextCtrl, 提供給我們語法高亮的功能。

  參見: StyledTextCtrl

7.3 Boa-constructor

  Boa-constructor 是 wxPython 的一個 RAD IDE。

7.4 多線程

  [官方很懶啊,什么也沒留下]

7.5 管理、非管理窗口

  [官方很懶啊,什么也沒留下]

8 非常有用的鏈接[可供參考的資料]

  · http://wxPython.org/

    wxPython 官方網站了,官方提供的資源都在這了。

  · http://wxwidgets.org/

    wxWidgets 網站,要使用 wxPython 的控件,這里也有。這個的描述文檔在 wxPython 里也有。

  · http://wxpython.org/maillist.php

    這個可厲害了,和大神們交流啊,去吧!不過,如果是請教問題的話,最好先整理好問題。

  · http://www.python.org/

    python 社區

  · http://starship.python.net/crew/theller/py2exe/

    python 腳本(包括wxPython)  轉換成 Windows 系統的 exe 程序。

9. 這個...

  你已經了解了 wxPython 的主要功能特性,而且能夠寫出 wxPython 的應用程序了。不要猶豫,加入 wxPython

社區吧,訂閱郵件列表,來一起討論。

10. 感覺貢獻者

  · wxPhthon 社區

  · and 很多人 ...

11. 附錄

11.1 小小編輯器

  參見:WxHowtoSmallEditor

  1 import wx
  2 import os.path
  3 
  4 
  5 class MainWindow(wx.Frame):
  6     def __init__(self, filename='noname.txt'):
  7         super(MainWindow, self).__init__(None, size=(400,200))
  8         self.filename = filename
  9         self.dirname = '.'
 10         self.CreateInteriorWindowComponents()
 11         self.CreateExteriorWindowComponents()
 12 
 13     def CreateInteriorWindowComponents(self):
 14         ''' Create "interior" window components. In this case it is just a
 15             simple multiline text control. '''
 16         self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
 17 
 18     def CreateExteriorWindowComponents(self):
 19         ''' Create "exterior" window components, such as menu and status
 20             bar. '''
 21         self.CreateMenu()
 22         self.CreateStatusBar()
 23         self.SetTitle()
 24 
 25     def CreateMenu(self):
 26         fileMenu = wx.Menu()
 27         for id, label, helpText, handler in \
 28             [(wx.ID_ABOUT, '&About', 'Information about this program',
 29                 self.OnAbout),
 30              (wx.ID_OPEN, '&Open', 'Open a new file', self.OnOpen),
 31              (wx.ID_SAVE, '&Save', 'Save the current file', self.OnSave),
 32              (wx.ID_SAVEAS, 'Save &As', 'Save the file under a different name',
 33                 self.OnSaveAs),
 34              (None, None, None, None),
 35              (wx.ID_EXIT, 'E&xit', 'Terminate the program', self.OnExit)]:
 36             if id == None:
 37                 fileMenu.AppendSeparator()
 38             else:
 39                 item = fileMenu.Append(id, label, helpText)
 40                 self.Bind(wx.EVT_MENU, handler, item)
 41 
 42         menuBar = wx.MenuBar()
 43         menuBar.Append(fileMenu, '&File') # Add the fileMenu to the MenuBar
 44         self.SetMenuBar(menuBar)  # Add the menuBar to the Frame
 45 
 46     def SetTitle(self):
 47         # MainWindow.SetTitle overrides wx.Frame.SetTitle, so we have to
 48         # call it using super:
 49         super(MainWindow, self).SetTitle('Editor %s'%self.filename)
 50 
 51 
 52     # Helper methods:
 53 
 54     def defaultFileDialogOptions(self):
 55         ''' Return a dictionary with file dialog options that can be
 56             used in both the save file dialog as well as in the open
 57             file dialog. '''
 58         return dict(message='Choose a file', defaultDir=self.dirname,
 59                     wildcard='*.*')
 60 
 61     def askUserForFilename(self, **dialogOptions):
 62         dialog = wx.FileDialog(self, **dialogOptions)
 63         if dialog.ShowModal() == wx.ID_OK:
 64             userProvidedFilename = True
 65             self.filename = dialog.GetFilename()
 66             self.dirname = dialog.GetDirectory()
 67             self.SetTitle() # Update the window title with the new filename
 68         else:
 69             userProvidedFilename = False
 70         dialog.Destroy()
 71         return userProvidedFilename
 72 
 73     # Event handlers:
 74 
 75     def OnAbout(self, event):
 76         dialog = wx.MessageDialog(self, 'A sample editor\n'
 77             'in wxPython', 'About Sample Editor', wx.OK)
 78         dialog.ShowModal()
 79         dialog.Destroy()
 80 
 81     def OnExit(self, event):
 82         self.Close()  # Close the main window.
 83 
 84     def OnSave(self, event):
 85         textfile = open(os.path.join(self.dirname, self.filename), 'w')
 86         textfile.write(self.control.GetValue())
 87         textfile.close()
 88 
 89     def OnOpen(self, event):
 90         if self.askUserForFilename(style=wx.OPEN,
 91                                    **self.defaultFileDialogOptions()):
 92             textfile = open(os.path.join(self.dirname, self.filename), 'r')
 93             self.control.SetValue(textfile.read())
 94             textfile.close()
 95 
 96     def OnSaveAs(self, event):
 97         if self.askUserForFilename(defaultFile=self.filename, style=wx.SAVE,
 98                                    **self.defaultFileDialogOptions()):
 99             self.OnSave(event)
100 
101 
102 app = wx.App()
103 frame = MainWindow()
104 frame.Show()
105 app.MainLoop()

 

11.2 表單、TAB 頁、Sizer

  參考:WxHowtoBuildingForms

  1 import wx
  2 
  3 
  4 class Form(wx.Panel):
  5     ''' The Form class is a wx.Panel that creates a bunch of controls
  6         and handlers for callbacks. Doing the layout of the controls is
  7         the responsibility of subclasses (by means of the doLayout()
  8         method). '''
  9 
 10     def __init__(self, *args, **kwargs):
 11         super(Form, self).__init__(*args, **kwargs)
 12         self.referrers = ['friends', 'advertising', 'websearch', 'yellowpages']
 13         self.colors = ['blue', 'red', 'yellow', 'orange', 'green', 'purple',
 14                        'navy blue', 'black', 'gray']
 15         self.createControls()
 16         self.bindEvents()
 17         self.doLayout()
 18 
 19     def createControls(self):
 20         self.logger = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY)
 21         self.saveButton = wx.Button(self, label="Save")
 22         self.nameLabel = wx.StaticText(self, label="Your name:")
 23         self.nameTextCtrl = wx.TextCtrl(self, value="Enter here your name")
 24         self.referrerLabel = wx.StaticText(self,
 25             label="How did you hear from us?")
 26         self.referrerComboBox = wx.ComboBox(self, choices=self.referrers,
 27             style=wx.CB_DROPDOWN)
 28         self.insuranceCheckBox = wx.CheckBox(self,
 29             label="Do you want Insured Shipment?")
 30         self.colorRadioBox = wx.RadioBox(self,
 31             label="What color would you like?",
 32             choices=self.colors, majorDimension=3, style=wx.RA_SPECIFY_COLS)
 33 
 34     def bindEvents(self):
 35         for control, event, handler in \
 36             [(self.saveButton, wx.EVT_BUTTON, self.onSave),
 37              (self.nameTextCtrl, wx.EVT_TEXT, self.onNameEntered),
 38              (self.nameTextCtrl, wx.EVT_CHAR, self.onNameChanged),
 39              (self.referrerComboBox, wx.EVT_COMBOBOX, self.onReferrerEntered),
 40              (self.referrerComboBox, wx.EVT_TEXT, self.onReferrerEntered),
 41              (self.insuranceCheckBox, wx.EVT_CHECKBOX, self.onInsuranceChanged),
 42              (self.colorRadioBox, wx.EVT_RADIOBOX, self.onColorchanged)]:
 43             control.Bind(event, handler)
 44 
 45     def doLayout(self):
 46         ''' Layout the controls that were created by createControls().
 47             Form.doLayout() will raise a NotImplementedError because it
 48             is the responsibility of subclasses to layout the controls. '''
 49         raise NotImplementedError
 50 
 51     # Callback methods:
 52 
 53     def onColorchanged(self, event):
 54         self.__log('User wants color: %s'%self.colors[event.GetInt()])
 55 
 56     def onReferrerEntered(self, event):
 57         self.__log('User entered referrer: %s'%event.GetString())
 58 
 59     def onSave(self,event):
 60         self.__log('User clicked on button with id %d'%event.GetId())
 61 
 62     def onNameEntered(self, event):
 63         self.__log('User entered name: %s'%event.GetString())
 64 
 65     def onNameChanged(self, event):
 66         self.__log('User typed character: %d'%event.GetKeyCode())
 67         event.Skip()
 68 
 69     def onInsuranceChanged(self, event):
 70         self.__log('User wants insurance: %s'%bool(event.Checked()))
 71 
 72     # Helper method(s):
 73 
 74     def __log(self, message):
 75         ''' Private method to append a string to the logger text
 76             control. '''
 77         self.logger.AppendText('%s\n'%message)
 78 
 79 
 80 class FormWithAbsolutePositioning(Form):
 81     def doLayout(self):
 82         ''' Layout the controls by means of absolute positioning. '''
 83         for control, x, y, width, height in \
 84                 [(self.logger, 300, 20, 200, 300),
 85                  (self.nameLabel, 20, 60, -1, -1),
 86                  (self.nameTextCtrl, 150, 60, 150, -1),
 87                  (self.referrerLabel, 20, 90, -1, -1),
 88                  (self.referrerComboBox, 150, 90, 95, -1),
 89                  (self.insuranceCheckBox, 20, 180, -1, -1),
 90                  (self.colorRadioBox, 20, 210, -1, -1),
 91                  (self.saveButton, 200, 300, -1, -1)]:
 92             control.SetDimensions(x=x, y=y, width=width, height=height)
 93 
 94 
 95 class FormWithSizer(Form):
 96     def doLayout(self):
 97         ''' Layout the controls by means of sizers. '''
 98 
 99         # A horizontal BoxSizer will contain the GridSizer (on the left)
100         # and the logger text control (on the right):
101         boxSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
102         # A GridSizer will contain the other controls:
103         gridSizer = wx.FlexGridSizer(rows=5, cols=2, vgap=10, hgap=10)
104 
105         # Prepare some reusable arguments for calling sizer.Add():
106         expandOption = dict(flag=wx.EXPAND)
107         noOptions = dict()
108         emptySpace = ((0, 0), noOptions)
109 
110         # Add the controls to the sizers:
111         for control, options in \
112                 [(self.nameLabel, noOptions),
113                  (self.nameTextCtrl, expandOption),
114                  (self.referrerLabel, noOptions),
115                  (self.referrerComboBox, expandOption),
116                   emptySpace,
117                  (self.insuranceCheckBox, noOptions),
118                   emptySpace,
119                  (self.colorRadioBox, noOptions),
120                   emptySpace,
121                  (self.saveButton, dict(flag=wx.ALIGN_CENTER))]:
122             gridSizer.Add(control, **options)
123 
124         for control, options in \
125                 [(gridSizer, dict(border=5, flag=wx.ALL)),
126                  (self.logger, dict(border=5, flag=wx.ALL|wx.EXPAND,
127                     proportion=1))]:
128             boxSizer.Add(control, **options)
129 
130         self.SetSizerAndFit(boxSizer)
131 
132 
133 class FrameWithForms(wx.Frame):
134     def __init__(self, *args, **kwargs):
135         super(FrameWithForms, self).__init__(*args, **kwargs)
136         notebook = wx.Notebook(self)
137         form1 = FormWithAbsolutePositioning(notebook)
138         form2 = FormWithSizer(notebook)
139         notebook.AddPage(form1, 'Absolute Positioning')
140         notebook.AddPage(form2, 'Sizers')
141         # We just set the frame to the right size manually. This is feasible
142         # for the frame since the frame contains just one component. If the
143         # frame had contained more than one component, we would use sizers
144         # of course, as demonstrated in the FormWithSizer class above.
145         self.SetClientSize(notebook.GetBestSize())
146 
147 
148 if __name__ == '__main__':
149     app = wx.App(0)
150     frame = FrameWithForms(None, title='Demo with Notebook')
151     frame.Show()
152     app.MainLoop()

 

11.3 畫圖

  參考:WxHowtoDrawing

  1 # doodle.py
  2 
  3 '''
  4 This module contains the DoodleWindow class which is a window that you
  5 can do simple drawings upon.
  6 '''
  7 
  8 import wx
  9 
 10 
 11 class DoodleWindow(wx.Window):
 12     colours = ['Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple',
 13         'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod',
 14         'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey']
 15 
 16     thicknesses = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128]
 17 
 18     def __init__(self, parent):
 19         super(DoodleWindow, self).__init__(parent,
 20             style=wx.NO_FULL_REPAINT_ON_RESIZE)
 21         self.initDrawing()
 22         self.makeMenu()
 23         self.bindEvents()
 24         self.initBuffer()
 25 
 26     def initDrawing(self):
 27         self.SetBackgroundColour('WHITE')
 28         self.currentThickness = self.thicknesses[0]
 29         self.currentColour = self.colours[0]
 30         self.lines = []
 31         self.previousPosition = (0, 0)
 32 
 33     def bindEvents(self):
 34         for event, handler in [ \
 35                 (wx.EVT_LEFT_DOWN, self.onLeftDown), # Start drawing
 36                 (wx.EVT_LEFT_UP, self.onLeftUp),     # Stop drawing
 37                 (wx.EVT_MOTION, self.onMotion),      # Draw
 38                 (wx.EVT_RIGHT_UP, self.onRightUp),   # Popup menu
 39                 (wx.EVT_SIZE, self.onSize),          # Prepare for redraw
 40                 (wx.EVT_IDLE, self.onIdle),          # Redraw
 41                 (wx.EVT_PAINT, self.onPaint),        # Refresh
 42                 (wx.EVT_WINDOW_DESTROY, self.cleanup)]:
 43             self.Bind(event, handler)
 44 
 45     def initBuffer(self):
 46         ''' Initialize the bitmap used for buffering the display. '''
 47         size = self.GetClientSize()
 48         self.buffer = wx.EmptyBitmap(size.width, size.height)
 49         dc = wx.BufferedDC(None, self.buffer)
 50         dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
 51         dc.Clear()
 52         self.drawLines(dc, *self.lines)
 53         self.reInitBuffer = False
 54 
 55     def makeMenu(self):
 56         ''' Make a menu that can be popped up later. '''
 57         self.menu = wx.Menu()
 58         self.idToColourMap = self.addCheckableMenuItems(self.menu,
 59             self.colours)
 60         self.bindMenuEvents(menuHandler=self.onMenuSetColour,
 61             updateUIHandler=self.onCheckMenuColours,
 62             ids=self.idToColourMap.keys())
 63         self.menu.Break() # Next menu items go in a new column of the menu
 64         self.idToThicknessMap = self.addCheckableMenuItems(self.menu,
 65             self.thicknesses)
 66         self.bindMenuEvents(menuHandler=self.onMenuSetThickness,
 67             updateUIHandler=self.onCheckMenuThickness,
 68             ids=self.idToThicknessMap.keys())
 69 
 70     @staticmethod
 71     def addCheckableMenuItems(menu, items):
 72         ''' Add a checkable menu entry to menu for each item in items. This
 73             method returns a dictionary that maps the menuIds to the
 74             items. '''
 75         idToItemMapping = {}
 76         for item in items:
 77             menuId = wx.NewId()
 78             idToItemMapping[menuId] = item
 79             menu.Append(menuId, str(item), kind=wx.ITEM_CHECK)
 80         return idToItemMapping
 81 
 82     def bindMenuEvents(self, menuHandler, updateUIHandler, ids):
 83         ''' Bind the menu id's in the list ids to menuHandler and
 84             updateUIHandler. '''
 85         sortedIds = sorted(ids)
 86         firstId, lastId = sortedIds[0], sortedIds[-1]
 87         for event, handler in \
 88                 [(wx.EVT_MENU_RANGE, menuHandler),
 89                  (wx.EVT_UPDATE_UI_RANGE, updateUIHandler)]:
 90             self.Bind(event, handler, id=firstId, id2=lastId)
 91 
 92     # Event handlers:
 93 
 94     def onLeftDown(self, event):
 95         ''' Called when the left mouse button is pressed. '''
 96         self.currentLine = []
 97         self.previousPosition = event.GetPositionTuple()
 98         self.CaptureMouse()
 99 
100     def onLeftUp(self, event):
101         ''' Called when the left mouse button is released. '''
102         if self.HasCapture():
103             self.lines.append((self.currentColour, self.currentThickness,
104                 self.currentLine))
105             self.currentLine = []
106             self.ReleaseMouse()
107 
108     def onRightUp(self, event):
109         ''' Called when the right mouse button is released, will popup
110             the menu. '''
111         self.PopupMenu(self.menu)
112 
113     def onMotion(self, event):
114         ''' Called when the mouse is in motion. If the left button is
115             dragging then draw a line from the last event position to the
116             current one. Save the coordinants for redraws. '''
117         if event.Dragging() and event.LeftIsDown():
118             dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
119             currentPosition = event.GetPositionTuple()
120             lineSegment = self.previousPosition + currentPosition
121             self.drawLines(dc, (self.currentColour, self.currentThickness,
122                 [lineSegment]))
123             self.currentLine.append(lineSegment)
124             self.previousPosition = currentPosition
125 
126     def onSize(self, event):
127         ''' Called when the window is resized. We set a flag so the idle
128             handler will resize the buffer. '''
129         self.reInitBuffer = True
130 
131     def onIdle(self, event):
132         ''' If the size was changed then resize the bitmap used for double
133             buffering to match the window size.  We do it in Idle time so
134             there is only one refresh after resizing is done, not lots while
135             it is happening. '''
136         if self.reInitBuffer:
137             self.initBuffer()
138             self.Refresh(False)
139 
140     def onPaint(self, event):
141         ''' Called when the window is exposed. '''
142         # Create a buffered paint DC.  It will create the real
143         # wx.PaintDC and then blit the bitmap to it when dc is
144         # deleted.  Since we don't need to draw anything else
145         # here that's all there is to it.
146         dc = wx.BufferedPaintDC(self, self.buffer)
147 
148     def cleanup(self, event):
149         if hasattr(self, "menu"):
150             self.menu.Destroy()
151             del self.menu
152 
153     # These two event handlers are called before the menu is displayed
154     # to determine which items should be checked.
155     def onCheckMenuColours(self, event):
156         colour = self.idToColourMap[event.GetId()]
157         event.Check(colour == self.currentColour)
158 
159     def onCheckMenuThickness(self, event):
160         thickness = self.idToThicknessMap[event.GetId()]
161         event.Check(thickness == self.currentThickness)
162 
163     # Event handlers for the popup menu, uses the event ID to determine
164     # the colour or the thickness to set.
165     def onMenuSetColour(self, event):
166         self.currentColour = self.idToColourMap[event.GetId()]
167 
168     def onMenuSetThickness(self, event):
169         self.currentThickness = self.idToThicknessMap[event.GetId()]
170 
171     # Other methods
172     @staticmethod
173     def drawLines(dc, *lines):
174         ''' drawLines takes a device context (dc) and a list of lines
175         as arguments. Each line is a three-tuple: (colour, thickness,
176         linesegments). linesegments is a list of coordinates: (x1, y1,
177         x2, y2). '''
178         dc.BeginDrawing()
179         for colour, thickness, lineSegments in lines:
180             pen = wx.Pen(wx.NamedColour(colour), thickness, wx.SOLID)
181             dc.SetPen(pen)
182             for lineSegment in lineSegments:
183                 dc.DrawLine(*lineSegment)
184         dc.EndDrawing()
185 
186 
187 class DoodleFrame(wx.Frame):
188     def __init__(self, parent=None):
189         super(DoodleFrame, self).__init__(parent, title="Doodle Frame",
190             size=(800,600),
191             style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
192         doodle = DoodleWindow(self)
193 
194 
195 if __name__ == '__main__':
196     app = wx.App()
197     frame = DoodleFrame()
198     frame.Show()
199     app.MainLoop()

 

11.4 工程管理

  參見:WxProject

  該部分展示了如何使用 wxPython Style Guide。

  [批注:在 77 行的地方,筆者的測試環境提示未定義 wx.NO_3D, 故下面的代碼筆記將  'wx.NO_3D|' 去除了 ]

  1 #!/usr/bin/env python
  2 
  3 """
  4 This sample comes from an IBM developerWorks article at
  5 http://www-106.ibm.com/developerworks/library/l-wxpy/index.html
  6 
  7 This small program was adapted to demonstrate the current guide lines
  8 on http://wiki.wxpython.org/index.cgi/wxPython_20Style_20Guide.
  9 Changes are noted in readme.txt.
 10 """
 11 
 12 import sys, os
 13 import wx
 14 
 15 
 16 # Process the command line.  Not much to do;
 17 # just get the name of the project file if it's given. Simple.
 18 projfile = 'Unnamed'
 19 if len(sys.argv) > 1:
 20     projfile = sys.argv[1]
 21 
 22 
 23 def MsgDlg(window, string, caption='wxProject', style=wx.YES_NO|wx.CANCEL):
 24     """Common MessageDialog."""
 25     dlg = wx.MessageDialog(window, string, caption, style)
 26     result = dlg.ShowModal()
 27     dlg.Destroy()
 28     return result
 29 
 30 
 31 class main_window(wx.Frame):
 32     """wxProject MainFrame."""
 33     def __init__(self, parent, title):
 34         """Create the wxProject MainFrame."""
 35         wx.Frame.__init__(self, parent, title=title, size=(500, 500),
 36                           style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
 37 
 38 
 39         # Set up menu bar for the program.
 40         self.mainmenu = wx.MenuBar()                  # Create menu bar.
 41 
 42         # Make the 'Project' menu.
 43         menu = wx.Menu()
 44 
 45         item = menu.Append(wx.ID_OPEN, '&Open', 'Open project')  # Append a new menu
 46         self.Bind(wx.EVT_MENU, self.OnProjectOpen, item)  # Create and assign a menu event.
 47 
 48         item = menu.Append(wx.ID_NEW, '&New', 'New project')
 49         self.Bind(wx.EVT_MENU, self.OnProjectNew, item)
 50 
 51         item = menu.Append(wx.ID_EXIT, 'E&xit', 'Exit program')
 52         self.Bind(wx.EVT_MENU, self.OnProjectExit, item)
 53 
 54         self.mainmenu.Append(menu, '&Project')  # Add the project menu to the menu bar.
 55 
 56         # Make the 'File' menu.
 57         menu = wx.Menu()
 58 
 59         item = menu.Append(wx.ID_ANY, '&Add', 'Add file to project')
 60         self.Bind(wx.EVT_MENU, self.OnFileAdd, item)
 61 
 62         item = menu.Append(wx.ID_ANY, '&Remove', 'Remove file from project')
 63         self.Bind(wx.EVT_MENU, self.OnFileRemove, item)
 64 
 65         item = menu.Append(wx.ID_ANY, '&Open', 'Open file for editing')
 66         self.Bind(wx.EVT_MENU, self.OnFileOpen, item)
 67 
 68         item = menu.Append(wx.ID_ANY, '&Save', 'Save file')
 69         self.Bind(wx.EVT_MENU, self.OnFileSave, item)
 70 
 71         self.mainmenu.Append(menu, '&File') # Add the file menu to the menu bar.
 72 
 73         # Attach the menu bar to the window.
 74         self.SetMenuBar(self.mainmenu)
 75 
 76         # Create the splitter window.
 77         splitter = wx.SplitterWindow(self, style=wx.SP_3D)  ### Atenttion, I remove  'wx.NO_3D |'
 78         splitter.SetMinimumPaneSize(1)
 79 
 80         # Create the tree on the left.
 81         self.tree = wx.TreeCtrl(splitter, style=wx.TR_DEFAULT_STYLE)
 82         self.tree.Bind(wx.EVT_TREE_BEGIN_LABEL_EDIT, self.OnTreeLabelEdit)
 83         self.tree.Bind(wx.EVT_TREE_END_LABEL_EDIT, self.OnTreeLabelEditEnd)
 84         self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnTreeItemActivated)
 85 
 86         # Create the editor on the right.
 87         self.editor = wx.TextCtrl(splitter, style=wx.TE_MULTILINE)
 88         self.editor.Enable(0)
 89 
 90         # Install the tree and the editor.
 91         splitter.SplitVertically(self.tree, self.editor)
 92         splitter.SetSashPosition(180, True)
 93 
 94         # Some global state variables.
 95         self.projectdirty = False
 96         self.root = None
 97         self.close = False
 98 
 99         self.Bind(wx.EVT_CLOSE, self.OnProjectExit)
100 
101         self.Show(True)
102 
103     # ----------------------------------------------------------------------------------------
104     # Some nice little handlers.
105     # ----------------------------------------------------------------------------------------
106 
107     def project_open(self, project_file):
108         """Open and process a wxProject file."""
109         try:
110             input = open(project_file, 'r')
111             self.tree.DeleteAllItems()
112 
113             self.project_file = project_file
114             name = input.readline().replace ('\n', '')
115             self.SetTitle(name)
116 
117             # create the file elements in the tree control.
118             self.root = self.tree.AddRoot(name)
119             self.activeitem = self.root
120             for line in input:
121                 self.tree.AppendItem(self.root, line.replace ('\n', ''))
122             input.close()
123             self.tree.Expand(self.root)
124 
125             self.editor.Clear()
126             self.editor.Enable(False)
127 
128             self.projectdirty = False
129         except IOError:
130             pass
131 
132     def project_save(self):
133         """Save a wxProject file."""
134         try:
135             output = open(self.project_file, 'w+')
136             output.write(self.tree.GetItemText(self.root) + '\n')
137             count = self.tree.GetChildrenCount(self.root)  # collect all file (tree) items.
138             iter = 0
139             child = ''
140             for i in range(count):
141                if i == 0:
142                   child, cookie = self.tree.GetFirstChild(self.root)
143                else:
144                   child, cookie = self.tree.GetNextChild(self.root, cookie)
145                output.write(self.tree.GetItemText(child) + '\n')
146             output.close()
147             self.projectdirty = False
148         except IOError:
149             MsgDlg(self, 'There was an error saving the new project file.', 'Error!', wx.OK)
150 
151     def CheckProjectDirty(self):
152         """Were the current project changed? If so, save it before."""
153         open_it = True
154         if self.projectdirty:
155             # save the current project file first.
156             result = MsgDlg(self, 'The project has been changed.  Save?')
157             if result == wx.ID_YES:
158                 self.project_save()
159             if result == wx.ID_CANCEL:
160                 open_it = False
161         return open_it
162 
163     def CheckTreeRootItem(self):
164         """Is there any root item?"""
165         if not self.root:
166             MsgDlg(self, 'Please create or open a project before.', 'Error!', wx.OK)
167             return False
168         return True
169 
170     # ----------------------------------------------------------------------------------------
171     # Event handlers from here on out.
172     # ----------------------------------------------------------------------------------------
173 
174     def OnProjectOpen(self, event):
175         """Open a wxProject file."""
176         open_it = self.CheckProjectDirty()
177         if open_it:
178             dlg = wx.FileDialog(self, 'Choose a project to open', '.', '', '*.wxp', wx.OPEN)
179             if dlg.ShowModal() == wx.ID_OK:
180                 self.project_open(dlg.GetPath())
181             dlg.Destroy()
182 
183     def OnProjectNew(self, event):
184         """Create a new wxProject."""
185         open_it = self.CheckProjectDirty()
186         if open_it:
187             dlg = wx.TextEntryDialog(self, 'Name for new project:', 'New Project',
188                                      'New project', wx.OK|wx.CANCEL)
189             if dlg.ShowModal() == wx.ID_OK:
190                 newproj = dlg.GetValue()
191                 dlg.Destroy()
192                 dlg = wx.FileDialog(self, 'Place to store new project.', '.', '', '*.wxp', wx.SAVE)
193                 if dlg.ShowModal() == wx.ID_OK:
194                     try:
195                         # save the project file.
196                         proj = open(dlg.GetPath(), 'w')
197                         proj.write(newproj + '\n')
198                         proj.close()
199                         self.project_open(dlg.GetPath())
200                     except IOError:
201                         MsgDlg(self, 'There was an error saving the new project file.', 'Error!', wx.OK)
202             dlg.Destroy()
203 
204     def SaveCurrentFile(self):
205         """Check and save current file."""
206         go_ahead = True
207         if self.root:
208             if self.activeitem != self.root:
209                 if self.editor.IsModified():  # Save modified file before
210                     result = MsgDlg(self, 'The edited file has changed.  Save it?')
211                     if result == wx.ID_YES:
212                         self.editor.SaveFile(self.tree.GetItemText(self.activeitem))
213                     if result == wx.ID_CANCEL:
214                         go_ahead = False
215                 if go_ahead:
216                     self.tree.SetItemBold(self.activeitem, 0)
217         return go_ahead
218 
219     def OnProjectExit(self, event):
220         """Quit the program."""
221         if not self.close:
222             self.close = True
223             if not self.SaveCurrentFile():
224                 self.close = False
225             if self.projectdirty and self.close:
226                 result = MsgDlg(self, 'The project has been changed.  Save?')
227                 if result == wx.ID_YES:
228                     self.project_save()
229                 if result == wx.ID_CANCEL:
230                     self.close = False
231             if self.close:
232                 self.Close()
233         else:
234             event.Skip()
235 
236     def OnFileAdd(self, event):
237         """Adds a file to the current project."""
238         if not self.CheckTreeRootItem():
239             return
240 
241         dlg = wx.FileDialog(self, 'Choose a file to add.', '.', '', '*.*', wx.OPEN)
242         if dlg.ShowModal() == wx.ID_OK:
243             path = os.path.split(dlg.GetPath())
244             self.tree.AppendItem(self.root, path[1])
245             self.tree.Expand(self.root)
246             self.project_save()
247 
248     def OnFileRemove(self, event):
249         """Removes a file to the current project."""
250         if not self.CheckTreeRootItem():
251             return
252         item = self.tree.GetSelection()
253         if item != self.root:
254             self.tree.Delete(item)
255             self.project_save()
256 
257     def OnFileOpen(self, event):
258         """Opens current selected file."""
259         if self.root:
260             item = self.tree.GetSelection()
261             if item != self.root:
262                 self.OnTreeItemActivated(None, item)
263                 return
264         MsgDlg(self, 'There is no file to load.', 'Error!', wx.OK)
265 
266     def OnFileSave(self, event):
267         """Saves current selected file."""
268         if self.root:
269             if self.activeitem != self.root:
270                 self.editor.SaveFile(self.tree.GetItemText(self.activeitem))
271                 return
272         MsgDlg(self, 'There is no file to save.', 'Error!', wx.OK)
273 
274 
275     def OnTreeLabelEdit(self, event):
276         """Edit tree label (only root label can be edited)."""
277         item = event.GetItem()
278         if item != self.root:
279             event.Veto()
280 
281     def OnTreeLabelEditEnd(self, event):
282         """End editing the tree label."""
283         self.projectdirty = True
284 
285 
286     def OnTreeItemActivated(self, event, item=None):
287         """Tree item was activated: try to open this file."""
288         go_ahead = self.SaveCurrentFile()
289 
290         if go_ahead:
291             if event:
292                 item = event.GetItem()
293             self.activeitem = item
294             if item != self.root:
295                 # load the current selected file
296                 self.tree.SetItemBold(item, 1)
297                 self.editor.Enable(1)
298                 self.editor.LoadFile(self.tree.GetItemText(item))
299                 self.editor.SetInsertionPoint(0)
300                 self.editor.SetFocus()
301             else:
302                 self.editor.Clear()
303                 self.editor.Enable(0)
304 
305 
306 class App(wx.App):
307     """wxProject Application."""
308     def OnInit(self):
309         """Create the wxProject Application."""
310         frame = main_window(None, 'wxProject - ' + projfile)
311         if projfile != 'Unnamed':
312             frame.project_open(projfile)
313         return True
314 
315 
316 if __name__ == '__main__':
317     app = App(0)
318     app.MainLoop()

 

11.5 FAQ

  參見:FAQ

 

12. 備注

12.1 '國際慣例'

  歡迎評論和發表意見

12.2 不同系統的路徑問題

  代碼:

1 import os
2 f=open(os.path.join(self.dirname,self.filename),'r')

  在 Windows 系統上可能存在問題,如果你的目錄是在驅動盤的根目錄下。在這種情況下,普通的對話框會返回

self.dirname = 'c:' (但你期望的是 'c:\',除非你使用 MS many years)。不幸的是,os.path.join 不會自動添

加期望的符號導致打開一個文件為 'c:file.py' 而出錯。一個更好的解決方法是:

1 f=open(os.path.normpath(os.path.join(self.dirname+os.sep,self.filename)),'r')

  我想大多數情況下是正常的,除非 self.dirname 是 'c:\',因為 normpath 不會去除第一個參數的分隔符。或

着一個更優雅的解決方法,使用 os.path.isbas() 。

 

12.3 使用 sizer 的步驟

   上面我們使用的是這種方式:

1 window.SetSizer(sizer)
2 window.SetAutoLayout(true)
3 sizer.Fit(window)

  我們也可以使用下面的方式:

1 window.SetSizerAndFit(sizer)

 

12.4 怎樣使能 tab 的切換功能

  對於一個沒有對話框的窗口來講,使能 TAB 按鍵功能真的比較難:僅有的線索是整個窗口使能 style wxTAB_TRAVERSAL,

但是卻不是很清晰。我最后得到的答案是:

  1)需要使能 TAB 功能的控件或窗口,開啟 style wxTAB_TRAVERSAL,例如:

1 class ContactsPanel(wx.Panel):
2     def __init__(self, parent,id):
3         wx.Panel.__init__(self, parent, id, wx.DefaultPosition,wx.DefaultSize,
4                          wx.RAISED_BORDER|wx.TAB_TRAVERSAL)

  2)TAB 的順序是根據你添加控件的順序來的(來源於:Massimiliano Sartor)

  3)TAB 的順序也有可能和創建的順序有關。我猜測是由於控件的 ID 號。使用 sizer、panel 沒有太多幫助

    (來源於:Keith Veleba)

  4)有一種慣用方法來設置 TAB 的順序:

1 order = (control1, control2, control3, ...)
2 for i in xrange(len(order) - 1):
3     order[i+1].MoveAfterInTabOrder(order[i])

  list 列表里是真正的控件的 TAB 順序,對於添加新控件,這樣能夠很容易的改變順序(來源於: Don Dwiggins)

 

 

 

 

 

 

 

 

 

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM