合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 第七章 使用基础控件 1. [使用基本的控件工作](#A.2BT391KFf6Zyx2hGOnTvZd5U9c-) 1. [显示文本](#A.2BZj55OmWHZyw-) 2. [使用按钮工作](#A.2BT391KGMJlK5d5U9c-) 3. [输入并显示数字](#A.2Bj5NRZV52Zj55OmVwW1c-) 4. [给用户以选择](#A.2Bftl1KGI3TuWQCWLp-) 5. [本章小结](#A.2BZyx64FwPftM-) `wxPython`工具包提供了多种不同的窗口部件,包括了本章所提到的基本控件。我们涉及静态文本、可编辑的文本、按钮、微调、滑块、复选框、单选按钮、选择器、列表框、组合框和标尺。对于每种窗口部件,我们将提供一个关于如何使用它的简短例子,并附上相关的`wxPython` `API`的说明。 ## 显示文本 这一节以在屏幕上显示文本的例子作为开始,包括用作标签的静态文本域,有样式和无样式的都使用了。你可以创建用于用户输入的单行和多行文本域。另外,我们将讨论如何选择文本的字体。 ### 如何显示静态文本? 大概对于所有的`UI`工具来说,最基本的任务就是在屏幕上绘制纯文本。在`wxPython`中,使用类`wx.StaticText`来完成。图7.1显示了这个静态文本控件。 ![](https://box.kancloud.cn/2016-08-21_57b9960ae8135.gif) 在`wx.StaticText`中,你能够改变文本的对齐方式、字体和颜色。简单的静态文本控件可以包含多行文本,但是你不能处理多种字体或样式。处理多种字体或样式,要使用更精细的文本控件,如`wx.html.HTMLWindow`,它在第十六章中说明。为了在静态文本控件中显示多行文本,我们要包括其中有换行符的字符串,并使控件的大小足够显示所有的文本。有一个特点是你在图7.1中所不能看到的,那就是`wx.StaticText`窗口不会接受或响应鼠标事件。 **如何显示静态文本** 例子7.1显示了产生图7.1的代码。 **例7.1** **如何使用静态文本的一个基本例子** ``` import wx class StaticTextFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Static Text Example', size=(400, 300)) panel = wx.Panel(self, -1) # 这是一个基本的静态文本 wx.StaticText(panel, -1, "This is an example of static text", (100, 10)) # 指定了前景色和背景色的静态文本 rev = wx.StaticText(panel, -1, "Static Text With Reversed Colors", (100, 30)) rev.SetForegroundColour('white') rev.SetBackgroundColour('black') # 指定居中对齐的的静态文本 center = wx.StaticText(panel, -1, "align center", (100, 50), (160, -1), wx.ALIGN_CENTER) center.SetForegroundColour('white') center.SetBackgroundColour('black') # 指定右对齐的静态文本 right = wx.StaticText(panel, -1, "align right", (100, 70), (160, -1), wx.ALIGN_RIGHT) right.SetForegroundColour('white') right.SetBackgroundColour('black') # 指定新字体的静态文本 str = "You can also change the font." text = wx.StaticText(panel, -1, str, (20, 100)) font = wx.Font(18, wx.DECORATIVE, wx.ITALIC, wx.NORMAL) text.SetFont(font) # 显示多行文本 wx.StaticText(panel, -1, "Your text\ncan be split\n" "over multiple lines\n\neven blank ones", (20,150)) #显示对齐的多行文本 wx.StaticText(panel, -1, "Multi-line text\ncan also\n" "be right aligned\n\neven with a blank", (220,150), style=wx.ALIGN_RIGHT) if __name__ == '__main__': app = wx.PySimpleApp() frame = StaticTextFrame() frame.Show() app.MainLoop() ``` `wx.StaticText`的构造函数和基本的`wxWidget`构造函数相同,如下所示: ``` wx.StaticText(parent, id, label, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, name="staticText") ``` 表7.1说明了这些参数——大多数的`wxPython`窗口部件都有相类似的参数。对于构造函数的参数的更详细的说明,请参见第2章的相关论述。 **表7.1** **`wx.StaticText`构造函数的参数** `parent`:父窗口部件。 `id`:标识符。使用-1可以自动创建一个唯一的标识。 `label`:你想显示在静态控件中的文本。 `pos`:一个`wx.Point`或一个`Python`元组,它是窗口部件的位置。 `size`:一个`wx.Size`或一个`Python`元组,它是窗口部件的尺寸。 `style`:样式标记。 `name`:对象的名字,用于查找的需要。 接下来我们更详细地讨论样式标记。 **使用样式工作** 所有在例7.1中静态文本实例所调用的方法都是属于基父类`wx.Window`的;`wx.StaticText`没有定义任何它自己的新方法。表7.2列出了一些专用于`wx.StaticText`的样式。 **表7.2** `wx.ALIGN_CENTER`:静态文本位于静态文本控件的中心。 `wx.ALIGN_LEFT`:文本在窗口部件中左对齐。这是默认的样式。 `wx.ALIGN_RIGHT`:文本在窗口部件中右对齐。 `wx.ST_NO_AUTORESIZE`:如果使用了这个样式,那么在使用了`SetLabel()`改变文本之后,静态文本控件不将自我调整尺寸。你应结合使用一个居中或右对齐的控件来保持对齐。 `wx.StaticText`控件覆盖了`SetLabel()`,以便根据新的文本来调整自身,除非`wx.ST_NO_AUTORESIZE`样式被设置了。 当创建了一个居中或右对齐的单行静态文本时,你应该显式地在构造器中设置控件的尺寸。指定尺寸以防止`wxPython`自动调整该控件的尺寸。`wxPython`的默认尺寸是刚好包容了文本的矩形尺寸,因此对齐就没有什么必要。要在程序中动态地改变窗口部件中的文本,而不改变该窗口部件的尺寸,就要设置`wx.ST_NO_AUTORESIZE`样式。这样就防止了在文本被重置后,窗口部件自动调整尺寸到刚好包容了文本。如果静态文本是位于一个动态的布局中,那么改变它的尺寸可能导致屏幕上其它的窗口部件移动,这就对用户产生了干扰。 **其它显示文本的技术** 还有其它的方法来显示文本。其中之一就是`wx.lib.stattext.GenStaticText`类,它是`wx.StaticText`的纯`Python`实现。它比标准C++版的跨平台性更好,并且它接受鼠标事件。当你想子类化或创建你自己的静态文本控件时,它是更可取的。 你可以使用`DrawText(text`, x,`y)`和`DrawRotatedText(text`, x, y, `angle)`方法直接绘制文本到你的设备上下文。后者是显示有一定角度的文本的最容易的方法,尽管`GenStaticText`的子类也能处理旋转问题。设备上下文在第6章中做了简短的说明,我们将在第12章中对它做更详细的说明。 ### 如何让用户输入文本? 超越纯粹显示静态文本,我们将开始讨论当输入文本时的用户交互。`wxPython`的文本域窗口部件的类是`wx.TextCtrl`,它允许单行和多行文本输入。它也可以作为密码输入控件,掩饰所按下的按键。如果平台支持的话,`wx.TextCtrl`也提供丰富格式文本的显示,通过使用所定义和显示的多文本样式。图7.2显示了一个作为单行控件的`wx.TextCtrl`的样板。其中的密码输入框对密码进行了掩饰。 ![](https://box.kancloud.cn/2016-08-21_57b9960b085f4.gif) 接下来,我们将演示如何创建文本,然后讨论文本控件的样式选项。 **如何创建文本输入控件** 例子7.2显示了用于生成图7.2的代码 **例7.2** **`wx.TextCtrl`的单行例子** ``` import wx class TextFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Text Entry Example', size=(300, 100)) panel = wx.Panel(self, -1) basicLabel = wx.StaticText(panel, -1, "Basic Control:") basicText = wx.TextCtrl(panel, -1, "I've entered some text!", size=(175, -1)) basicText.SetInsertionPoint(0) pwdLabel = wx.StaticText(panel, -1, "Password:") pwdText = wx.TextCtrl(panel, -1, "password", size=(175, -1), style=wx.TE_PASSWORD) sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6) sizer.AddMany([basicLabel, basicText, pwdLabel, pwdText]) panel.SetSizer(sizer) if __name__ == '__main__': app = wx.PySimpleApp() frame = TextFrame() frame.Show() app.MainLoop() ``` `wx.TextCtrl`类的构造函数较小且比其父类`wx.Window`更精细,它增加了两个参数: `wx.TextCtrl(parent`, `id`, `value` = "", `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `style`=0, `validator`=`wx.DefaultValidator` `name`=`wx.TextCtrlNameStr)` 参数`parent`, `id`, `pos`, `size`, `style`, 和 `name`与`wx.Window`构造函数的相同。`value`是显示在该控件中的初始文本。 `validator`参数用于一个`wx.Validator`。`validator`通常用于过虑数据以确保只能键入要接受的数据。将在第9章对`validator`做更详细的讨论。 **使用单行文本控件样式** 这里,我们将讨论一些唯一无二的文本控件样式。 表7.3说明了用于单行文本控件的样式标记 **表7.3** **单行`wx.TextCtrl`的样式** `wx.TE_CENTER`:控件中的文本居中。 `wx.TE_LEFT`:控件中的文本左对齐。默认行为。 `wx.TE_NOHIDESEL`:文本始终高亮显示,只适用于`Windows`。 `wx.TE_PASSWORD`:不显示所键入的文本,代替以星号显示。 `wx.TE_PROCESS_ENTER`:如果使用了这个样式,那么当用户在控件内按下回车键时,一个文本输入事件被触发。否则,按键事件内在的由该文本控件或该对话框管理。 `wx.TE_PROCESS_TAB`:如果指定了这个样式,那么通常的字符事件在`Tab`键按下时创建(一般意味一个制表符将被插入文本)。否则,`tab`由对话框来管理,通常是控件间的切换。 `wx.TE_READONLY`:文本控件为只读,用户不能修改其中的文本。 `wx.TE_RIGHT`:控件中的文本右对齐。 像其它样式标记一样,它们可以使用|符号来组合使用,尽管其中的三个对齐标记是相互排斥的。 对于添加文本和移动插入点,该文本控件自动管理用户的按键和鼠标事件。对于该文本控件可用的命令控制组合说明如下: * `ctrl`-x :剪切 `ctrl`-c :复制 `ctrl`-v :粘贴 `ctrl`-z :撤消 ### 不输入的情况下如何改变文本? 除了根据用户的输入改变显示的文本外,`wx.TextCtrl`提供了在程序中改变显示的文本的一些方法。你可以完全改变文本或仅移动插入点到文本中不同的位置。表7.4列出了`wx.TextCtrl`的文本处理方法。 **表7.4** `AppendText(text)`:在尾部添加文本。 `Clear()`:重置控件中的文本为“”。并且生成一个文本更新事件。 `EmulateKeyPress(event)`:产生一个按键事件,插入与事件相关联的控制符,就如同实际的按键发生了。 `GetInsertionPoint()` `SetInsertionPoint(pos)` `SetInsertionPointEnd()`:得到或设置插入点的位置,位置是整型的索引值。控件的开始位置是0。 `GetRange(from`, `to)`:返回控件中位置索引范围内的字符串。 `GetSelection()` `GetStringSelection()` `SetSelection(from`, `to)`:`GetSelection()`以元组的形式返回当前所选择的文本的起始位置的索引值(开始,结束)。`GetStringSelection()`得到所选择的字符串。`SetSelection(from`, `to)`设置选择的文本。 `GetValue()` `SetValue(value)`:`SetValue()`改变控件中的全部文本。`GetValue()`返回控件中所有的字符串。 `Remove(from`, `to)`:删除指定范围的文本。 `Replace(from`, `to`, `value)`:用给定的值替换掉指定范围内的文本。这可以改变文本的长度。 `WriteText(text)`:类似于`AppendText()`,只是写入的文本被放置在当前的插入点。 当你的控件是只读的或如果你根据事件而非用户键盘输入来改变控件中的文本是,这些方法是十分有用的。 ### 如何创建一个多行或样式文本控件? 你可以使用`wx.TE_MULTILINE`样式标记创建一个多行文本控件。如果本地窗口控件支持样式,那么你可以改变被控件管理的文本的字体和颜色样式,这有时被称为丰富格式文本。对于另外的一些平台,设置样式的调用被忽视掉了。图7.3显示了多行文本控件的一个例子。 ![](https://box.kancloud.cn/2016-08-21_57b9960b1e38e.gif) 例7.3包含了用于创建图7.3的代码。通常,创建一个多行文本控件是通过设置`wx.TE_MULTILINE`样式标记来处理的。较后的部分,我们将讨论使用丰富文本样式。 **例7.3** **创建一个多行文本控件** ``` import wx class TextFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Text Entry Example', size=(300, 250)) panel = wx.Panel(self, -1) multiLabel = wx.StaticText(panel, -1, "Multi-line") multiText = wx.TextCtrl(panel, -1, "Here is a looooooooooooooong line of text set in the control.\n\n" "See that it wrapped, and that this line is after a blank", size=(200, 100), style=wx.TE_MULTILINE) #创建一个文本控件 multiText.SetInsertionPoint(0) #设置插入点 richLabel = wx.StaticText(panel, -1, "Rich Text") richText = wx.TextCtrl(panel, -1, "If supported by the native control, this is reversed, and this is a different font.", size=(200, 100), style=wx.TE_MULTILINE|wx.TE_RICH2) #创建丰富文本控件 richText.SetInsertionPoint(0) richText.SetStyle(44, 52, wx.TextAttr("white", "black")) #设置文本样式 points = richText.GetFont().GetPointSize() f = wx.Font(points + 3, wx.ROMAN, wx.ITALIC, wx.BOLD, True) #创建一个字体 richText.SetStyle(68, 82, wx.TextAttr("blue", wx.NullColour, f)) #用新字体设置样式 sizer = wx.FlexGridSizer(cols=2, hgap=6, vgap=6) sizer.AddMany([multiLabel, multiText, richLabel, richText]) panel.SetSizer(sizer) if __name__ == '__main__': app = wx.PySimpleApp() frame = TextFrame() frame.Show() app.MainLoop() ``` **使用多行或丰富文本样式** 除了`wx.TE_MULTILINE`,还有另外的样式标记,它们只在一个多行或丰富文本控件的上下文中有意义。表7.5列出了这些窗口样式。 **表7.5** `wx.HSCROLL`:如果文本控件是多行的,并且如果该样式被声明了,那么长的行将不会自动换行,并显示水平滚动条。该选项在`GTK`+中被忽略。 `wx.TE_AUTO_URL`:如果丰富文本选项被设置并且平台支持的话,那么当用户的鼠标位于文本中的一个`URL`上或在该`URL`上敲击时,这个样式将导致一个事件被生成。 `wx.TE_DONTWRAP`:`wx.HSCROLL`的别名。 `wx.TE_LINEWRAP`:对于太长的行,以字符为界换行。某些操作系统可能会忽略该样式。 `wx.TE_MULTILINE`:文本控件将显示多行。 `wx.TE_RICH`:用于`Windows`下,丰富文本控件用作基本的窗口部件。这允许样式文本的使用。 `wx.TE_RICH2`:用于`Windows`下,把最新版本的丰富文本控件用作基本的窗口部件。 `wx.TE_WORDWRAP`:对于太长的行,以单词为界换行。许多操作系统会忽略该样式。 记住,上面这些样式可以组合使用,所以上面例子中的多行丰富文本控件使用`wx.TE_MULTILINE` | `wx.TE_RICH2`来声明。 用在`wx.TextCtrl`窗口部件中的文本样式是类`wx.TextAttr`的实例。`wx.TextAttr`实例的属性有文本颜色、背景色、和字体,它们都能够在构造函数中被指定,如下所示: `wx.TextAttr(colText`, `colBack`=`wx.NullColor`, `font`=`wx.NullFont)` 文本色和背景色是`wxPython`对象,它们可以使用颜色名或颜色的`RGB`值(红, 绿, 蓝)来指定。`wx.NullColor`指明使用控件目前的背景色。`font`是一个`wx.Font`对象,我们将在下一小节讨论。`wx.NullFont`对象指明使用当前默认字体。 类`wx.TextAttr`有相关属性的`get`*()方法:`GetBackgroundColour()`, `GetFont()`, 和 `GetTextColour()`,也有返回布尔值的验证存在性的方法:`HasBackgroundColour()`, `HasFont()`, 和 `HasTextColour()`。如果属性包含一个默认值,则`Has`*()方法返回`False`。如果所有这三个属性都包含默认值,则`IsDefault()`方法返回`true`。这个类没有`set`*()方法,因为`wx.TextAttr`的实例是不可变的。要改变文本的样式,你必须创建一个实例。 使用文本样式,要调用`SetDefaultStyle(style)`或`SetStyle(start`, `end`,`style)`。第一个方法设置为控件当前的样式。任何插入到该控件中的文本,不管是键入的,或使用了`AppendText()` 或 `WriteText()`方法的,都以该样式显示。如果样式的某个属性是默认的,那么该样式的当前值被保留。但是,如果样式的所有属性都是默认的,那么恢复默认样式。`SetStyle()`与`SetDefaultStyle(style)`类似,只是立即对位于`start` 和 `end`位置之间的文本起作用。样式参数中的默认属性通过检查该控件的当前默认样式来解决。例7.3使用下面一行代码来反转文本中几个字符的颜色: `richText.SetStyle(44`, 52, `wx.TextAttr(`"`white`", "`black`")) 背景色变为了黑色,相应的字符变为了白色。 表7.6列出了`wx.TextCtrl`的方法,它们在处理多行控件和丰富文本中是有用的。 **表7.6** `GetDefaultStyle()` `SetDefaultStyle(style)`:上面已作了说明。 `GetLineLength(lineNo)`:返回给定行的长度的整数值。 `GetLineText(lineNo)`:返回给定行的文本。 `GetNumberOfLines()`:返回控件中的行的数量。对于单行,返回1。 `IsMultiLine()` `IsSingleLine()`:布尔类型的方法,确定控件的状态。 `PositionToXY(pos)`:指定文本内的一个整数值位置,返回以元组(列,行)形式的索引位置。列和行的索引值均以0作为开始。 `SetStyle(start`, `end`,`style)`:立即改变指定范围内文本的样式。 `ShowPosition(pos)`:引起一个多行控件的滚动,以便观察到指定位置的内容。 `XYToPosition(x`, `y)`:与`PositionToXY(pos)`相反——指定行和列,返回整数值位置。 如果你能在系统中使用任意字体的话,那么就可以更加灵活的创建样式。 接下来,我们将给你展示如何创建和使用字体实例。 ### 如何创建一个字体? 字体是类`wx.Font`的实例。你所访问的任何字体,它已经被安装并对于基本的系统是可访问的。创建一个字体实例,要使用如下的构造函数: `wx.Font(pointSize`, `family`, `style`, `weight`, `underline`=`False`, `faceName`="", `encoding`=`wx.FONTENCODING_DEFAULT)` `pointSize`是字体的以磅为单位的整数尺寸。`family`用于快速指定一个字体而无需知道该字体的实际的名字。字体的准确选择依赖于系统和具体可用的字体。可用的字体类别的示例显示在表7.7中。你所得到的精确的字体将依赖于你的系统。 **表7.7** `wx.DECORATIVE`:一个正式的,老的英文样式字体。 `wx.DEFAULT`:系统默认字体。 `wx.MODERN`:一个单间隔(固定字符间距)字体。 `wx.ROMAN`:`serif`字体,通常类似于`Times` `New` `Roman`。 `wx.SCRIPT`:手写体或草写体 `wx.SWISS`:`sans`-`serif`字体,通常类似于`Helvetica`或`Arial`。 `style`参数指明字体的是否倾斜,它的值有:`wx.NORMAL`, `wx.SLANT`, 和 `wx.ITALIC`。同样,`weight`参数指明字体的醒目程度,可选值有:`wx.NORMAL`, `wx.LIGHT`,或`wx.BOLD`。这些常量值的行为根据它的名字就可以知道了。`underline`参数仅工作在`Windows`系统下,如果取值为`True`,则加下划线,`False`为无下划线。 `faceName`参数指定字体名。 `encoding`参数允许你在几个编码中选择一个,它映射内部的字符和字本显示字符。编码不是`Unicode`编码,只是用于`wxPython`的不同的8位编码。大多数情况你可以使用默认编码。 为了获取系统的有效字体的一个列表,并使用户可用它们,要使用专门的类`wx.FontEnumerator`,如下所示: e = `wx.FontEnumerator()` `e.EnumerateFacenames()` `fontList` = `e.GetFacenames()` 要限制该列表为固定宽度,就要将上面的第一行改为e = `wx.FontEnumerator(fixedWidth`=`True)`。 ### 如果我们系统不支持丰富文本,那么我还能使用样式文本吗? 可以。在`wxPython`中有一个跨平台的样式文本窗口部件,名为`wx.stc.StyledTextCtrl`,它是`Python`对`Scintilla`丰富文本组件的封装。因为`Scintilla`不是`wxWidgets`的一部分,而是作为一个独立的第三方组被合并到了`wxPython`中,所以它不与我们已经讨论过的类共享相同的`API`。`wx.stc.StyledCtrl`的完整说明超过了我们要讲的范围,但是你可以在`http:`//`wiki.wxpython.org`/`index.cgi`/`wxStyledTextCtrl`找到相关的文档。 ### 如果我的文本控件不匹配我的字符串该怎么办? 当使用多行`wx.TextCtrl`的时候,要知道的一点是,该文本控件是以何种方式存储字符串的。在内部,存储在该`wx.TextCtrl`中的多行字符是以\n作为行的分隔符的。这与基本的操作系统无关,即使某些系统使用了不同的字符组合作为一行的分隔符。当你使用`GetValue()`来获取该字符串时,原来的行分隔符被还原,因此你不必考虑手工转换。这个的好处就是控件中的文本不依赖于任何特定的操作系统。 缺点是,文本控件中的行的长度和行的索引与它们在文本控件外的可能是不同的。例如,如果你在一个`Windows`系统上,系统所用的行分隔符是\r\n,通过`GetValue()`所得知的字符串的长度将比通过`GetLastPosition()`所得知的字符串的结尾长。通过在例7.3中增加下面两行: `print` "`getValue`", `len(multiText.GetValue())` `print` "`lastPos`", `multiText.GetLastPosition()` 我们在`Unix`系统上所得的结果应该是: `getValue` 119 `lastPos` 119 我们在`Windows`系统上所得的结果应该是: `getValue` 121 `lastPos` 119 这意味你不应该使用多行文本控件的位置索引来取得原字符串,位置索引应该用作`wx.TextCtrl`的另外方法的参数。对于该控件中的文本的子串,应该使用`GetRange()`或`GetSelectedText()`。也不要反向索引;不要使用原字符串的索引来取得并放入文本控件中。下面是一个例子,它使用了不正确的方法在插入点之后直接得到10个字符: ``` aLongString = """Any old multi line string will do here. Just as long as it is multiline""" text = wx.TextCtrl(panel, -1, aLongString, style=wx.TE_MULTILINE) x = text.GetInsertionPoint() selection = aLongString[x : x + 10] ### 这将是不正确的 ``` 在`Windows`或`Mac`系统中要得到正确的结果,最后一行应换为: `selection` = `text.GetRange(x`, x + 10) ### 如何响应文本事件? 有一个由`wx.TextCtrl`窗口部件产生的便利的命令事件,你可能想用它。你需要把相关事件传递给`Bind`方法以捕获该事件,如下所示: `frame.Bind(wx.EVT_TEXT`, `frame.OnText`, `text)` 表7.8说明了这些命令事件。 **表7.8** **`wx.TextCtrl`的事件** `EVT_TEXT`:当控件中的文本改变时产生该事件。文本因用户的输入或在程序中使用`SetValue()`而被改变,都要产生该事件。 `EVT_TEXT_ENTER`:当用户在一个`wx.TE_PROCESS_ENTER`样式的文本控件中按下了回车键时,产生该事件。 `EVT_TEXT_URL`:如果在`Windows`系统上,`wx.TE_RICH`或`wx.TE_RICH2`样式被设置了,并且`wx.TE_AUTO_URL`样式也被设置了,那么当在文本控件内的`URL`上发生了一个鼠标事件时,该事件被触发。 `EVT_TEXT_MAXLEN`:如果使用`SetMaxLength()`指定了该控件的最大长度,那么当用户试图输入更长的字符串时,该事件被触发。你可能会用这个,例如,这时给用户显示一个警告消息。 接下来,让我们来讨论被主要设计来得到鼠标输入的控件。其中最简单的就是按钮。 ## 使用按钮工作 在`wxPython`中有很多不同类型的按钮。这一节,我们将讨论文本按钮、位图按钮、开关按钮(`toggle` `buttons`)和通用(`generic`)按钮。 ### 如何生成一个按钮? 在第一部分(`part` 1)中,我们已经说明了几个按钮的例子,所以这里我们只简短的涉及它的一些基本的东西。图7.4显示了一个简单的按钮。 **图7.4** ![](https://box.kancloud.cn/2016-08-21_57b9960b34b42.gif) 使用按钮是非常简单的。例7.4显示了该简单按钮的代码。 **例7.4** **创建并显示一个简单的按钮** ``` import wx class ButtonFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Button Example', size=(300, 100)) panel = wx.Panel(self, -1) self.button = wx.Button(panel, -1, "Hello", pos=(50, 20)) self.Bind(wx.EVT_BUTTON, self.OnClick, self.button) self.button.SetDefault() def OnClick(self, event): self.button.SetLabel("Clicked") if __name__ == '__main__': app = wx.PySimpleApp() frame = ButtonFrame() frame.Show() app.MainLoop() ``` `wx.Button`的构造函数类似于我们已经看到过的,如下所示: `wx.Button(parent`, `id`, `label`, `pos`, `size`=`wxDefaultSize`, `style`=0, `validator`, `name`="`button`") 参数`label`是显示在按钮上的文本。它可以在程序运行期间使用`SetLabel()`来改变,并且使用`GetLabel()`来获取。另外两个有用的方法是`GetDefaultSize()`和`SetDefault()`。`GetDefaultSize()`返回系统默认按钮的尺寸(对于框架间的一致性是有用的);`SetDefault()`设置按钮为对话框或框架的默认按钮。默认按钮的绘制不同于其它按钮,它在对话框获得焦点时,通常按下回车键被激活。 `wx.Button`类有一个跨平台的样式标记:`wx.BU_EXACTFIT`。如果定义了这个标记,那么按钮就不把系统默认的尺寸作为最小的尺寸,而是把能够恰好填充标签的尺寸作为最小尺寸。如果本地窗口部件支持的话,你可以使用标记`wx.BU_LEFT`, `wx.BU_RIGHT`, `wx.BU_TOP`, 和 `wx.BU_BOTTOM`来改变按钮中标签的对齐方式。每个标记对齐标签到边,该边你根据标记的名字可以知道。正如我们在第一部分中所讨论过的,`wx.Button`在被敲击时触发一个命令事件,事件类型是`EVT_BUTTON`。 ### 如何生成一个位图按钮? 有时候,你可能想在你的按钮上显示一个图片,而非一个文本标签,如图7.5所示。 ![](https://box.kancloud.cn/2016-08-21_57b9960b4b0f6.gif) 在`wxPython`中,使用类`wx.BitmapButton`来创建一个位图按钮。处理一个`wx.BitmapButton`的代码是与通用按钮的代码非常类似的,例7.5显示了产生7.5的代码。 **例7.5** **创建一个位图按钮** ``` import wx class BitmapButtonFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Bitmap Button Example', size=(200, 150)) panel = wx.Panel(self, -1) bmp = wx.Image("bitmap.bmp", wx.BITMAP_TYPE_BMP).ConvertToBitmap() self.button = wx.BitmapButton(panel, -1, bmp, pos=(10, 20)) self.Bind(wx.EVT_BUTTON, self.OnClick, self.button) self.button.SetDefault() self.button2 = wx.BitmapButton(panel, -1, bmp, pos=(100, 20), style=0) self.Bind(wx.EVT_BUTTON, self.OnClick, self.button2) def OnClick(self, event): self.Destroy() if __name__ == '__main__': app = wx.PySimpleApp() frame = BitmapButtonFrame() frame.Show() app.MainLoop() ``` 与普通按钮的主要的区别是你需要提供一个位图,而非一个标签。否则,构造器和大部分代码是与文本按钮的例子相同的。位图按钮在被敲击时同样产生`EVT_BUTTON`事件。 关于位图按钮有几个有趣的特性。首先,一个样式标记`wx.BU_AUTODRAW`,它是默认的。如果该标记是打开的,那么位图将带有一个3D的边框,这使它看起来像一个文本按钮(图7.5中的左按钮),并且按钮比原位图大几个像素。如果该标记是关闭的,则位图被简单地绘制为按钮而没有边框。通过设置`style`=0使图7.5中右边的按钮关闭默认设置,它没有了3D的效果。 默认情况下,给`wxPython`传递单个位图作为主显示的位图,在当按钮被按下或获得焦点或无效时,`wxPython`自动创建一个标准的派生自主显示的位图的位图作为此时显示在按钮上的位图。如果自动创建的位图不是你想要的,你可以使用下面的方法: `SetBitmapDisabled()`, `SetBitmapFocus()`,`SetBitmapLabel()`, 和`SetBitmap`-`Selected()`显式地告诉`wxPython`你要使用哪个位图。这些方法都要求一个`wx.Bitmap`对象作为参数,并且它们都有相应的`get`*()方法。 你不能通过使用标准的`wxWidgets` C++库来合并一个位图和文本。你可以创建一个包含文本的位图。然而,正如我们将在通用按钮问题讨论中所看到的,`wxPython`有额外的方法来实现这一合并行为。 === 如何创建开关按钮(`toggle` `button`)?=== 你可以使用`wx.ToggleButton`创建一个开关按钮(`toggle` `button`)。开关按钮(`toggle` `button`)看起来十分像文本按钮,但它的行为更像复选框,它的选择或非选择状态是可视化的。换句话说,当你按下一个开关按钮(`toggle` `button`)时,它将一直保持被按下的状态直到你再次敲击它。 在`wx.ToggleButton`与父类`wx.Button`之间只有丙个区别: 1、当被敲击时,`wx.ToggleButton`发送一个`EVT_TOGGLEBUTTON`事件。 2、`wx.ToggleButton`有`GetValue()`和`SetValue()`方法,它们处理按钮的二进制状态。 开关按钮(`toggle` `button`)是有用的,它相对于复选框是另一好的选择,特别是在工具栏中。记住,你不能使用`wxWidgets`提供的对象来将开关按钮(`toggle` `button`)与位图按钮合并,但是`wxPython`有一个通用按钮类,它提供了这种行为,我们将在下一节对其作讨论。 ### 什么是通用按钮,我为什么要使用它? 通用按钮是一个完全用`Python`重新实现的一个按钮窗口部件,回避了本地系统窗口部件的用法。它的父类是`wx.lib.buttons.` `GenButton`。通用按钮有通用位图和切换按钮。 这儿有几个使用通用按钮的原因: 1、通用按钮比本地按钮具有更好的跨平台的外观。另一方面,通用按钮可能在具体的系统上看起来与本地按钮有些微的不同。 2、使用通用按钮,你对它的外观有更多的控制权,并且能改变属性,如3D斜面的宽度和颜色,而这对于本地控件可能是不允许的。 3、通用按钮类允许特性的合并,而`wxWidget`按钮不行。比如`GenBitmapTextButton`允许文本标签和位图的组合,`GenBitmapToggleButton`实现一个位图切换按钮。 4、如果你正在创建一个按钮类,使用通用按钮是较容易的。由于其代码和参数是用`Python`写的,所以当创建一个新的子类的时候,对于检查和覆盖,它们的可用性更好。 图7.6显示了实际的通用按钮和常规按钮的对照。 **图7.6** ![](https://box.kancloud.cn/2016-08-21_57b9960b5d926.gif) 例7.6显示了产生图7.6的代码。第二个导入语句:`import` `wx.lib.buttons` `as` `buttons`,是必须的,它使得通用按钮类可用。 **例7.6** **创建和使用`wxPython`的通用按钮** ``` import wx import wx.lib.buttons as buttons class GenericButtonFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Generic Button Example', size=(500, 350)) panel = wx.Panel(self, -1) sizer = wx.FlexGridSizer(1, 3, 20, 20) b = wx.Button(panel, -1, "A wx.Button") b.SetDefault() sizer.Add(b) b = wx.Button(panel, -1, "non-default wx.Button") sizer.Add(b) sizer.Add((10,10)) b = buttons.GenButton(panel, -1, 'Genric Button')#基本的通用按钮 sizer.Add(b) b = buttons.GenButton(panel, -1, 'disabled Generic')#无效的通用按钮 b.Enable(False) sizer.Add(b) b = buttons.GenButton(panel, -1, 'bigger')#自定义尺寸和颜色的按钮 b.SetFont(wx.Font(20, wx.SWISS, wx.NORMAL, wx.BOLD, False)) b.SetBezelWidth(5) b.SetBackgroundColour("Navy") b.SetForegroundColour("white") b.SetToolTipString("This is a BIG button...") sizer.Add(b) bmp = wx.Image("bitmap.bmp", wx.BITMAP_TYPE_BMP).ConvertToBitmap() b = buttons.GenBitmapButton(panel, -1, bmp)#通用位图按钮 sizer.Add(b) b = buttons.GenBitmapToggleButton(panel, -1, bmp)#通用位图开关按钮 sizer.Add(b) b = buttons.GenBitmapTextButton(panel, -1, bmp, "Bitmapped Text", size=(175, 75))#位图文本按钮 b.SetUseFocusIndicator(False) sizer.Add(b) b = buttons.GenToggleButton(panel, -1, "Toggle Button")#通用开关按钮 sizer.Add(b) panel.SetSizer(sizer) if __name__ == '__main__': app = wx.PySimpleApp() frame = GenericButtonFrame() frame.Show() app.MainLoop() ``` 在例7.6中,通用按钮的用法非常类似于常规按钮。通用按钮产生与常规按钮同样的`EVT_BUTTON` 和 `EVT_TOGGLEBUTTON`事件。通用按钮引入了`GetBevelWidth()`和`SetBevelWidth()`方法来改变3D斜面效果。它们用在了图7.6中大按钮上。 通用位图按钮类`GenBitmapButton`工作的像标准的`wxPython`版本。在构造器中。`GenBitmapTextButton`要求先要一个位图,然后是文本。通用类`GenToggleButton`,`GenBitmapToggleButton`,和 `GenBitmapTextToggleButton`与非开关版的一样,并且对于处理按钮的开关状态响应于`GetToggle()` 和 `SetToggle()`。 在下一节,我们将讨论关于使你的用户能够输入或观看一个数字值的方案。 ## 输入并显示数字 有时你想要显示图形化的数字信息,或你想让用户不必使用键盘来输入一个数字量。在这一节,我们将浏览`wxPython`中用于数字输入和显示的工具:滑块(`slider`)、微调控制框和显示量度的标尺。 ### 如何生成一个滑块? 滑块是一个窗口部件,它允许用户通过在该控件的尺度内拖动指示器来选择一个数值。在`wxPython`中,该控件类是`wx.Slider`,它包括了滑块的当前值的只读文本的显示。图7.7显示了水平和垂直滑块的例子。 **图7.7** ![](https://box.kancloud.cn/2016-08-21_57b9960b7dd80.gif) 滑块的基本使用是十分简单的,但是你可以增加许多事件。 **如何使用滑块** 例7.7是产生图7.7的例子。 **例7.7** **水平和垂直滑块的显示代码** ``` import wx class SliderFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Slider Example', size=(300, 350)) panel = wx.Panel(self, -1) self.count = 0 slider = wx.Slider(panel, 100, 25, 1, 100, pos=(10, 10), size=(250, -1), style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) slider.SetTickFreq(5, 1) slider = wx.Slider(panel, 100, 25, 1, 100, pos=(125, 70), size=(-1, 250), style=wx.SL_VERTICAL | wx.SL_AUTOTICKS | wx.SL_LABELS ) slider.SetTickFreq(20, 1) if __name__ == '__main__': app = wx.PySimpleApp() frame = SliderFrame() frame.Show() app.MainLoop() ``` 通常,当你使用`wx.Slider`类时,所有你所需要的就是一个构造函数,它与别的调用不同,如下所示: `wx.Slider(parent`, `id`, `value`, `minValue`, `maxValue`, `pos`=`wxDefaultPosition`, `size`=`wx.DefaultSize`, `style`=`wx.SL_HORIZONTAL`, `validator`=`wx.DefaultValidator`, `name`="`slider`") `value`是滑块的初始值,而`minValue`和`maxValue`是两端的值。 **使用滑块样式工作** 滑块的样式管理滑块的位置和方向,如下表7.9所示。 **表7.9** **`wx.Slider`的样式** `wx.SL_AUTOTICKS`:如果设置这个样式,则滑块将显示刻度。刻度间的间隔通过`SetTickFreq`方法来控制。 `wx.SL_HORIZONTAL`:水平滑块。这是默认值。 `wx.SL_LABELS`:如果设置这个样式,那么滑块将显示两头的值和滑块的当前只读值。有些平台可能不会显示当前值。 `wx.SL_LEFT`:用于垂直滑块,刻度位于滑块的左边。 `wx.SL_RIGHT`:用于垂直滑块,刻度位于滑块的右边。 `wx.SL_TOP`:用于水平滑块,刻度位于滑块的上部。 `wx.SL_VERTICAL`:垂直滑块。 如果你想通过改变滑块中的值来影响你的应用程序中的其它的部分,那么这儿有几个你可使用的事件。这些事件与窗口滚动条所发出的是相同的,详细的说明参见第8章的滚动条部分。 表7.10列出了你可用于滑块的`Set`*()方法。每个`Set`*()方法都有一个对应的`Get`方法——`Get`方法的描述参考其对应的`Set`*()方法。 **表7.10** `GetRange()` `SetRange(minValue`, `maxValue)`:设置滑块的两端值。 `GetTickFreq()` `SetTickFreq(n`, `pos)`:使用参数n设置刻度的间隔。参数`pos`没有被使用,但是它仍然是必要的,将它设置为1。 `GetLineSize()` `SetLineSize(lineSize)`:设置你每按一下方向键,滑块所增加或减少的值。 `GetPageSize()` `SetPageSize(pageSize)`:设置你每按一下`PgUp`或`PgDn`键,滑块所增加或减少的值。 `GetValue()` `SetValue(value)`:设置滑块的值。 尽管滑块提供了一个可能范围内的值的快速的可视化的表示,但是它们也有两个缺点。其一是它们占据了许多的空间,另外就是使用鼠标精确地设置滑块是困难的。下面我们将讨论的微调控制器解决了上面的这两个问题。 ### 如何得到那些灵巧的上下箭头按钮? 微调控制器是文本控件和一对箭头按钮的组合,它用于调整数字值,并且在你要求一个最小限度的屏幕空间的时候,它是替代滑块的最好选择。图7.8显示了`wxPython`的微调控制器控件。 图7.8 ![](https://box.kancloud.cn/2016-08-21_57b9960b90a12.gif) 在`wxPython`中,类`wx.SpinCtrl`管理微调按钮和相应的文本显示。在接下来的部分,我们将创建一个微调控制器。 **如何创建一个微调控制器** 要使用`wx.SpinCtrl`来改变值,可通过按箭头按钮或通过在文本控件中输入。键入的非数字的文本将被忽略,尽管控件显示的是键入的非数字的文本。一个超出范围的值将被认作是相应的最大或最小值,尽管显示的是你输入的值。例7.8显示了`wx.SpinCtrl`的用法。 **例7.8** **使用`wx.SpinCtrl`** ``` import wx class SpinnerFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Spinner Example', size=(100, 100)) panel = wx.Panel(self, -1) sc = wx.SpinCtrl(panel, -1, "", (30, 20), (80, -1)) sc.SetRange(1,100) sc.SetValue(5) if __name__ == '__main__': app = wx.PySimpleApp() SpinnerFrame().Show() app.MainLoop() ``` 几乎微调控件所有复杂的东西都是在其构造函数中,其构造函数如下: `wx.SpinCtrl(parent`, `id`=-1, `value`=`wx.EmptyString`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `style`=`wx.SP_ARROW_KEYS`, `min`=0, `max`=100, `initial`=0, `name`="`wxSpinCtrl`") 参数`value`是虚设的。使用`initial`参数来设置该控件的值,并使用`min`和`max`来设置该控件的范围。 对于`wx.SpinCtrl`有两个样式标记。默认样式是`wx.SP_ARROW_KEYS`,它允许用户通过键盘上的上下箭头键来改变控件的值。样式`wx.SP_WRAP`使得控件中的值可以循环改变,也就是说你通过箭头按钮改变控件中的值到最大或最小值时,如果再继续,值将变为最小或最大,从一个极端到另一个极端。你也可以捕获`EVT_SPINCTRL`事件,它在当控件的值改变时产生(即使改变是直接由文本输入引起的)。如果文本改变了,将引发一个`EVT_TEXT`事件,就如同你使用一个单独的文本控件时一样。 如例7.8所示,你可以使用`SetRange(minVal`, `maxVal)` 和 `SetValue(value)`方法来设置范围和值。`SetValue()`函数要求一个字符串或一个整数。要得到值,使用方法:`GetValue()`(它返回一个整数), `GetMin()`, 和 `GetMax()`。 当你需要对微调控制器的行为有更多的控制时,如允许浮点数或一个字符串的列表,你可以把一个`wx.SpinButton`和一个`wx.TextCtrl`放到一起,并在它们之间建立一个联系。然后捕获来自`wx.SpinButton`的事件,并更新`wx.TextCtrl`中的值。 ### 如何生成一个进度条? 如果你只想图形化地显示一个数字值而不允许用户改变它,那么使用相应的`wxPython`窗口部件`wx.Gauge`。 相关的例子就是图7.9所显示的进度条。 **图7.9** ![](https://box.kancloud.cn/2016-08-21_57b9960ba3e13.gif) 例7.9显示了产生图7.9的代码。与本章中许多别的例子不同的是,这里我们增加了一个事件处理器。下面的代码在空闭时调整标尺的值,使得值周而复始的变化。 **例7.9** **显示并更新一个`wx.Gauge`** ``` import wx class GaugeFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Gauge Example', size=(350, 150)) panel = wx.Panel(self, -1) self.count = 0 self.gauge = wx.Gauge(panel, -1, 50, (20, 50), (250, 25)) self.gauge.SetBezelFace(3) self.gauge.SetShadowWidth(3) self.Bind(wx.EVT_IDLE, self.OnIdle) def OnIdle(self, event): self.count = self.count + 1 if self.count = 50: self.count = 0 self.gauge.SetValue(self.count) if __name__ == '__main__': app = wx.PySimpleApp() GaugeFrame().Show() app.MainLoop() ``` `wx.Gauge`的构造函数类似于其它的数字的窗口部件: `wx.Gauge(parent`, `id`, `range`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `style`=`wx.GA_HORIZONTAL`, `validator`=`wx.DefaultValidator`, `name`="`gauge`") 当你使用参数`range`来指定数字值时,该值代表标尺的上限,而下限总是0。默认样式`wx.GA_HORIZONTAL`提供了一个水平条。要将它旋转90度,使用`wx.GA_VERTICAL`样式。如果你是在`Windows`上,那么样式`wx.GA_PROGRESSBAR`给你的是来自`Windows`工具包的本地化的进度条。 作为一个只读控件,`wx.Gauge`没有事件。然而,它的属性你可以设置。你可以使用`GetValue()`, `Set`-`Value(pos)`, `GetRange()`, 和 `SetRange(range)`来调整它的值和范围。如果你是在`Windows`上,并且没有使用本地进度条样式,那么你可以使用`SetBezelFace(width)` `and` `SetShadowWidth()`来改变3D效果的宽度。 ## 给用户以选择 几乎每个应用程序都要求用户在一套预先定义的选项间进行选择。在`wxPython`中,有多种窗口部件帮助用户处理这种任务,包括复选框、单选按钮、列表框和组合框。接下来的部分将介绍这些窗口部件。 ### 如何创建一个复选框? 复选框是一个带有文本标签的开关按钮。复选框通常成组的方式显示,但是每个复选框的开关状态是相互独立的。当你有一个或多个需要明确的开关状态的选项时,可以使用复选框。图7.10显示了一组复选框。 **图7.10** ![](https://box.kancloud.cn/2016-08-21_57b9960bb7fa4.gif) 在`wxPython`中复选框很容易使用。它们是`wx.CheckBox`类的实例,并且通过把它们一起放入一个父容器中可以让它们在一起显示。例7.10提供了生成图7.10的代码。 **例7.10** **插入三个复选框到一个框架中** ``` import wx class CheckBoxFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Checkbox Example', size=(150, 200)) panel = wx.Panel(self, -1) wx.CheckBox(panel, -1, "Alpha", (35, 40), (150, 20)) wx.CheckBox(panel, -1, "Beta", (35, 60), (150, 20)) wx.CheckBox(panel, -1, "Gamma", (35, 80), (150, 20)) if __name__ == '__main__': app = wx.PySimpleApp() CheckBoxFrame().Show() app.MainLoop() ``` `wx.CheckBox`有一个典型的`wxPython`构造函数: `wx.CheckBox(parent`, `id`, `label`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `style`=0, `name`="`checkBox`") `label`参数是复选框的标签文本。复选框没有样式标记,但是它们产生属于自己的独一无二的命令事件:`EVT_CHECKBOX`。`wx.CheckBox`的开关状态可以使用`GetValue()`和`SetValue(state)`方法来访问,并且其值是一个布尔值。`IsChecked()`方法等同于`GetValue()`方法,只是为了让代码看起来更易明白。 ### 如何创建一组单选按钮(radio **`button`)?** 单选按钮是一种允许用户从几个选项中选择其一的窗口部件。与复选框不同,单选按钮是显式地成组配置,并且只能选择其中一个选项。当选择了新的选项时,上次的选择就关闭了。单选按钮的使用比复选框复杂些,因为它需要被组织到一组中以便使用。`radio` `button`的名字得自于老式轿车上有着同样行为的成组的选择按钮。 在`wxPython`中,有两种方法可以创建一组单选按钮。其一,`wx.RadioButton`,它要求你一次创建一个按钮,而`wx.RadioBox`使你可以使用单一对象来配置完整的一组按钮,这些按钮显示在一个矩形中。 `wx.RadioButton`类更简单些,在单选按钮对其它窗口部件有直接影响或单选按钮不是布置在一个单一的矩形中的情况下,它是首选。图7.11显示了一组`wx.RadioButton`对象的列子。 **图7.11** ![](https://box.kancloud.cn/2016-08-21_57b9960bcda38.gif) 我们在这个例子中使用`wx.RadioButton`的原因是因为每个单选按钮控制着一个关联的文本控件。由于窗口部件是位于这组单选按钮之外的,所以我们不能只用一个单选按钮框。 **如何创建单选按钮** 例7.11显示了图7.11的代码,它管理单选按钮和文本控件之间的联系。 **例7.11** **使用`wx.RadioButton`来控制另一个窗口部件** ``` import wx class RadioButtonFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Radio Example', size=(200, 200)) panel = wx.Panel(self, -1) #创建单选按钮 radio1 = wx.RadioButton(panel, -1, "Elmo", pos=(20, 50), style=wx.RB_GROUP) radio2 = wx.RadioButton(panel, -1, "Ernie", pos=(20, 80)) radio3 = wx.RadioButton(panel, -1, "Bert", pos=(20, 110)) #创建文本控件 text1 = wx.TextCtrl(panel, -1, "", pos=(80, 50)) text2 = wx.TextCtrl(panel, -1, "", pos=(80, 80)) text3 = wx.TextCtrl(panel, -1, "", pos=(80, 110)) self.texts = {"Elmo": text1, "Ernie": text2, "Bert": text3}#连接按钮和文本 for eachText in [text2, text3]: eachText.Enable(False) for eachRadio in [radio1, radio2, radio3]:#绑定事件 self.Bind(wx.EVT_RADIOBUTTON, self.OnRadio, eachRadio) self.selectedText = text1 def OnRadio(self, event):#事件处理器 if self.selectedText: self.selectedText.Enable(False) radioSelected = event.GetEventObject() text = self.texts[radioSelected.GetLabel()] text.Enable(True) self.selectedText = text if __name__ == '__main__': app = wx.PySimpleApp() RadioButtonFrame().Show() app.MainLoop() ``` 我们创建了单选按钮和文本框,然后使用字典来建立它们间的连接。一个`for`循环使得两个文本框无效,另一个`for`循环绑定单选按钮命令事件。当事件发生的时候,当前活动的文本框变为无效,与被敲击的按钮相匹配的文本框变为有效。 `wx.RadioButton`的使用类似于是`wx.CheckBox`。它们的构造函数几乎是相同的,如下所示: `wx.RadioButton(parent`, `id`, `label`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `style`=0, `validator`=`wx.DefaultValidator`, `name`="`radioButton`") 在复选框中,`label`是相应按钮的显示标签。 `wx.RB_GROUP`样式声明该按钮位于一组单选按钮开头。一组单选按钮的定义是很重要的,因为它控制开关行为。当组中的一个按钮被选中时,先前被选中的按钮被切换到未选中状态。在一个单选按钮使用`wx.RB_GROUP`被创建后,所有后来的被添加到相同父窗口部件中的单选按钮都被添加到同一组,直到另一单选按钮使用`wx.RB_GROUP`被创建,并开始下一个组。在例7.11中,第一个单选按钮是使用`wx.RB_GROUP`声明的,而后来的没有。结果导致所有的按钮都被认为在同一组中,这样一来,敲击它们中的一个时,先前被选中按钮将关闭。 **使用单选框** 通常,如果你想去显示一组按钮,分别声明它们不是最好的方法。取而代之,`wxPython`使用`wx.RadioBox`类让你能够创建一个单一的对象,该对象包含了完整的组。如图7.12所示,它看起来非常类似一组单选按钮。 **图7.12** ![](https://box.kancloud.cn/2016-08-21_57b9960be4dbc.gif) 要使用`wx.RadioBox`类,你所需要的全部就是构造函数。例7.12显示了图7.12的代码。 **例7.12** **建造单选框** ``` import wx class RadioBoxFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Radio Box Example', size=(350, 200)) panel = wx.Panel(self, -1) sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'] wx.RadioBox(panel, -1, "A Radio Box", (10, 10), wx.DefaultSize, sampleList, 2, wx.RA_SPECIFY_COLS) wx.RadioBox(panel, -1, "", (150, 10), wx.DefaultSize, sampleList, 3, wx.RA_SPECIFY_COLS | wx.NO_BORDER) if __name__ == '__main__': app = wx.PySimpleApp() RadioBoxFrame().Show() app.MainLoop() ``` `wx.RadioBox`的构造函数比简单的单选按钮更复杂,因为你需要去一下子为所有的按钮指定数据,如下所示: ``` wx.RadioBox(parent, id, label, pos=wx.DefaultPosition, size=wxDefaultSize, choices=None, majorDimension=0, style=wx.RA_SPECIFY_COLS, validator=wx.DefaultValidator, name="radioBox") ``` `label`参数是静态文本,它显示在单选框的边框上。这些按钮使用`choices`参数指定,它是一个`Python`的字符串标签的序列。 如同网格的`sizer`一样,你通过使用规定一个维数的尺寸来指定`wx.RadioBox`的尺度,`wxPython`在另一维度上自动填充。维度的主尺寸使用`majorDimension`参数指定。哪一维是主要的由样式标记决定。默认值是`wx.RA_SPECIFY_COLS`。在本例中,左框的列数被设置为2,右框的列数被设置为3,行数由`choices`列表中的元素数量动态的决定。如果你想得到相反的行为,你要将样式设置为`wx.RA_SPECIFY_ROWS`。如果你想在单选框被敲击时响应命令事件,那么这个命令事件是`EVT_RADIOBOX`。 `wx.RadioBox`类有许多方法来管理框中的不同的单选按钮。这些方法使你能够处理一个特定的内部按钮,传递该按钮的索引。索引以0为开始,并按严格的顺序展开,它的顺序就是按钮标签传递给构造函数的顺序。表7.11列出了这些方法。 **表7.11** **`wx.RadioBox`的方法** `EnableItem(n`, `flag)`:`flag`参数是一个布尔值,它用于使索引为n的按钮有效或无效。要使整个框立即有效,使用`Enable()`。 `FindString(string)`:根据给定的标签返回相关按钮的整数索引值,如果标签没有发现则返回-1。 `GetCount()`:返回框中按钮的数量。 `GetItemLabel(n)` `SetItemLabel(n`, `string)`:返回或设置索引为n的按钮的字符串标签。 `GetSelection()` `GetStringSelection()` `SetSelection(n)` `SetStringSelection(` `string)`:`GetSelection()` 和 `SetSelection()`方法处理当前所选择的单选按钮的整数索引。`GetStringSelection()`返回当前所选择的按钮的字符串标签,`SetStringSelection()`改变所选择的按钮的字符串标签为给定值。没有`set`*()产生`EVT_RADIOBOX`事件。 `ShowItem(item`, `show)`:`show`参数是一个布尔值,用于显示或隐藏索引为`item`的按钮。 单选按钮不是给用户一系列选择的唯一方法。列表框和组合框占用的空间也少,也可以被配置来让用户从同一组中作多个选择。 ### 如何创建一个列表框? 列表框是提供给用户选择的另一机制。选项被放置在一个矩形的窗口中,用户可以选择一个或多个。列表框比单选按钮占据较少的空间,当选项的数目相对少的时候,列表框是一个好的选择。然而,如果用户必须将滚动条拉很远才能看到所有的选项的话,那么它的效用就有所下降了。图7.13显示了一个`wxPython`列表框。 ![](https://box.kancloud.cn/2016-08-21_57b9960c0ac54.gif) 在`wxPython`中,列表框是类`wx.ListBox`的元素。该类的方法使你能够处理列表中的选择。 **如何创建一个列表框** 例7.13显示了产生图7.13的代码 **例7.13** **使用`wx.ListBox`** ``` import wx class ListBoxFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'List Box Example', size=(250, 200)) panel = wx.Panel(self, -1) sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen'] listBox = wx.ListBox(panel, -1, (20, 20), (80, 120), sampleList, wx.LB_SINGLE) listBox.SetSelection(3) if __name__ == '__main__': app = wx.PySimpleApp() ListBoxFrame().Show() app.MainLoop() ``` `wx.ListBox`的构造函数类似于单选框的,如下所示: `wx.ListBox(parent`, `id`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `choices`=`None`, `style`=0, `validator`=`wx.DefaultValidator`, `name`="`listBox`") 单选框和列表框的主要区别是`wx.ListBox`没有`label`属性。显示在列表中的元素放置在参数`choices`中,它是一个字符串的序列。列表框有三种互斥的样式,它决定用户如何从列表框中选择元素,说明在表7.12中。 用户通常对于多选有一些问题,因为它们一般希望见到的是单选列表,对于多选来说可能是有挑战性的(就像单选题和多选题一样),尤其是对于那些易受困扰的用户。如果你使用了一个多选的列表,我们建议你清楚地标明该列表。 **表7.12** **列表框的选择类型样式** `wx.LB_EXTENDED`:用户可以通过使用`shift`并敲击鼠标来选择一定范围内的连续的选项,或使用等同功能的按键。 `wx.LB_MULTIPLE`:用户可以一次选择多个选项(选项可以是不连续的)。实际上,在这种情况下,列表框的行为就像是一组复选框。 `wx.LB_SINGLE`:用户一次只能选一个选项。实际上,在这种情况下,列表框的行为就像是一组单选按钮。 有三种控制`wx.ListBox`中滚动条的显示的样式,如表7.13所示。 **表7.13** **列表框的滚动条类型样式** `wx.LB_ALWAYS_SB`:列表框将始终显示一个垂直的滚动条,不管有没有必要。 `wx.LB_HSCROLL`:如果本地控支持,那么列表框在选择项太多时,将创建一个水平滚动条。 `wx.LB_HSCROLL`:列表框只在需要的时候显示一个垂直的滚动条。这是默认样式。 还有一个样式`wx.LB_SORT`,它使得列表中的元素按字母顺序排序。 有两个专用于`wx.ListBox`的命令事件。`EVT_LISTBOX`事件在当列表中的一个元素被选择时触发(即使它是当前所选择的元素)。如果列表被双击,`EVT_LISTBOX_DCLICK`事件发生。 有一些专用于列表框的方法,你可以用来处理框中的项目。表7.14对许多的方法作了说明。列表框中的项目索引从0开始。 一旦你有了一个列表框,自然就想把它与其它的窗口部件结合起来使用,如下拉菜单,或复选框。在下一节,我们对此作讨论。 **表7.14** **列表框的方法** `Append(item)`:把字符串项目添加到列表框的尾部。 `Clear()`:清空列表框。 `Delete(n)`:删除列表框中索引为n的项目。 `Deselect(n)`:在多重选择列表框中,导致位于位置n的选项取消选中。在其它样式中不起作用。 `FindString(string)`:返回给定字符串的整数位置,如果没有发现则返回-1。 `GetCount()`:返回列表中字符串的数量。 `GetSelection()` `SetSelection(n`, `select)` `GetStringSelection()` `SetStringSelection(string`, `select)` `GetSelections()`:`GetSelection()`得到当前选择项的整数索引(仅对于单选列表)。对于多选列表,使用`GetSelections()`来返回包含所选项目的整数位置的元组。对于单选列表,`GetStringSelection()`返回当前选择的字符串。相应的`set`方法使用布尔值参数`select`设置指定字符串或索引选项的状态。使用这种方法改变选择不触发`EVT_LISTBOX`事件。 `GetString(n)` `SetString(n`, `string)`:得到或设置位置n处的字符串。 `InsertItems(items`, `pos)`:插入参数`items`中的字符串列表到该列表框中`pos`参数所指定的位置前。位置0表示把项目放在列表的开头。 `Selected(n)`:返回对应于索引为n的项目的选择状态的布尔值。 `Set(choices)`:重新使用`choices`的内容设置列表框。 ### 如何合并复选框和列表框? 你可以使用类`wx.CheckListBox`来将复选框与列表框合并。图7.14显示了列表框和复选框在合并在一起的例子。 **图7.14** ![](https://box.kancloud.cn/2016-08-21_57b9960c22100.gif) `wx.CheckListBox`的构造函数和大多数方法与`wx.ListBox`的相同。它有一个新的事件:`wx.EVT_CHECKLISTBOX`,它在当列表中的一个复选框被敲击时触发。它有两个管理复选框的新的方法:`Check(n`, `check)`设置索引为n的项目的选择状态,`IsChecked(item)`在给定的索引的项目是选中状态时返回`True`。 ### 如果我想要下拉形式的选择该怎么做? 下拉式选择是一种仅当下拉箭头被敲击时才显示选项的选择机制。它是显示所选元素的最简洁的方法,当屏幕空间很有限的时候,它是最有用的。图7.15显示了一个关闭的下拉式选择。图7.16显示了一个打开的下拉式选择。 **图7.15** ![](https://box.kancloud.cn/2016-08-21_57b9960c346ad.gif) **图7.16** ![](https://box.kancloud.cn/2016-08-21_57b9960c49741.gif) 下拉式选择的使用与标准的列表框是很相似的。例7.14显示了如何创建一个下拉式选择。 **例7.14** ``` import wx class ChoiceFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Choice Example', size=(250, 200)) panel = wx.Panel(self, -1) sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'] wx.StaticText(panel, -1, "Select one:", (15, 20)) wx.Choice(panel, -1, (85, 18), choices=sampleList) if __name__ == '__main__': app = wx.PySimpleApp() ChoiceFrame().Show() app.MainLoop() ``` `wx.Choice`的构造函数与列表框的基本相同: `wx.Choice(parent`, `id`, `pos`=`wx.DefaultPosition`, `size`=`wx.DefaultSize`, `choices`=`None`, `style`=0, `validator`=`wx.DefaultValidator`, `name`="`choice`") `wx.Choice`没有专门的样式,但是它有独特的命令事件:`EVT_CHOICE`。几乎表7.14中所有适用于单选列表框的方法都适用于`wx.Choice`对象。 ### 我能够将文本域与列表合并在一起吗? 将文本域与列表合并在一起的窗口部件称为组合框,其本质上是一个下拉选择和文本框的组合。图7.17显示了一个组合框。 **图7.17** 左边是`wx.CB_DropDOWN`样式,右边是`wx.CB_SIMPLE`样式 ![](https://box.kancloud.cn/2016-08-21_57b9960c5ea9a.gif) 在`Windows`上,你可以使用右边的样式,它是一个列表框和文本框的组合。 创建组合框的代码与我们已经见过的选择是类似的。该类是`wx.ComboBox`,它是`wx.Choice`的一个子类。例7.15显示了图7.17的代码: **例7.15** ``` import wx class ComboBoxFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Combo Box Example', size=(350, 300)) panel = wx.Panel(self, -1) sampleList = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'] wx.StaticText(panel, -1, "Select one:", (15, 15)) wx.ComboBox(panel, -1, "default value", (15, 30), wx.DefaultSize, sampleList, wx.CB_DropDOWN) wx.ComboBox(panel, -1, "default value", (150, 30), wx.DefaultSize, sampleList, wx.CB_SIMPLE) if __name__ == '__main__': app = wx.PySimpleApp() ComboBoxFrame().Show() app.MainLoop() ``` `wx.ComboBox`的构造函数如下所示: ``` wx.ComboBox(parent, id, value="", pos=wx.DefaultPosition, size=wx.DefaultSize, choices, style=0, validator=wx.DefaultValidator, name="comboBox") ``` 对于`wx.ComboBox`来说有4种样式。其中的两种决定了如何绘制组合框:`wx.CB_DropDOWN`创建一个带有下拉列表的组合框,`wx.CB_SIMPLE`创建一个带有列表框的组合框。在`Windows`上你可以只使用`wx.CB_SIMPLE`样式。任何组合框都可以被指定为`wx.CB_READONLY`样式,它防止用户在文本域中键入。当组合框被指定为只读时,所做的选择必须来自于选择列表的元素之一,即使你用程序来设置它也不行。最后`wx.CB_SORT`样式导致选择列表中的元素按字母顺序显示。 由于`wx.ComboBox`是`wx.Choice`的子类,所有的`wx.Choice`的方法都能被组合框调用,如表7.14所示。另外,还有许多方法被定义来处理文本组件,它们的行为同`wx.TextCtrl`(参见表7.4),所定义的方法有`Copy()`, `Cut()`, `GetInsertionPoint()`, `GetValue()`, `Paste()`, `Replace(from`,`to`, `text)`, `Remove(from`, `to)`, `SetInsertionPoint(pos)`, `SetInsertionPointEnd()`,和 `SetValue()`。 ## 本章小结 在这一章中,我们给你展示了如何使用`wxPython`中许多最基本和常用的控件。这些通用的版本在跨平台使用时显得一致性较好。 1、对于静态文本标签的显示,你可以使用`wx.StaticText`类。还有一个完全用`wxPython`实现的版本,名为`wx.lib.stattext.GenStaticText`。 2、如果你需要一个控件以让用户输入文本,那么使用类`wx.TextCtrl`。它允许单行和多行的输入,还有密码掩饰和其它的功用。如果本地控支持它,你可以使用`wx.TextCtrl`来得到样式文本。样式是`wx.Text`-`Attr`类的实例,`wx.Font`包含字体信息。对于所有的系统,你可以使用类`wx.stc.StyledTextCtrl`(它是`wxPython`对开源`Scintilla`文本组件的封装)在一个可编辑的文本组件中实现颜色和字体样式。 3、创建按钮,使用`wx.Button`类,它也有一个通用版`wx.lib.buttons.GenButton`。按钮可以使用位图来代替一个文本标签(`wx.BitmapButton`),或在按下和未按下之间有一个开关状态。还有一个等价于位图和开关按钮的通用版,它比标准版有更全面的特性。 4、有一些方法用于选择或显示数字值。你可以使用`wx.Slider`类来显示一个垂直或水平的滑块。`wx.SpinCtrl`显示一个可以使用上下按钮来改变数字值的文本控件。`wx.Gauge`控件显示一个进度条指示器。 5、你可以从一系列的控件中选出让用户从列表选项作出选择的最佳控件,最佳控件所应考虑的条件是选项的数量,用户能否多选和你想使用的屏幕空间的总量。复选框使用`wx.CheckBox`类。这儿有两个方法去得到单选按钮:`wx.RadioButton`给出单个单选按钮,而`wx.RadioBox`给出显示在一起的一组按钮。这儿有几个列表显示控件,它们的用法相似。列表框的创建使用`wx.ListBox`,并且你可以使用`wx.CheckListBox`来增加复选框。对于更简洁的下拉式,使用`wx.Choice.` `wx.ComboBox`合并了列表和文本控件的特性。 到目前为止,我们已经涉及了基本的常用窗口部件,在接下来的章节,我们将讨论不同种类的框架,它可以用这些框架来包含我们已经涉及了基本的常用窗口部件。