本文翻译自http://wiki.wxpython.org/Getting%20Started
首先声明:本人还是个菜鸟,翻译只是为了学习,就当作记笔记了。水平有限,错误和疏漏在所难免,希望各路高手能够给予指导。而且简单查了一下,好像中文世界目前还没有完整的翻译 Getting Started with wxPython 的。
按惯例,我们先来写一个 “Hello, World!” 小程序。这是代码:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
app = wx.App(False) #创建1个APP,禁用stdout/stderr重定向
frame = wx.Frame(None, wx.ID_ANY, "Hello, World!") #这是一个顶层的window
frame.Show(True) #显示这个frame
app.MainLoop()
解释:
代码 | 说明 |
---|---|
app = wx.App(False) | 每一个 wxPython 应用程序都是一个 wx.App 实例。对于大多数的简单程序,直接实例化 wx.App 即可。但如果你希望创建一个复杂的应用程序,那么可以对 wx.App class 做一些扩展。”False” 参数意味着“不要把 stdout 和 stderr 信息重定向到窗口”,当然也可以不加 “False” 参数。 |
frame = wx.Frame(None, wx.ID_ANY, “Hello, World!”) | 完整的语法是 x.Frame(Parent, Id, Title) 。在本例中,我们使用 “None” 来表示这个frame是顶层的框架,没有父框架;使用 “wx.ID_ANY” 让 wxWidgets 来给我们挑选一个ID。 |
frame.Show(True) | 显示这个Frame |
app.MainLoop() | 运行这个应用程序 |
Note1: 你还可以用 -1
来替代wx.ID_ANY
,-1
就是默认值的意思。另外 wxWidgets 还提供了其它的标准 ID(v2.8)。 你也可以自定义一个ID,但 Getting Started with wxPython 认为,没有理由那样做,用标准ID更好。
Note2: 实际上,wx.Frame的完整语法是(详细的参数介绍):
wx.Frame(Parent, ID, Title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, name="frame")
当人们谈论GUI的时候,他们通常指的是windows,menus和icons。那么自然地,你可能会认为应该用wx.Window
来代表屏幕上的一个window。但实际上不是这样的。wx.Window
是一个基础的class,所有的可视化元素,例如buttons, menus等等,都起源于wx.Window
类。而程序窗口则是一个wx.Frame
。新手经常把这2个概念搞混,需要特别留心。
现在我们来写一个简单的记事本。在这个例子中,我们会用到几个组件,来理解一些特性或功能,例如事件(events)和回调(callbacks)。
首先,我们需要创建1个frame,并且这个frame包含1个可编辑的文本框(text box)。文本框需要用wx.TextCtrl
来创建。默认情况下,文本框只能编辑1行文字——无论文字有多长,都不会换行。所以,我们需要用wx.TE_MULTILINE
参数来允许多行编辑。
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
"""We simply derive a new class of Frame."""
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title, size = (200, 100))
self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, 'Small editor')
app.MainLoop()
在这个例子中,我们生成一个wx.Frame
的子类,并重写它的__init__
方法。我们用wx.TextCtrl
来声明一个简单的文本编辑器。注意,因为在MyFrame.__init__
中已经运行了self.Show()
,所以在创建MyFrame的实例之后,就不用再调用frame.Show()
了。
所有的应用程序都会有一个菜单栏,和一个状态栏。让我们来给这个记事本程序添加一个:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
"""We simply derive a new class of Frame."""
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title, size = (200, 100))
self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
self.CreateStatusBar() #创建位于窗口的底部的状态栏
#设置菜单
filemenu = wx.Menu()
#wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
filemenu.Append(wx.ID_ABOUT, u"关于", u"关于程序的信息")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT, u"退出", u"终止应用程序")
#创建菜单栏
menuBar = wx.MenuBar()
menuBar.Append(filemenu, u"文件")
self.SetMenuBar(menuBar)
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, title = u"记事本")
app.MainLoop()
TIP: wx.ID_ABOUT
和wx.ID_EXIT
是wxWidgets提供的标准ID(查看全部标准ID)。如果有一个现成的标准ID,最好还是使用它,而不要自定义。因为这样可以让wxWidgets知道,在不同的平台怎样去显示这个组件,使它看起来更美观。
我们已经创建了1个记事本,虽然它有菜单,但是什么都做不了。我们希望点击菜单之后,程序能够做出反应,例如退出,或者保存文件。在Python中,点击菜单,点击按钮,输入文本,鼠标移动等等,都被称为事件event,而对event做出反应,则被称为event handling。对不同的event做出不同的响应,这是GUI程序的根本。我们可以使用Bind()
方法,将1个对象Object和1个时间event建立绑定关系。
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self,parent, title=title, size=(200,100))
...
menuItem = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
self.Bind(wx.EVT_MENU, self.OnAbout, menuItem)
这段代码意味着:从现在开始,一旦用户点击了菜单中的 “About” 项目,self.OnAbout
就会被执行。
Note: Bind()
之后,运行我的程序就提示编码错误,不能再使用中文了,所以下面的代码示例都是全英文的。不知道这是不是python(x,y)独有的问题。谁能帮我解答一下?
wx.EVT_MENU
指代“选择菜单中的项目”这个事件。wxWidgets 提供了很多的事件,可以点这里查看不完整的列表,也可以使用下面的代码打印完整的列表。所有的事件都是wx.Event
的子类。
import wx
for x in dir(wx):
if x.startswith('EVT_'):
print x
如果直接运行上面的Bind程序,会提示不存在OnAbout这个attribute。还需要在Class中声明self.OnAbout
方法:
def OnAbout(self, event):
...
这里的event参数是wx.Event
的子类的一个实例。
当event发生的时候,method就会被执行。默认情况下,这个method会处理event,并且当callback完成之后,event也会停止。但是在一些结构化的事件处理器event handlers中,我们可以使用event.Skip()
来跳过一个event。例如
def OnButtonClick(self, event):
if (某种条件):
做某事()
else:
event.Skip()
def OnEvent(self, event):
...
当一个点击按钮的事件发生时,OnButtonClick会被调用。如果“某种条件”为真,我们就会“做某事()”。否则我们就会让其它的event handler来处理这个事件。
现在来看看我们的程序:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class MainWindow(wx.Frame):
"""We simply derive a new class of Frame."""
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title, size = (600, 400))
self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
self.CreateStatusBar() # 创建位于窗口的底部的状态栏
# 设置菜单
filemenu = wx.Menu()
# wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
" Information about this program") # (ID, 项目名称, 状态栏信息)
filemenu.AppendSeparator()
menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
" Terminate the program") # (ID, 项目名称, 状态栏信息)
# 创建菜单栏
menuBar = wx.MenuBar()
menuBar.Append(filemenu, "&File") # 在菜单栏中添加filemenu菜单
self.SetMenuBar(menuBar) # 在frame中添加菜单栏
# 设置events
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
self.Show(True)
def OnAbout(self, e):
# 创建一个带"OK"按钮的对话框。wx.OK是wxWidgets提供的标准ID
dlg = wx.MessageDialog(self, "A small text editor.", \
"About Sample Editor", wx.OK) # 语法是(self, 内容, 标题, ID)
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 当结束之后关闭对话框
def OnExit(self, e):
self.Close(True) # 关闭整个frame
app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()
Note1: 上述代码的菜单项目名称”&About”, “E&xit”, “&File” 中的 “&”是做什么用的? “&” 的位置也不一样,分别意味着什么?如果直接print "&About"
,会把 “&” 打印出来。但是在上面的应用程序菜单中看不到 “&”。而且我试过把 “&”去掉,没有任何变化。谁能帮我解答一下?
Note2: 下面代码中的wx.OK
可以省略,此时等于wx.ID_ANY
dlg = wx.MessageDialog(self, "A small text editor.", \
"About Sample Editor", wx.OK) # 语法是(self, 内容, 标题, ID)
当然,一个文本编辑器不能够没有打开或保存文档的功能——这些功能是由对话来实现的。一般对话由底层平台提供,这样你的应用程序看上去就像是一个原生程序。在本例中,对话由 MainWindow
的 OnOpen
方法来实施:
def OnOpen(self,e):
""" Open a file"""
self.dirname = ''
dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
self.filename = dlg.GetFilename()
self.dirname = dlg.GetDirectory()
f = open(os.path.join(self.dirname, self.filename), 'r')
self.control.SetValue(f.read())
f.close()
dlg.Destroy()
解释:
ShowModal
打开对话框 - “Modal” 的意思是,在用户点击 OK 或 Cancel 之前,不能做任何的操作。ShowModal
的返回值是一个被点击按钮的 ID, 如果用户点击了 OK 按钮,程序就读取文件现在,你可以向菜单中添加相应的条目,并把它链接到OnOpen
方法。如果你遇到了问题,请向下滚动页面,查阅下文的完整代码。
当然,目前这个程序还远不是一个合格的文本编辑器。但是,添加其它的功能并不比我们刚才所完成的内容更难,你可以从 wxPython 提供的 Demo 获取灵感(点此下载Demo,选择版本后,下载 wxPython-demo-x.x.x 文件):
主题:
在这个章节,我们将会讲解 wxPython 处理窗口和窗口内容的方法,包括创建输入组件,使用各种工具和控件 widgets/controls。 我们将会创建一个计算股票价格的小程序。如果你已经是个有经验的 GUI 开发者,这部分的内容对你来说太简单了,你可以直接阅读下文的 Boa-Constructor 章节。
在 frame 里面,你可以使用若干个 wxWindow 子类来充实 frame 的内容,常用的元素有以下几种:
wx.MenuBar
, 在 frame 的顶部填加菜单栏wx.StatusBar
, 在 frame 的底部填加状态栏,显示状态信息wx.ToolBar
, 在 frame 中添加工具栏wx.Control
的子类,它们代表用户接口的widgets (例如显示数据 and/or 处理用户输入的可见元素). 常见的wx.Control
对象包括 wx.Button
, wx.StaticText
, wx.TextCtrl
和 wx.ComboBox
. wx.Panel
, 它是容纳各种wx.Control
对象的容器。把wx.Control
对象放入wx.Panel
, 用户就可以操作它们。所有的可见元素 (wxWindow 对象和它们的子类) 都能够容纳子元素。例如,一个wx.Frame
可以容纳若干个wx.Panel
对象,而这些wx.Panel
又可以容纳若干wx.Button
, wx.StaticText
和 wx.TextCtrl
对象,就像这样:
注意,这仅仅是描述可见元素的相关性,而不是描述应该怎样布局它们。如果要处理元素的布局,有以下几种选择:
wx.LayoutConstraints
, 但是很复杂LayoutAnchors
, 比wx.LayoutConstraints
简单些作为wx.Sizer
的子类,Sizer 能够被用来在 frame 或 window 中布置可见元素。它的作用包括:
一些常见的 Sizer 包括:
wx.BoxSizer
, 基于水平线或垂直线布置可见元素wx.GridSizer
, 按照网格结构来布置元素wx.FlexGridSizer
, 与wx.GridSizer
类似,但更加灵活通过调用sizer.Add(window, options...)
或者 sizer.AddMany(...)
来给出一个wx.Window
对象的列表,sizer 就能够布置它们. Sizer 还能够嵌套,你可以把 1 个 sizer 放进另 1 个 sizer 里面,例如把 2 个按水平线布置按钮的wx.BoxSizer
放进另 1 个按垂直线布置元素的wx.BoxSizer
里面,就像这样:
NOTE: 在上面的例子中,6 个按钮并不是按照 2 行 3 列来做阵列式布局的,如果要那样做,你必须使用wx.GridSizer
接下来,我们给我们的文本编辑器增加 2 个嵌套的 sizer,把 1 个水平布局的 sizer 嵌入到 1 个垂直布局的 sizer 里面:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
import os
class MainWindow(wx.Frame):
"""We simply derive a new class of Frame."""
def __init__(self, parent, title):
self.dirname = ''
# "-1"这个尺寸参数意味着通知wxWidget使用默认的尺寸
# 在这个例子中,我们使用200像素的宽度,和默认的高度
wx.Frame.__init__(self, parent, title = title, size = (200, -1))
self.control = wx.TextCtrl(self, style = wx.TE_MULTILINE)
self.CreateStatusBar() # 创建位于窗口的底部的状态栏
# 设置菜单
filemenu = wx.Menu()
# wx.ID_ABOUT和wx.ID_EXIT是wxWidgets提供的标准ID
menuOpen = filemenu.Append(wx.ID_OPEN, "&Open", " Open a file")
menuAbout = filemenu.Append(wx.ID_ABOUT, "&About", \
" Information about this program") # (ID, 项目名称, 状态栏信息)
filemenu.AppendSeparator()
menuExit = filemenu.Append(wx.ID_EXIT, "E&xit", \
" Terminate the program") # (ID, 项目名称, 状态栏信息)
# 创建菜单栏
menuBar = wx.MenuBar()
menuBar.Append(filemenu, "&File") # 在菜单栏中添加filemenu菜单
self.SetMenuBar(menuBar) # 在frame中添加菜单栏
# 设置events
self.Bind(wx.EVT_MENU, self.OnOpen, menuOpen)
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
# 设置sizers
self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)
self.buttons = []
for i in range(0, 6):
self.buttons.append(wx.Button(self, -1, "Button &" + str(i)))
self.sizer2.Add(self.buttons[i], 1, wx.SHAPED)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.control, 1, wx.EXPAND)
self.sizer.Add(self.sizer2, 0, wx.GROW)
# 激活sizer
self.SetSizer(self.sizer)
self.SetAutoLayout(True)
self.sizer.Fit(self)
self.Show(True)
def OnAbout(self, e):
# 创建一个带"OK"按钮的对话框。wx.OK是wxWidgets提供的标准ID
dlg = wx.MessageDialog(self, "A small text editor.", \
"About Sample Editor", wx.OK) # 语法是(self, 内容, 标题, ID)
dlg.ShowModal() # 显示对话框
dlg.Destroy() # 当结束之后关闭对话框
def OnExit(self, e):
self.Close(True) # 关闭整个frame
def OnOpen(self, e):
""" open a file. """
# wx.FileDialog语法:(self, parent, message, defaultDir, defaultFile,
# wildcard, style, pos)
dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*",
wx.OPEN)
if dlg.ShowModal() == wx.ID_OK:
self.filename = dlg.GetFilename()
self.dirname = dlg.GetDirectory()
f = open(os.path.join(self.dirname, self.filename), 'r') # 暂时只读
self.control.SetValue(f.read())
f.close()
dlg.Destroy()
app = wx.App(False)
frame = MainWindow(None, title = "Small editor")
app.MainLoop()
sizer.Add
方法有 3 个参数(语法):
wx.GROW
或者wx.EXPAND
, 它们的作用是一样的,这意味着控件可以调整自己的尺寸以适应 frame 尺寸的变化。如果使用wx.SHAPED
来充当第 3 个参数,那么控件的尺寸虽然可以变化,但是形状会保持不变。在上面的例子中,self.sizer.Add(self.sizer2, 0, wx.GROW)
权重因子是 0,所以我们可以看到无论 frame 的形状怎么变,self.sizer2
的高度是一直不变的,因为它的父 sizer self.sizer
是按照垂直线来布置元素的。而self.sizer2
的宽度可以变,因为第 3 个参数是wx.GROW
。
另外,self.sizer2.Add(self.buttons[i], 1, wx.SHAPED)
第 3 个参数是wx.SHAPED
,所以无论 frame 的形状和尺寸怎样变化,按钮的形状都不会变,长度和宽度一直保持着相同的比率。
flag 参数也可以使用wx.ALIGN_CENTER_HORIZONTAL
, wx.ALIGN_CENTER_VERTICAL
, 或wx.ALIGN_CENTER
(for both) 来设置元素的居中方式,还可以使用wx.ALIGN_LEFT
, wx.ALIGN_TOP
, wx.ALIGN_RIGHT
, wx.ALIGN_BOTTOM
中的 1 个或 2 个组合,来设置元素的对齐方式。默认的对齐方式是wx.ALIGN_LEFT | wx.ALIGN_TOP
.
wx.Sizer
和它的子类有一个可能会让人感到困惑的地方,就是 sizer 和父窗口之间的区别。当你把一个对象添加到 sizer 里面时,不需要指定这个对象的父窗口。sizer 只是对窗口布局的方式,它本身并不是窗口。但是在创建对象的时候就需要指定父窗口。在上面的例子中,使用wx.Button
(语法)创建按钮的时候就需要指定 frame 或 window 作为按钮的父窗口,而不是指定 sizer 来当父窗口。
一旦你完成可见元素的设置,并把它们加入到 sizer(或者嵌套的 sizer),下一步就是告诉 frame 或 window 来使用 sizer。用以下 3 个必要的步骤来完成这项工作:
window.SetSizer(sizer)
window.SetAutoLayout(True)
sizer.Fit(window)
SetSizer()
告诉你的 window (or frame) 应该使用哪个sizer。SetAutoLayout()
告诉你的 window 使用 sizer 来布局组件sizer.Fit()
告诉 sizer 计算它所容纳的元素的初始化尺寸和位置我们已经在上文讲解过菜单的设置和使用方法,不再累述。
当你创建一个对话框或者输入控件的时候,可以使用wx.Validator
来简化控件加载数据的进程,对输入的数据进行验证,或从中摘录数据。wx.Validator
还可以被用来截取控件域内发生的一些事件,例如敲击键盘的动作。要使用验证器,你必须先定义一个wx.Validator
的子类 (既不是wx.TextValidator
也不是wx.GenericValidator
),然后再调用myInputField.SetValidator
(myValidator
) 把它关联到你的控件域。
NOTE1: 你定义的wx.Validator
子类必须覆盖wxValidator.Clone()
方法。
NOTE2: 原文并没有进一步的讲解 Validators 的设置和使用方法,不过你可以参考这个 bing.com 的网页快照
现在我们来写一个小程序,这个程序很简单,frame 中只有一个包含有一个标签label[7] 的面板panel[8]:
# -*- coding: utf-8 -*-
"""
Created on Sun Dec 20 20:46:32 2015
@author: chenghit
"""
import wx
class ExampleFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
panel = wx.Panel(self)
self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))
self.Show()
app = wx.App(False)
ExampleFrame(None)
app.MainLoop()
如果你读完前面怎样编写文本编辑器的部分,你一定会觉得这个程序非常简单。但需要注意的是,在这里应该用一个 sizer 来布置组件,而不应该手工的一一指定它们的位置。注意这行代码:
self.quote = wx.StaticText(panel, label="Your quote:", pos=(20, 30))
wxStaticText
的 parent 参数是一个 panel。我们的静态文本将陈列在我们刚刚创建的 panel 上面,并使用了wxPoint
参数来定义位置。根据wx.StaticText
的语法,还可以定义一个wxSize
参数,但是在这个例子中并没有采用。
[7] 根据 wxPython 的文档:
Panel 就是放置组件的窗口,它通常被放置在 frame 里面。在继承它的父类 wxWindow 的基础上,Panel 还含有一些额外的,细微的功能性。Panel 的主要目的是在功能性和外观上和对话框相似,但是又有作为父窗口的灵活性。
事实上, 对于那些处理文字录入的对象(通常被称作控件或组件)来说,Panel 就是个灰色的背景。
[8] label 的作用仅仅是显示文本,并不和用户进行交互。
你可以在 wxPython 的 demo 和 docs 中种类繁多的控件,但是本文将只会讲解其中最常用的几种:
wxButton
是最基本的控件: 它是一个你可以点击的按钮,并带有文字。下面是一个 “Clear” 按钮的例子(比方说,你点击之后会清空文字):clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
wxTextCtrl
这个控件可以让用户输入文字,它产生 2 种主要的事件:如果文字被改变了,它会调用 EVT_TEXT ;如果键盘被按下,它会调用 EVT_CHAR。根据下面的例子,如果你按下了 “Clear” 按钮,将只会产生一个 EVT_TEXT 事件,而不会产生 EVT_CHAR 事件。textField = wx.TextCtrl(self)
self.Bind(wx.EVT_TEXT, self.OnChange, textField)
self.Bind(wx.EVT_CHAR, self.OnKeyPress, textField)
wxComboBox
下拉菜单,和 wxTextCtrl
很像,但是除了 EVT_TEXT 和 EVT_CHAR 之外,wxComboBox
还能够生成 EVT_COMBOBOX 事件. ComboBox 可以是 “下拉菜单+复选框” , 可以是 “下拉菜单+表格”…可以点击这里查看 ComboBox 的示例,虽然是 C# 写的,但 ComboBox 的概念是相同的。wxCheckBox
复选框,可以让用户做出 true/false 的选择wxRadioBox
单选框,可以让用户从一个列表中做出选择现在让我们来丰富我们的程序:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))
# 这个多行的文本框只是用来记录并显示events,不要纠结之
self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300),
style=wx.TE_MULTILINE | wx.TE_READONLY)
# 一个按钮
self.button = wx.Button(self, label='Save', pos=(200, 325))
self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
# 仅有1行的编辑控件
self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
self.editname = wx.TextCtrl(self, value='Enter here your name:',
pos=(150, 60), size=(140, -1))
self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
# 一个ComboBox控件(下拉菜单)
self.sampleList = ['friends', 'advertising', 'web search', \
'Yellow Pages']
self.lblhear = wx.StaticText(self, label="How did you hear from us ?",
pos=(20, 90))
self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1),
choices=self.sampleList,
style=wx.CB_DROPDOWN)
self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
# 注意ComboBox也绑定了EVT_TEXT事件
self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)
# 复选框
self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?",
pos=(20,180))
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
# 单选框
radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', \
'navy blue', 'black', 'gray']
self.rb = wx.RadioBox(label="What color would you like ?",
pos=(20, 210), choices=radioList, \
majorDimension=3, style=wx.RA_SPECIFY_COLS)
self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)
def OnClick(self, event):
self.logger.AppendText('Click on object with Id %d\n' % \
event.GetId())
def EvtText(self, event):
self.logger.AppendText(self, 'EvtText: %s\n' % event.GetString())
def EvtChar(self, event):
self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
event.Skip()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def EvtCheckBox(self, event):
self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
def EvtRadioBox(self, event):
self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
app = wx.App(False)
frame = wx.Frame(None)
panel = ExamplePanel(frame)
frame.Show()
app.MainLoop()
现在我们的 class 变得很复杂,我们添加了很多的控件,并且它们是可以交互的。我们还添加了一个 wxTextCtrl
控件来显示其它控件产生的事件:
有时候,一个表单(form)太大了,无法在一页内完整的显示。这时候就要用到wxNoteBook
,它允许用户通过点击标签在几个页面之间快速的浏览。我们先把wxNoteBook
放进 frame,然后再用AddPage
把上面的 Panel 放进wxNoteBook
:
app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)
# 这里把ExamplePanel重复3次放进Notebook
nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")
frame.Show()
app.MainLoop()
NOTE: 现在 ExamplePanel 的父窗口是 Notebook 了,这很关键。
严格的定义每个元素的位置并不会带来理想的显示效果,因为总是有很多原因导致 frame 的尺寸并不是我们希望的那样的大小。上文我们已经讲解过wx.BoxSizer
, wx.GridSizer
, 和wx.FlexGridSizer
, 现在我们再介绍一种:wx.GridBagSizer
. 你一定用过Excel,一定做过“合并单元格”的操作吧?对了,wxGridBagSizer
就是合并单元格之后的wxGridSizer
。 GridBagSizer介绍,GridBadSizer教程
在下面采用了 GridBagSizer 的例子中,”pos” 参数控制组件放置的坐标位置,(0, 0) 意味着组件紧贴在左上角,而 (3, 5) 则意味着组件要再向下 3 行,再向右 5 列。”span” 就是合并单元格的参数:
# -*- coding: utf-8 -*-
"""
http://blog.csdn.net/chenghit
"""
import wx
class ExamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# 创建一些Sizer
mainSizer = wx.BoxSizer(wx.VERTICAL)
grid = wx.GridBagSizer(hgap=5, vgap=5) # 行和列的间距是5像素
hSizer = wx.BoxSizer(wx.HORIZONTAL)
self.quote = wx.StaticText(self, label='Your quote:', pos=(20, 30))
grid.Add(self.quote, pos=(0,0)) # 加入GridBagSizer
self.logger = wx.TextCtrl(self, pos=(300,20), size=(200,300), style=wx.TE_MULTILINE | wx.TE_READONLY)
self.button = wx.Button(self, label='Save', pos=(200, 325))
self.Bind(wx.EVT_BUTTON, self.OnClick, self.button)
self.lblname = wx.StaticText(self, label='Your name:', pos=(20, 60))
grid.Add(self.lblname, pos=(1,0))
self.editname = wx.TextCtrl(self, value='Enter here your name:', pos=(150, 60), size=(140, -1))
grid.Add(self.editname, pos=(1,1))
self.Bind(wx.EVT_TEXT, self.EvtText, self.editname)
self.Bind(wx.EVT_CHAR, self.EvtChar, self.editname)
# 向GridBagSizer中填充空白的空间
grid.Add((10, 40), pos=(2,0))
self.sampleList = ['friends', 'advertising', 'web search', 'Yellow Pages']
self.lblhear = wx.StaticText(self, label="How did you hear from us ?", pos=(20, 90))
grid.Add(self.lblhear, pos=(3,0))
self.edithear = wx.ComboBox(self, pos=(150, 90), size=(95, -1), choices=self.sampleList, style=wx.CB_DROPDOWN)
grid.Add(self.edithear, pos=(3,1))
self.Bind(wx.EVT_COMBOBOX, self.EvtComboBox, self.edithear)
self.Bind(wx.EVT_TEXT, self.EvtText, self.edithear)
self.insure = wx.CheckBox(self, label="Do you want Insured Shipment ?", pos=(20,180))
# 加入Sizer的同时,设置对齐方式和边距
grid.Add(self.insure, pos=(4,0), span=(1,2), flag=wx.BOTTOM, border=5)
self.Bind(wx.EVT_CHECKBOX, self.EvtCheckBox, self.insure)
radioList = ['blue', 'red', 'yellow', 'orange', 'green', 'purple', 'navy blue', 'black', 'gray']
self.rb = wx.RadioBox(self, label="What color would you like ?", pos=(20, 210), choices=radioList,
majorDimension=3, style=wx.RA_SPECIFY_COLS)
grid.Add(self.rb, pos=(5,0), span=(1,2)) # 合并了1行2列的单元格
self.Bind(wx.EVT_RADIOBOX, self.EvtRadioBox, self.rb)
hSizer.Add(grid, 0, wx.ALL, 5)
hSizer.Add(self.logger)
mainSizer.Add(hSizer, 0, wx.ALL, 5)
mainSizer.Add(self.button, 0, wx.CENTER)
# 可以把SetSizer()和sizer.Fit()合并成一条SetSizerAndFit()语句
self.SetSizerAndFit(mainSizer)
def OnClick(self, event):
self.logger.AppendText('Click on object with Id %d\n' % event.GetId())
def EvtText(self, event):
self.logger.AppendText('EvtText: %s\n' % event.GetString())
def EvtChar(self, event):
self.logger.AppendText('EvtChar: %d\n' % event.GetKeyCode())
event.Skip()
def EvtComboBox(self, event):
self.logger.AppendText('EvtComboBox: %s\n' % event.GetString())
def EvtCheckBox(self, event):
self.logger.AppendText('EvtCheckBox: %d\n' % event.Checked())
def EvtRadioBox(self, event):
self.logger.AppendText('EvtRadioBox: %d\n' % event.GetInt())
app = wx.App(False)
frame = wx.Frame(None, title="Demo with Notebook")
nb = wx.Notebook(frame)
nb.AddPage(ExamplePanel(nb), "Absolute Positioning")
nb.AddPage(ExamplePanel(nb), "Page Two")
nb.AddPage(ExamplePanel(nb), "Page Three")
frame.Show()
app.MainLoop()
原文基本上到这里就结束了,后面的 Drawing 相关的内容只是列出了标题,却没有介绍。如果要编写小游戏,这部分内容是很关键的,太遗憾了。
先发出来,附加的内容再慢慢补充。