大家好,又见面了,我是你们的朋友全栈君。
本文翻译自http://wiki.wxpython.org/Getting%20Started
首先声明:本人还是个菜鸟,翻译只是为了学习,就当作记笔记了。水平有限,错误和疏漏在所难免,希望各路高手能够给予指导。而且简单查了一下,好像中文世界目前还没有完整的翻译 Getting Started with wxPython 的。
wxPython入门
第一个应用程序:”Hello, World!”
按惯例,我们先来写一个 “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")
最后运行程序,我们可以看到类似这样的窗口:
Windows 还是 Frames?
当人们谈论GUI的时候,他们通常指的是windows,menus和icons。那么自然地,你可能会认为应该用wx.Window
来代表屏幕上的一个window。但实际上不是这样的。wx.Window
是一个基础的class,所有的可视化元素,例如buttons, menus等等,都起源于wx.Window
类。而程序窗口则是一个wx.Frame
。新手经常把这2个概念搞混,需要特别留心。
创建一个简单的记事本
现在我们来写一个简单的记事本。在这个例子中,我们会用到几个组件,来理解一些特性或功能,例如事件(events)和回调(callbacks)。
第1步
首先,我们需要创建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()
了。
添加一个菜单栏MenuBar
所有的应用程序都会有一个菜单栏,和一个状态栏。让我们来给这个记事本程序添加一个:
# -*- 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知道,在不同的平台怎样去显示这个组件,使它看起来更美观。
事件处理event handling
我们已经创建了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)
这是带wx.OK的对话框:
这是省略wx.OK的对话框:
对话Dialogs
当然,一个文本编辑器不能够没有打开或保存文档的功能——这些功能是由对话来实现的。一般对话由底层平台提供,这样你的应用程序看上去就像是一个原生程序。在本例中,对话由 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 文件):
- Drag and Drop.
- MDI
- Tab view/multiple files
- Find/Replace dialog
- Print dialog (Printing)
- Macro-commands in python ( using the eval function)
- etc …
操作窗口
主题:
- Frames
- Windows
- Controls/Widgets
- Sizers
- Validators
在这个章节,我们将会讲解 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
, 但是很复杂 - 可以使用 Delphi-like
LayoutAnchors
, 比wx.LayoutConstraints
简单些 - 使用 wxSizer 的子类,这也是本文将要讲解的。
Sizers
作为wx.Sizer
的子类,Sizer 能够被用来在 frame 或 window 中布置可见元素。它的作用包括:
- 为每个可见元素计算合适的尺寸
- 参照一定的尺度为元素定位
- 当 frame 的尺寸变化时,动态的对元素的尺寸和(或)位置做出调整
一些常见的 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 个参数(语法):
- 第 1 个参数把 1 个控件添加到 sizer 里面
- 第 2 个参数是 proportion 权重因子,代表着这个控件相对于其它控件所占有的空间比例。例如,如果你有 3 个编辑控件,你希望它们的空间比例是 3:2:1,那么在把它们加到 sizer 里面的时候,就按照这个比例数值来指定权重因子。如果权重因子为 “0”,意味着这个控件或者 sizer,在它的父 sizer 的布局方向上的尺寸,不会随着 frame 的增大(缩小)而增大(缩小)。在上面的例子中,
- 第 3 个参数 flag 通常用
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 计算它所容纳的元素的初始化尺寸和位置
菜单Menus
我们已经在上文讲解过菜单的设置和使用方法,不再累述。
验证器Validators
当你创建一个对话框或者输入控件的时候,可以使用wx.Validator
来简化控件加载数据的进程,对输入的数据进行验证,或从中摘录数据。wx.Validator
还可以被用来截取控件域内发生的一些事件,例如敲击键盘的动作。要使用验证器,你必须先定义一个wx.Validator
的子类 (既不是wx.TextValidator
也不是wx.GenericValidator
),然后再调用myInputField.SetValidator
(myValidator
) 把它关联到你的控件域。
NOTE1: 你定义的wx.Validator
子类必须覆盖wxValidator.Clone()
方法。
NOTE2: 原文并没有进一步的讲解 Validators 的设置和使用方法,不过你可以参考这个 bing.com 的网页快照
实例讲解
在 Panel 中创建第 1 个 Label
现在我们来写一个小程序,这个程序很简单,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
控件来显示其它控件产生的事件:
The notebook
有时候,一个表单(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 了,这很关键。
使用 sizer 布局元素
严格的定义每个元素的位置并不会带来理想的显示效果,因为总是有很多原因导致 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 相关的内容只是列出了标题,却没有介绍。如果要编写小游戏,这部分内容是很关键的,太遗憾了。
先发出来,附加的内容再慢慢补充。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143240.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...