wxPython入门
Contents
第一个应用程序: "Hello, World"
作为常见,我们首先编写一个小的“Hello,world”应用程序。下面是代码:
1 #!/usr/bin/env python
2 import wx
3
4 app = wx.App(False) #创建一个新的应用程序对象,不能直接输出标准输出流/标准错误流到一个窗口。
5 frame = wx.Frame(None, wx.ID_ANY, "Hello World") #一个框架作为顶级窗口。
6 frame.Show(True) #显示这个框架.
7 app.MainLoop()
注释:
app = wx.App(False) |
每个wxPython应用程序对象都是wx.App的一个实例。 对于大多数简单的应用程序可以使用wx.App。 当你需要一个更加复杂的应用时,可能需要扩展wx.App 类。 "False"意味着"不能直接输出标准输出流/标准错误流到一个窗口"。 |
wx.Frame(None,wx.ID_ANY,"Hello") |
wx.Frame是一个顶级窗口。 语法是: x.Frame(Parent, Id, Title)。大部分都是这样的结构 (一个父对象,Id). 举个例子, 我们使用None 代表 "没有父窗口"和wx.ID_ANY由wxWidgets设定一个id . |
frame.Show(True) |
显示这个框架。 |
app.MainLoop() |
最后,我们开始应用程序的MainLoop,其作用是处理事件。 |
注: 你总是想使用wx.ID_ANY或者另一个标准ID为了wxWidgets. 你也可以生成自己的IDs, 但是没必要每次这样做。
运行这个,你会看到这样的效果:
窗口还是框架?
当人们谈论到图形界面时,通常会说到窗口、菜单和图标。 很自然, 你期望 wx.Window 意味着屏幕上的窗口。不幸的是,不是这样的.一个wx.Window是所有可见元素的基类(按钮, 菜单等),并且我们通常所认为程序窗口是一个wx.Frame。这个不一致会导致很多新的用户混乱。
构建一个简单的文本编辑器
在本教程中,我们将构建一个简单的文本编辑器.在这个过程中,我们将探讨几个组件,并了解事件和回调等特点。
第一步
第一步就是建立一个带文本框的框架。 一个文本框是一个 wx.TextCtrl 组件。 默认情况下,文本框是单行模式,但是你可以使用 wx.TE_MULTILINE参数允许输入多行文本。
1 #!/usr/bin/env python
2 import wx
3 class MyFrame(wx.Frame):
4 """ We simply derive a new class of Frame. """
5 def __init__(self, parent, title):
6 wx.Frame.__init__(self, parent, title=title, size=(200,100))
7 self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
8 self.Show(True)
9
10 app = wx.App(False)
11 frame = MyFrame(None, 'Small editor')
12 app.MainLoop()
在这个例子中,我们从wx.Frame 派生和覆盖其 __init__ 方法。 在这里,我们声明一个新 wx.TextCtrl,一个简单的文本编辑框。请注意,由于MyFrame 已经运行了self.Show()在__init__方法, 我们不再需要调用 frame.Show()。
添加一个菜单栏
每个应用程序都应该有一个菜单栏和状态栏。将它们添加:
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() #窗口下创建一个状态栏
8
9 #创建菜单
10 filemenu= wx.Menu()
11
12 #wx.ID_ABOUT和wx.ID_EXIT是由wxWidgets提供的标准ID
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 #创建菜单栏
18 menuBar = wx.MenuBar()
19 menuBar.Append(filemenu,"&File") #添加"filemenu"到菜单栏
20 self.SetMenuBar(menuBar) #添加菜单栏
21 self.Show(True)
22
23 app = wx.App(False)
24 frame = MainWindow(None, "Sample editor")
25 app.MainLoop()
提示: 注意wx.ID_ABOUT 和 wx.ID_EXIT 标识. 这些是wxWidgets提供的标准标识(点击这里查看完整清单).如果一个可用的,使用标准的ID是个好习惯。这有助于wxWidgets知道如何在每个平台显示,使它看起来更加本地化。
事件处理
在wxPython中对事件做出反应称为事件处理。事件指一些事情发生在你的应用程序 (按钮单击, 文本输入, 鼠标移动, 等等)。 GUI编程的大部分是对事件作出响应。 绑定一个对象事件使用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是"选择菜单项"事件。您可以了解更多的事件(查看完整列表)。 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是true,我们 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() #窗口下创建一个状态栏
10
11 #创建菜单
12 filemenu= wx.Menu()
13
14 #wx.ID_ABOUT和wx.ID_EXIT是由wxWidgets提供的标准ID
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 #创建菜单栏
19 menuBar = wx.MenuBar()
20 menuBar.Append(filemenu,"&File") #添加"filemenu"到菜单栏
21 self.SetMenuBar(menuBar) #添加菜单栏
22
23 #设置事件
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 #OK按钮的一个消息对话框。 wx.OK是wxWidgets的一个标准ID。
31 dlg = wx.MessageDialog( self, "A small text editor", "About Sample Editor", wx.OK)
32 dlg.ShowModal() #显示他
33 dlg.Destroy() #最后完成时销毁他
34
35 def OnExit(self,e):
36 self.Close(True) #关闭框架
37
38 app = wx.App(False)
39 frame = MainWindow(None, "Sample editor")
40 app.MainLoop()
1 wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor", wx.OK)
我们可以省略ID。在这种情况下wxWidget自动生成一个ID(就像如果你指定的wx.ID_ANY):
1 wx.MessageDialog( self, "A small editor in wxPython", "About Sample Editor")
对话框
当然如果一个编辑器不能保存或打开文档,那么是没用的。这就需要通用对话框了。常见的对话框由底层平台提供,以至于让应用程序看起来更加地本地化。这是一个OnOpen方法的使用在 MainWindow中:
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()
说明:
- 首先,我们通过调用相应的构造函数。
然后, 我们调用 ShowModal。 打开该对话框 - "Modal"意味着用户不能做任何事情,直到他点击确定或取消。
ShowModal 的返回值是你按下按钮的ID. 如果用户按下确定,我们读取该文件。
您现在应该可以进入菜单中添加相应的条目,并把它连接到 OnOpen方法。 如果遇到了问题,可以向下滚动查看完整的源代码。
可能的扩展
当然,这个还远远没达到像样的编辑器。但是,加入其他功能应该不是非常的困难。你也许可以从wxPython示例中找到灵感:
- 拖放
- MDI多文档界面
- 标签查看/多个文件
- 查找/替换对话框
打印对话框(Printing)
- 宏命令在Python(使用eval函数)
- 等等 ...
窗口化工作
主题:
- 框架
- 窗口
- 控制/部件
- Sizers
- 验证
在这节,我们将要学习窗口和所包含内容的处理方式,包括构建输入表单和使用各个部件/控制。 我们将要编写一个计算报价的小程序。如果您已经是经验丰富的图形用户界面开发人员,这将是容易的,你可以继续前进到本页面Boa-Constructor的学习在高级主题章节中。
概述
布局可视元素
- 在一个框架中,您将使用一组wxWindow子类来充实框架的内容。 下面是一些比较常见的元素,你可能想放在你的框架中:
wx.MenuBar,这是放在框架顶部的菜单栏。
wx.StatusBar, 这是显示在框架底部的状态栏信息。
wx.ToolBar,在你的框架中放一个工具栏。
wx.Control子类。这些对象,代表用户界面部件(即,它显示数据/或处理用户输入的可见元素)。wx.Control对象的实例包括 wx.Button, wxStaticText, wx.TextCtrl 和 wx.ComboBox.
wx.Panel是一个保存各种wx.Control对象的容器。放你的wx.Control对象在wx.Panel意味着用户可以从一个UI组件到下一个进行切换。
所有的可视元素(wxWindow对象和它们的子类)都可以容纳子元素。因此,举例来说,一个wx.Frame可以拥有几个wx.Panel对象,包含几个wx.Button, wx.StaticText和wx.TextCtrl对象, 给你展示元素的整个层次结构:
注意:这只是说明可视化元素的相互关联 -- 而不是 他们都要布置到框架中。 为了处理框架中元素的布局, 你有几种选择: - 你可以手动指定它的位置通过精确在父窗口内的像素坐标。 由于在平台之间字体大小等差异,这个选项一般不推荐。
你可以使用wx.LayoutConstraints, 虽然这些使用起来是相当复杂。
你可以使用Delphi-like LayoutAnchors,这样可以更加容易的使用wx.LayoutConstraints.
- 你可以使用wxSizer的子类.
这个文档集中于wxSizers的使用,因为这些是我最熟悉的方案。
TODO:添加其它元素的放置计划???。获得有关其他元素放置计划的印象: http://wxpython.org/tut-part2.php
Sizers
sizer (wx.Sizer的一个子类)可用于处理在一个窗口或框架中的可视元素的安排。 Sizers可以:
- 计算出可视元素一个合适的大小。
- 按一定的规则放置元素。
- 动态调整大小和/或重新定位元素当帧大小变化时.
sizers较常见的类型包括:
wx.BoxSizer, 水平放置可视元素。
wx.GridSizer, 网状布局可视元素。
wx.FlexGridSizer, w它类似于一个wx.GridSizer,但它允许更灵活的布局视觉元素。
一个sizer会被给定一个wx.Window对象大小的列表,不管是通过调用sizer.Add(window, options...), 或者是调用sizer.AddMany(...)。 一个sizer将仅仅工作在被给定的元素。 Sizers可以嵌套。也就是说, 你可以添加一个sizer到另一个sizer, 例如有两行按钮(通过水平的wx.BoxSizer布局)包含在另一个 wx.BoxSizer 的上面, 如图这样:
注意:请注意上面这个例子不是两行三列的布局六个按钮 -- 如果要这样做, 你应当使用wxGridSizer.
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()
The sizer.Add method has three arguments. The first one specifies the control to include in the sizer. The second one is a weight factor which means that this control will be sized in proportion to other ones. For example, if you had three edit controls and you wanted them to have the proportions 3:2:1 then you would specify these factors as arguments when adding the controls. 0 means that this control or sizer will not grow. The third argument is normally wx.GROW (same as wx.EXPAND) which means the control will be resized when necessary. If you use wx.SHAPED instead, the controls aspect ratio will remain the same.
If the second parameter is 0, i.e. the control will not be resized, the third parameter may indicate if the control should be centered horizontally and/or vertically by using wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_CENTER_VERTICAL, or wx.ALIGN_CENTER (for both) instead of wx.GROW or wx.SHAPED as that third parameter.
You can alternatively specify combinations of wx.ALIGN_LEFT, wx.ALIGN_TOP, wx.ALIGN_RIGHT, and wx.ALIGN_BOTTOM. The default behavior is equivalent to wx.ALIGN_LEFT | wx.ALIGN_TOP.
One potentially confusing aspect of the wx.Sizer and its sub-classes is the distinction between a sizer and a parent window. When you create objects to go inside a sizer, you do not make the sizer the object's parent window. A sizer is a way of laying out windows, it is not a window in itself. In the above example, all six buttons would be created with the parent window being the frame or window which encloses the buttons -- not the sizer. If you try to create a visual element and pass the sizer as the parent window, your program will crash.
- Once you have set up your visual elements and added them to a sizer (or to a nested set of sizers), the next step is to tell your frame or window to use the sizer. You do this in three steps:
1 window.SetSizer(sizer)
2 window.SetAutoLayout(true)
3 sizer.Fit(window)
The SetSizer() call tells your window (or frame) which sizer to use. The call to SetAutoLayout() tells your window to use the sizer to position and size your components. And finally, the call to sizer.Fit() tells the sizer to calculate the initial size and position for all its elements. If you are using sizers, this is the normal process you would go through to set up your window or frame's contents before it is displayed for the first time.
Menus
TODO:
Validators
When you create a dialog box or other input form, you can use a wx.Validator to simplify the process of loading data into your form, validating the entered data, and extracting the data out of the form again. wx.Validator can also be used to intercept keystrokes and other events within an input field. To use a validator, you have to create your own sub-class of wx.Validator (neither wx.TextValidator nor wx.GenericValidator are implemented in wxPython). This sub-class is then associated with your input field by calling myInputField.SetValidator(myValidator).
Note: Your wx.Validator sub-class must implement the wxValidator.Clone() method.
A Working Example
Our first label within a panel
- Let's start with an example. Our program is going to have a single Frame with a panel [7] containing a label [8]:
1 import wx
2 class ExampleFrame(wx.Frame):
3 def __init__(self, parent):
4 wx.Frame.__init__(self, parent)
5 self.quote = wx.StaticText(self, label="Your quote :", pos=(20, 30), size=(200, -1))
6 self.Show()
7
8 app = wx.App(False)
9 ExampleFrame(None)
10 app.MainLoop()
- This design should be clear and you should not have any problem with it if you read the Small Editor section of this howto. Note: a sizer should be used here instead of specifying the position of each widget. Notice in the line:
1 self.quote = wx.StaticText(self, label="Your quote :", pos=(20, 30))
the use of self as parent parameter of our wxStaticText. Our Static text is going to be on the panel we have created just before. The second parameter refers to the Id number of the new control. As this static control is not really going to be sending events, we don't care about this parameter. The wxPoint is used as positioning parameter. There is also an optional wxSize parameter but its use here is not justified.
[7] According to wxPython documentation:
- "A panel is a window on which controls are placed. It is usually placed within a frame. It contains minimal extra functionality over and above its parent class wxWindow; its main purpose is to be similar in appearance and functionality to a dialog, but with the flexibility of having any window as a parent.", in fact, it is a simple window used as a (grayed) background for other objects which are meant to deal with data entry. These are generally known as Controls or Widgets.
[8] A label is used to display text that is not supposed to interact with the user.
Adding a few more controls
- You will find a complete list of the numerous Controls that exist in wxPython in the demo and help, but here we are going to present those most frequently used:
wxButton The most basic Control: A button showing a text that you can click. For example, here is a "Clear" button (e.g. to clear a text):
1 clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
2 self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
wxTextCtrl This control let the user input text. It generates two main events. EVT_TEXT is called whenever the text changes. EVT_CHAR is called whenever a key has been pressed.
1 textField = wx.TextCtrl(self)
2 self.Bind(wx.EVT_TEXT, self.OnChange, textField)
3 self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
- For example: If the user presses the "Clear" button and that clears the text field, that will generate an EVT_TEXT event, but not an EVT_CHAR event.
wxComboBox A combobox is very similar to wxTextCtrl but in addition to the events generated by wxTextCtrl, wxComboBox has the EVT_COMBOBOX event.
wxCheckBox The checkbox is a control that gives the user true/false choice.
wxRadioBox The radiobox lets the user choose from a list of options.
Let's have a closer look at what Form1 looks like now:
1 import wx
2 class ExamplePanel(wx.Panel):
3 def __init__(self, parent):
4 wx.Panel.__init__(self, parent)
5 self.quote = wx.StaticText(self, label="Your quote :", pos=(20, 30))
6
7 # A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
8 self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
9
10 # A button
11 self.button =wx.Button(self, label="Save", pos=(200, 325))
12 self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
13
14 # the edit control - one line version.
15 self.lblname = wx.StaticText(self, label="Your name :", pos=(20,60))
16 self.editname = wx.TextCtrl(self, value="Enter here your name", pos=(150, 60), size=(140,-1))
17 self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
18 self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
19
20 # the combobox Control
21 self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
22 self.lblhear = wx.StaticText(self, label="How did you hear from us ?", pos=(20, 90))
23 self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), choices=self.sampleList, style=wx.CB_DROPDOWN)
24 self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
25 self.Bind(wx.EVT_TEXT, self.EvtText,self.edithear)
26
27 # Checkbox
28 self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?", pos=(20,180))
29 self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
30
31 # Radio Boxes
32 radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
33 rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList, majorDimension=3,
34 style=wx.RA_SPECIFY_COLS)
35 self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)
36
37 def EvtRadioBox(self, event):
38 self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
39 def EvtComboBox(self, event):
40 self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
41 def OnClick(self,event):
42 self.logger.AppendText(" Click on object with Id %d\n" %event.GetId())
43 def EvtText(self, event):
44 self.logger.AppendText('EvtText: %s\n' % event.GetString())
45 def EvtChar(self, event):
46 self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
47 event.Skip()
48 def EvtCheckBox(self, event):
49 self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
50
51
52 app = wx.App(False)
53 frame = wx.Frame(None)
54 panel = ExamplePanel(frame)
55 frame.Show()
56 app.MainLoop()
- Our class has grown a lot bigger; it has now a lot of controls and
those controls react. We have added a special wxTextCtrl control to show the various events that are sent by the controls.
The notebook
- Sometimes, a form grows too big to fit on a single page. The
wxNoteBook is used in that kind of case : It allows the user to navigate quickly between a small amount of pages by clicking on associated tabs. We implement this by putting the wxNotebook instead of our form into the main Frame and then add our Form1 into the notebook by using method AddPage.
Note how ExamplePanel's parent is the Notebook. This is important.
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()
Improving the layout - Using Sizers
- Using absolute positioning is often not very satisfying: The result is ugly if the windows are not (for one reason or another) the
right size. WxPython has very rich vocabulary of objects to lay out controls.
wx.BoxSizer is the most common and simple layout object but it permits a vast range of possibilities. Its role is roughly to arrange a set of controls in a line or in a row and rearrange them when needed (i.e. when the global size is changed).
wx.GridSizer and wx.FlexGridSizer are two very important layout tools. They arrange the controls in a tabular layout.
Here is the sample above re-written to use sizers:
1 class ExamplePanel(wx.Panel):
2 def __init__(self, parent):
3 wx.Panel.__init__(self, parent)
4
5 # create some sizers
6 mainSizer = wx.BoxSizer(wx.VERTICAL)
7 grid = wx.GridBagSizer(hgap=5, vgap=5)
8 hSizer = wx.BoxSizer(wx.HORIZONTAL)
9
10 self.quote = wx.StaticText(self, label="Your quote: ")
11 grid.Add(self.quote, pos=(0,0))
12
13 # A multiline TextCtrl - This is here to show how the events work in this program, don't pay too much attention to it
14 self.logger = wx.TextCtrl(self, size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
15
16 # A button
17 self.button =wx.Button(self, label="Save")
18 self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
19
20 # the edit control - one line version.
21 self.lblname = wx.StaticText(self, label="Your name :")
22 grid.Add(self.lblname, pos=(1,0))
23 self.editname = wx.TextCtrl(self, value="Enter here your name", size=(140,-1))
24 grid.Add(self.editname, pos=(1,1))
25 self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
26 self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
27
28 # the combobox Control
29 self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
30 self.lblhear = wx.StaticText(self, label="How did you hear from us ?")
31 grid.Add(self.lblhear, pos=(3,0))
32 self.edithear = wx.ComboBox(self, size=(95, -1), choices=self.sampleList, style=wx.CB_DROPDOWN)
33 grid.Add(self.edithear, pos=(3,1))
34 self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
35 self.Bind(wx.EVT_TEXT, self.EvtText,self.edithear)
36
37 # add a spacer to the sizer
38 grid.Add((10, 40), pos=(2,0))
39
40 # Checkbox
41 self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?")
42 grid.Add(self.insure, pos=(4,0), span=(1,2), flag=wx.BOTTOM, border=5)
43 self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
44
45 # Radio Boxes
46 radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
47 rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList, majorDimension=3,
48 style=wx.RA_SPECIFY_COLS)
49 grid.Add(rb, pos=(5,0), span=(1,2))
50 self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, rb)
51
52 hSizer.Add(grid, 0, wx.ALL, 5)
53 hSizer.Add(self.logger)
54 mainSizer.Add(hSizer, 0, wx.ALL, 5)
55 mainSizer.Add(self.button, 0, wx.CENTER)
56 self.SetSizerAndFit(mainSizer)
This example uses a GridBagSizer to place its items. The "pos" argument controls where in the grid the widget is placed, in (x, y) positions. For instance, (0, 0) is the top-left position, whereas (3, 5) would be the third row down, and the fifth column across. The span argument allows a widget to span over multiple rows or columns.
Responding to User Actions
Topics:
- Events
- Pop-up Menus
Overview
- Concepts to go here.
A Working Example
TODO: Example to go here.
Drawing
Topics:
- Device Contexts
- Fonts
- Colours
- onPaint() methods
Overview
- In this section, we shall try to introduce the way to draw inside a window. We'll also show how to create a menu that pops up when there is a right-click inside the main window.
A Working Example
Using wxPython
Debugging Techniques
- When a python program hits an unhandled exception (bug!) in your program, it aborts with a traceback that is useful in locating the source of the problem. wxPython programs do the same, but with a twist. The traceback is routed to stdio, which is captured in a nice GUI frame independent of your program. If an exception shows up in an event handler, the traceback is displayed, and your program continues on as best it can. BUT, if the exception occurs while initializing your program, the traceback will show up, and then your program will abort, taking the stdio window (and your traceback) with it before even the fastest reader can make sense of it. You can keep stdio from being hijacked by wxPython by providing a couple of optional parameters when you instantiate your wxApp. An example says it best:
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...
You can also use the Widget Inspection Tool to help debug most layout issues.
TODO: Discuss source debuggers, event loop conflicts, etc.
PyCrust interactive shell
wxPython distribution comes with the nice PyCrust shell. With it you can interactvely test your layout. Here is a screenshot with a sample code.
Deploying your wxPython Application
Next Steps
Events
- Event handling is one of the key feature of wxPython. All GUI systems known to us rely on events to distribute information between various applications. Deciding what to do when a particular event is received is every GUI-application's job and is called Event Handling. Back in the old days before object programming, dealing with events meant you would have a "switch" operation that decided what to do with a particular type of event. With the arrival of Object Oriented Programming things are not that simple any more : There are now two ways of handling events:
- One method (e.g. Java one) relies on what are called event handlers. Event handlers are attached to a particular object and bound to a callback function/method. When the object receives a particular type of event, the event handler triggers the callback function.
- The other approach is to give predetermined names to methods that are supposed to handle a particular event. This way, if you want to modify the response of a particular class to a particular event, you have to derive your class and overload the right method.
So then "self.Bind(wx.EVT_SOMETHING, ACallable)" would mean:
When a SOMETHING event is delivered to this Window (self), and it comes from any child window or itself, then call ACallable, and self must be a class derived from a wxPython window (e.g. a Button, a Dialog, a Frame), and "ACallable" can be any function, though the usual choice by the programmer is to make it a function within the above-mentioned class.
The second version "self.Bind(wx.EVT_SOMETHING, ACallable, srcWin)" means:
When a SOMETHING event is generated by "srcWin", and it comes up through the window hierarchy to this Window (self), then call ACallable.
But some events can only be caught in the Window in which they are generated (which means the second form then won't do anything), so it is best to use the first form whenever possible, which would be basically everywhere except for menu items, which don't have a Bind() method.
TODO: Creating custom event handlers event. Skip() -- non-intuitive meaning of this method.
Scintilla
- Scintilla is the base component used by wxStyledTextCtrl which gives us syntax coloring in wxPython.
TODO: Describe minimal modifications needed to the simple editor example, in order to syntax color the text with StyledTextCtrl, instead of just the TextCtrl?
Boa-constructor
- Boa-constructor is a RAD IDE for wxPython.
multi threading
TODO:
Managed/ Non Managed windows
Useful resources
- To start with, a very obvious website but you can also have a look in the demo shipped in with the wxPython package. It's full of very useful examples approaching nearly all the subjects you can think of. How to run the demo:
- under windows Simply select the program Run The Demo in the submenu wxPython of the start menu.
- under Linux find the demo directory in the source distribution and run "python demo.py"
- You can also try to find information on the wxWidgets website. wxPython's documentation contains all wxWidgets', so it's a bit pointless going there if you've already had a look at wxPython's documentation.
http://wxpython.org/maillist.php
- The wxPython mailing lists. This is a very good place to find specific information. Before asking anything, please search the archives first.
http://boa-constructor.sourceforge.net/
- Boa-constructor is a RAD GUI building IDE for wxPython.
http://www-106.ibm.com/developerworks/library/l-wxpy/index.html
- An excellent article for newbies.
http://www.oreillynet.com/pub/a/Python/excerpts/chpt20/wxpython.html
- Last but not least: The book "Python Programming on Win32" by Mark Hammond and Andy Robinson has an excellent chapter on wxPython .
Scintilla is a complete editing component for which wxPython offers bindings (a control named wxStyledTextCtrl2 ).
- The reference website for the python community.
http://starship.python.net/crew/theller/py2exe/
- With this tool, you can convert Python (and also wxPython scripts...) scripts into standalone windows programs. This makes it it easy to distribute your work.
- To start with, a very obvious website but you can also have a look in the demo shipped in with the wxPython package. It's full of very useful examples approaching nearly all the subjects you can think of. How to run the demo:
As a conclusion- slithering our way to the future of GUI-apps
You have now covered the main aspects of wxPython programming and should be able to start writing wxPython applications. Do not hesitate to take part in the wxPython community by subscribing to the mailing lists and by posting questions or answers on those lists.
Contributors
- The wxPython community
- Lucas Bruand
Rob CakeBread
- Charlie Derr
- Robin Dunn
- Michael Roberts
- Erik Westra
- We would like to thank also:
- Andrew Kuchling for his help and support and not being too bored with my never ending flow of questions and requests.
- Robin North, J-P Syed, Armel Guenneugues, Pilar Rodriguez, Matteo Caligaris for being supportive and not kidding too much around about calling me a geek.
Appendix
Small editor - Complete Source
Building Forms - Complete source
Drawing with wxPython - Complete Source
A rudimentary project organizer - Complete Source
WxProject It shows also the use of the wxPython Style Guide.
Frequently Asked Questions
See: FAQ
Comments
Comment Sample
Feel free to add any remarks or comments here. Content suggestions are welcome as well as corrections...
- Lucas Bruand
OS independent path concatenation
This code:
1 import os
2 f=open(os.path.join(self.dirname,self.filename),'r')
...can still cause problems on Windows in cases where you've used the common dialog to navigate to the root dir of the drive. In that case, the common dialog function will return self.dirname='c:' (you'd expect 'c:\' unless you've dealt with MS for many years). Unfortunately, os.path.join won't join as expected when the string contains a ':', and so an error occurs when opening 'c:file.py', since this is dependent on the cwd setting (assuming 'file.py' exists in 'c:\', but not in your cwd).
Solutions? I use:
1 f=open(self.dirname+'/'+self.filename,'r')
without any problems. I presume that lower level functions like "open" probably call os.path.normpath() which fixes it on Windows. The advantage to this is that I can use the same method ('/') for directories & URL's, and the code runs cross-platform Win/Linux. I'm sure it will get me someday, though. A better option would probably be:
1 f=open(os.path.normpath(os.path.join(self.dirname+os.sep,self.filename)),'r')
I think this works for all cases except where self.dirname is 'c:\' before you start (normpath won't strip extra separators on the 1st join arg). Or perhaps an elegant solution using os.path.isabs() would be better.
- Kevin Vap
Use the sizer in one step
I always thought setting sizer must be shorter than three lines an indeed it is (at least for wxWidgets/wxPython 2.4), the used above
1 window.SetSizer(sizer)
2 window.SetAutoLayout(true)
3 sizer.Fit(window)
might be shorten to one line
1 window.SetSizerAndFit(sizer)
- Lukasz Pankowski
How to get tabs to work
It's beeen really hard to me getting tabs to work in non-dialog windows: the only hint in the whole documentation is in wxWindow style wxTAB_TRAVERSAL description, but it's not so clear. What I finally got was:
1)For the tabbing to work at all, the window or individual panel you plonk controls/wigits on has to have as part of its style flag the following: wxTAB_TRAVERSAL ie;
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) The tab order is set by the order you add controls to the panel or frame.
(information comes from http://mail.gnu.org/archive/html/gnumed-devel/2002-07/msg00015.html)
- Massimiliano Sartor
3) Tabbing order also seems to be dependent in the order widgets are created. I assume this is due to widget ID numbers. Order of addition to sizers/panels did not seem to help me with #2. -- Keith Veleba
4) Here's a little idiom for setting tab order once you have the controls set up:
1 order = (control1, control2, control3, ...)
2 for i in xrange(len(order) - 1):
3 order[i+1].MoveAfterInTabOrder(order[i])
(The list contains the actual control objects.) This makes it easy to change the order, add new controls, etc. -- Don Dwiggins
