ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 第九章 通过对话框让用户选择 1. [使用模式对话框工作](#A.2BT391KGohXw9b.2BYvdaEZd5U9c-) 2. [使用标准对话框](#A.2BT391KGgHUcZb.2BYvdaEY-) 3. [创建向导](#A.2BUhte.2BlQRW.2Fw-) 4. [显示启动提示](#A.2BZj55OlQvUqhj0Hk6-) 5. [使用验证器(validator)来管理对话框中的数据](#A.2BT391KJqMi8FWaP8I-validator.2B.2FwlnZXuhdAZb.2BYvdaEZOLXaEZXBjbg-) 6. [本章小结](#A.2BZyx64FwPftM-) 模式对话框用于与用户进行快速的交互或在用户可以执行程序的下一步之前,对话框中的信息必须被输入的时候。在`wxPython`中,有几个标准的函数用来显示基本的模式对话框。这些对话框包括警告框,单行文本域,和从列表中选择。在随后的部分,我们将给你展示这些对话框,以及如何使用这些预定义的函数来减轻你的工作量。 ### 如何创建一个模式对话框? 模式对话框阻塞了别的窗口部件接收用户事件,直到该模式对话框被关闭;换句话说,在它存在期间,用户一直被置于对话模式中。如图9.1所示,你不能总是根据外观来区别对话框和框架。在`wxPython`中,对话框与框架间的区别不是基于它们的外观的,而主要是它们处理事件的办法的实质。 **图9.1** **一个模式对话框** ![](https://box.kancloud.cn/2016-08-21_57b9960d76f9d.gif) 对话框的创建和配置与框架稍微有些不同。例9.1显示了产生图9.1的代码。所显示的对话框上的按钮被敲击后,该对话框就关闭了,并且一条消息被输出到`stdout(`标准输出)。 **例9.1** **定义一个模式对话框** ``` import wx class SubclassDialog(wx.Dialog): def __init__(self):#初始化对话框 wx.Dialog.__init__(self, None, -1, 'Dialog Subclass', size=(300, 100)) okButton = wx.Button(self, wx.ID_OK, "OK", pos=(15, 15)) okButton.SetDefault() cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel", pos=(115, 15)) if __name__ == '__main__': app = wx.PySimpleApp() app.MainLoop() dialog = SubclassDialog() result = dialog.ShowModal()#显示模式对话框 if result == wx.ID_OK: print "OK" else: print "Cancel" dialog.Destroy() ``` 与前一章的`wx.Frame`的例子比较,这儿有两个需要注意的事情。在`__init__`方法中,按钮是被直接添加到`wx.Dialog`,而非`wx.Panel`。面板在对话框中的使用比在框架中少的多,部分原因是因为对话框与框架相比倾向简单化,但主要是因为`wx.Panel`特性(标准系统背景和`tab`键横向切换控件焦点)已经默认存在于`wx.Dialog`中。 要显示为模式对话框,使用`ShowModal()`方法。这与用于框架的的`Show()`方法在对程序的执行上有不同的作用。在调用`ShowModal()`后你的应用程序将处于等待中,直到对话框被取消。 模式将保持到对话框方法`EndModal(retCode)`被调用,该方法关闭对话框。参数`retCode`是由`ShowModal()`方法返回的一个整数值。典型的,应用程序利用这个返回值来知道用户是如何关闭对话框的,以控制以后的操作。但是结束这个模式并没有销毁或甚至关闭对话框。保持对话框的存在可能是一件好事,因为这意味你可以把用户选择的信息存储为对话框实例的数据成员,并且即使在对话框被关闭后也能从对话框重新获得那些信息。在接下来的部分,我们将看一些我们使用对话框处理程序中用户输入的数据的例子。 由于例9.1中没有定义事件处理器,你可能会惊奇对话框是如何响应按钮敲击的。这个行为已经定义在`wxDialog`中了。有两个预定义的`wxPython` `ID`号,它们在对话框中有特殊的意思。当对话框中的一个使用`wx.ID_OK` `ID`的`wx.Button`被敲击时,模式就结束了,对话框也关闭了,`wx.ID_OK`就是`ShowModal()`调用返回的值。同样,一个使用`wx.ID_CANCEL` `ID`的按钮做相同的事情,但是`ShowModal()`的返回值是`wx.ID_CANCEL`。 例9.1显示了处理模式对话框的一个典型的方法。在对话框被调用后,返回值被用作`if`语句中的测试。在这种情况下,我们简单地打印结果,在更复杂的例子中,`wx.ID_OK`将执行用户在对话框中所要求的动作,如打开文件或选择颜色。 典型的,你在完成对对话框的使用后,你应该显式地销毁它。这通知C++对象它应该自我销毁,然后这将使得它的`Python`部分被作为垃圾回收。如果你希望在你的应用程序中,以后再次使用该对话框时不重建它,以加速对话框的响应时间,那么你可以保持对该对话框的一个引用,并当你需要再次激活它时,简单地调用它的`ShowModal()`方法。当应用程序准备退出时,确保已销毁了它,否则`MainLoop()`将仍将它作为一个存在的顶级窗口,并且程序将不能正常退出。 ### 如何创建一个警告框? 经由一个对话框与用户交互的最简单的三个办法分别是:`wx.MessageDialog`,它是一个警告框、`wx.TextEntryDialog`,它提示用户去输入一些短的文本、`wx.SingleChoiceDialog`,它使用户能够从一个有效选项列表中进行选择。在接下来的三个小节中,我们将论这些简单的对话框。 消息对话框显示一个短的消息,并使用户通过按下按钮来作响应。通常,消息框被用作去显示重要的警告、`yes`/`no`问题、或询问用户是否继续某种操作。图9.2显示了一个典型的消息框。 **图9.2** ![](https://box.kancloud.cn/2016-08-21_57b9960d8e353.gif) 使用消息框是十分的简单。例9.2显示了创建一个消息框的两种办法。 **例9.2** **创建一个消息框** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() # 方法一,使用类 dlg = wx.MessageDialog(None, "Is this explanation OK?", 'A Message Box', wx.YES_NO | wx.ICON_QUESTION) retCode = dlg.ShowModal() if (retCode == wx.ID_YES): print "yes" else: print "no" dlg.Destroy() # 方法二,使用函数 retCode = wx.MessageBox("Is this way easier?", "Via Function", wx.YES_NO | wx.ICON_QUESTION) ``` 例9.2创建了两个消息框,一个在另一个的后面。这第一个方法是创建类`wx.MessageDialog`的一个实例,并使用`ShowModal()`来显示它。 **使用`wx.MessageDialog`类** 使用`wx.MessageDialog`的构造函数,你可以设置对话框的消息和按钮,构造函数如下: `wx.MessageDialog(parent`, `message`, `caption`="`Message` `box`", * `style`=`wx.OK` | `wx.CANCEL`, `pos`=`wx.DefaultPosition)` `message`参数是实际显示在对话框中的文本。如果消息字符串包含\n字符,那么文本将在此换行。`caption`参数显示在对话框的标题栏中。`pos`参数使你可以指定对话框显示在屏幕上的位置——在微软`Windows`下,这个参数将被忽略。 `wx.MessageDialog`的样式标记分为两类。第一类控制显示在对话框中的按钮。表9.1说明了这些样式。 **表9.1 `wx.MessageDialog`的按钮样式** | | | | --- | --- | | `wx.CANCEL` | 包括一个`cancel`(取消)按钮。这个按钮有一个`ID`值`wx.ID_CANCEL`。 | | `wx.NO_DEFAULT` | 在一个`wx.YES_NO`对话框中,`No`(否)按钮是默认的。 | | `wx.OK` | 包括一个`OK`按钮,这个按钮有一个`ID`值`wx.ID_OK`。 | | `wx.YES_DEFAULT` | 在一个`wx.YES_NO`对话框中,`Yes`按钮是默认的。这是默认行为。 | | `wx.YES_NO` | 包括`Yes`和`No`按钮,各自的`ID`值分别是`wx.ID_YES`和`wx.ID_NO`。 | 第二套样式标记控制紧挨着消息文本的图标。它们显示在表9.2中。 **表9.2 `wx.MessageDialog`的图标样式** | | | | --- | --- | | `wx.ICON_ERROR` | 表示一个错误的图标。 | | `wx.ICON_EXCLAMATION` | 表示警告的图标。 | | `wx.ICON_HAND` | 同`wx.ICON_ERROR`。 | | `wx.ICON_INFORMATION` | 信息图标,字母i。 | | `wx.ICON_QUESTION` | 问号图标。 | 最后,你可以使用样式`wx.STAY_ON_TOP`将对话框显示在系统中任何其它窗口的上面,包括系统窗口和`wxPython`应用程序窗口。 你在例9.2所见到的,对话框通过使用`ShowModal()`被调用。根据所显示的按钮,返回的结果是以下值之一:`wx.ID_OK`, `wx.ID_CANCEL`,`wx.ID_YES`, 或 `wx.ID_NO`。如同其它对话框的情况,你通常使用这些值来控制程序的执行。 **使用`wx.MessageBox()`函数** 例9.2中的#1显示了一个调用消息框的更简短的方法。这个便利的函数`wx.MessageBox()`创建对话框,调用`ShowModal()`,并且返回下列值之一:`wx.YES`, `wx.NO`, `wx.CANCEL`, 或 `wx.OK`。函数的形式比`MessageDialog`的构造函数更简单,如下所示: ``` wx.MessageBox(message, caption="Message", style=wx.OK) ``` 在这个例子中,参数`message`, `caption`, `style`的意思和构造函数中的相同,你可以使用所有相同的样式标记。正如我们贯穿本章将看到的,在`wxPython`预定义的几个对话框都有便利的函数。在你为单一的使用创建对话框的时候,你的选择有一个优先的问题。如果你计划束缚住对话框以便多次调用它,那么你可能会优先选择去实例化对象以便你能够束缚该引用,而不使用函数的方法,尽管这对于这些简单的对话框来说,所节约的时间可以忽略不计。 要在你的消息框中显示大量的文本(例如,终端用户许可证的显示),你可以使用`wxPython`特定的类`wx.lib.dialogs.ScrolledMessageDialog`,它包含如下的构造函数: ``` wx.lib.dialogs.ScrolledMessageDialog(parent, msg, caption, pos=wx.wxDefaultPosition, size=(500,300)) ``` 这个对话框不使用本地消息框控件,它根据别的`wxPython`窗口部件来创建一个对话框。它只显示一个`OK`按钮,并且没有更多的样式信息。 ### 如何从用户得到短的文本? 这第二个简单类型的对话框是`wx.TextEntryDialog`,它被用于从用户那里得到短的文本输入。它通常用在在程序的开始时要求用户名或密码的时候,或作为一个数据输入表单的基本替代物。图9.3显示了一个典型的文本对话框。 **图9.3** **文本输入标准对话框** ![](https://box.kancloud.cn/2016-08-21_57b9960da1d94.gif) 例9.3显示了产生图9.3的代码 **例9.3** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() dialog = wx.TextEntryDialog(None, "What kind of text would you like to enter?", "Text Entry", "Default Value", style=wx.OK|wx.CANCEL) if dialog.ShowModal() == wx.ID_OK: print "You entered: %s" % dialog.GetValue() dialog.Destroy() ``` 在前一小节,我们创建了一个对话框类的实例,在这里,我们要用到的对话框类是`wx.TextEntryDialog`。该类的构造函数比简单消息对话框要复杂一些: ``` wx.TextEntryDialog(parent, message, caption="Please enter text", defaultValue="", style=wx.OK | wx.CANCEL | wx.CENTRE, pos=wx.DefaultPosition) ``` `message`参数是显示在对话框中的文本提示,而`caption`显示在标题栏中。`defaultValue`显示在文本框中的默认值。`style`可以包括`wx.OK`和`wx.CANCEL`,它显示适当的按钮。 几个`wx.TextCtrl`的样式也可以用在这里。最有用的应该是`wx.TE_PASSWORD`,它掩饰所输入的真实密码。你也可以使用`wx.TE_MULTILINE`来使用户能够在对话框中输入多行文本,也可以使用`wx.TE_LEFT`, `wx.TE_CENTRE`, 和 `wx.TE_RIGHT`来调整所输入的文本的对齐位置。 例9.3的最后显示了在文本框和对话框之间的另一区别。用户所输入的信息被存储在该对话框实例中,并且以后必须应用程序获取。在这种情况下,你可以使用对话框的`GetValue()`方法来得到该值。记住,如果用户按下`Cancel`(取消)去退出该对话框,这意味他们不想去使用他所键入的值。你也可以在程序中使用`SetValue()`方法来设置该值。 下面这些是使用文本对话框的便利函数: 1、`wx.GetTextFromUser()` 2、`wx.GetPasswordFromUser()` 3、`wx.GetNumberFromUser()` 其中和例9.3的用处最近似的是`wx.GetTextFromUser()`: ``` wx.GetTextFromUser(message, caption="Input text", default_value="", parent=None) ``` 这里的`message`, `caption`, `default_value`, 和 `parent`与`wx.TextEntryDialog`的构造函数中的一样。如果用户按下`OK`,该函数的返回值是用户所输入的字符串。如果用户按下`Cancel`,该函数返回空字符串。 如果你希望用户输入密码,你可以使用`wx.GetPasswordFromUser()`函数: ``` wx.GetPasswordFromUser(message, caption="Input text", default_value="", parent=None) ``` 这里的参数意义和前面的一样。用户的输入被显示为星号,如果用户按下`OK`,该函数的返回值是用户所输入的字符串。如果用户按下`Cancel`,该函数返回空字符串。 最后,你可以使用`wx.GetNumberFromUser()`要求用户输入一个数字: ``` wx.GetNumberFromUser(message, prompt, caption, value, min=0, max=100, parent=None) ``` 这里的参数的意义有一点不同,`message`是显在`prompt`上部的任意长度的消息,`value`参数是默认显示在文本框中的长整型值。`min`和`max`参数为用户的输入限定一个范围。如果用户按下`OK`按钮退出的话,该方法返回所输入的值,并转换为长整型。如果这个值不能转换为一个数字,或不在指定的范围内,那么该函数返回-1,这意味如果你将该函数用于负数的范围的话,你可能要考虑一个转换的方法。 ### 如何用对话框显示选项列表? 如果给你的用户一个空的文本输入域显得太自由了,那么你可以使用`wx.SingleChoiceDialog`来让他们在一组选项中作单一的选择。图9.4显示了一个例子。 **图9.4** **一个单选对话框** ![](https://box.kancloud.cn/2016-08-21_57b9960db5def.gif) 例9.4显示了产生图9.4的代码 **例9.4** **显示一个选择列表对话框** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() choices = ["Alpha", "Baker", "Charlie", "Delta"] dialog = wx.SingleChoiceDialog(None, "Pick A Word", "Choices", choices) if dialog.ShowModal() == wx.ID_OK: print "You selected: %s\n" % dialog.GetStringSelection() dialog.Destroy() ``` `wx.SingleChoiceDialog`的构造函数如下所示: ``` wx.SingleChoiceDialog(parent, message, caption, choices, clientData=None, style=wx.OK | wx.CANCEL | wx.CENTRE, pos=wx.DefaultPosition) ``` `message`和`caption`参数的意义与前面的一样,分别显示在对话框和标题栏中。`choices`参数要求一个字符串的列表,它们是你呈现在对话框中的选项。`style`参数有三个项,这是默认的,分别是`OK`按钮、`Cancle`按钮和使对话框在屏幕中居中。`centre`选项和`pos`参数在`Windows`操作系统上不工作。 如果你想在用户看见对话框之前,设置它的默认选项,使用`SetSelection(selection)`方法。参数`selection`是选项的索引值,而非实际选择的字符串。在用户选择了一个选项后,你即可以使用`GetSelection()`——它返回所选项的索引值,也可以使用`GetStringSelection()`——它返回实际所选的字符串,来得到它。 有两个用于单选对话框的便利函数。第一个是`wx.GetSingleChoice`,它返回用户所选的字符串: `wx.GetSingleChoice(message`, `caption`, `aChoices`, `parent`=`None)` 参数`message`, `caption`, 和`parent`的意义和`wx.SingleChoiceDialog`构造函数的一样。`aChoices`参数是选项的列表。如果用户按下`OK`,则返回值是所选的字符串,如果用户按下`Cancel`,则返回值是空字符串。这意味如果空字符是一个有效的选择的话,那么你就不该使用这个函数。 第二个是`wx.GetSingleChoiceIndex:` ``` wx.GetSingleChoiceIndex(message, caption, aChoices, parent=None) ``` 这个函数与第一个有相同的参数,但是返回值不同。如果用户按下`OK`,则返回值是所选项的索引,如果用户按下`Cancel`,则返回值是-1。 ### 如何显示进度条? 在许多程序中,程序需要自己做些事情而不受用户输入的干扰。这时就需要给用户一些可见的显示,以表明程序正在做一些事情及完成的进度。在`wxPython`中,这通常使用一个进度条来管理,如图9.5所示。 **图9.5** ![](https://box.kancloud.cn/2016-08-21_57b9960dc8ff1.gif) 例9.5显示了产生图9.5的代码 **例9.5** **生成一个进度条** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() progressMax = 100 dialog = wx.ProgressDialog("A progress box", "Time remaining", progressMax, style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME) keepGoing = True count = 0 while keepGoing and count progressMax: count = count + 1 wx.Sleep(1) keepGoing = dialog.Update(count) dialog.Destroy() ``` 进度条的所有选项在构造函数中被设置,构造函数如下: ``` wx.ProgressDialog(title, message, maximum=100, parent=None, style=wx.PD_AUTO_HIDE | wx.PD_APP_MODAL) ``` 这些参数不同于其它对话框的。参数`title`被放置在窗口的标题栏,`message`被显示在对话框中。`maximum`是你用来显示进度计数的最大值。 表9.3 列出了特定于`wx.ProgressDialog`六个样式,它们影响进度条的行为。 **表9.3 `wx.ProgressDialog`的样式** | | | | --- | --- | | `wx.PD_APP_MODAL` | 如果设置了这个样式,进度条对整个应用程序是模式的,这将阻塞所有的用户事件。如果没有设置这个样式,那么进度条仅对它的父窗口是模式的。 | | `wx.PD_AUTO_HIDE` | 进度条将自动隐藏自身直到它达到它的最大值。 | | `wx.PD_CAN_ABORT` | 在进度条上放上一个`Cancel`按钮,以便用户停止。如何响应来自该对话框的取消将在以后说明。 | | `wx.PD_ELAPSED_TIME` | 显示该对话框已经出现了多长时间。 | | `wx.PD_ESTIMATED_TIME` | 显示根据已花的时间、当前的计数值和计数器的最大值所估计出的完成进度所需的总时间。 | | `wx.PD_REMAINING_TIME` | 显示要完成进度所估计的剩余时间,或(所需总时间-已花时间)。 | 要使用进度条,就要调用它的唯一的方法`Update(value`,`newmsg`="")。`value`参数是进度条的新的内部的值,调用`update`将导致进度条根据新的计数值与最大计算值的比例重绘。如果使用可选的参数`newmsg`,那么进度条上的文本消息将变为该字符串。这让你可以给用户一个关于当前进度的文本描述。 这个`Update()`方法通常返回`True`。但是,如果用户通过`Cancel`按钮已经取消了该对话框,那么下次的`Update()`将返回`False`。这是你响应用户的取消请求的机会。要检测用户的取消请求,我们建议你尽可能频繁地`Update()`。 ## 使用标准对话框 大多数操作系统都为像文件选择、字体选择和颜色选择这些任务提供了标准对话框。这为平台提供了一致感观。你也可以使用来自于`wxPython`的这些对话框,它们也为你的应用程序提供了一致的感观。如果你使用`wxPython`,那么它为你提供了类似的对话框,即使所在的平台没有提供系统对话框。 ### 如何使用文件选择对话框? 在`wxPython`中,`wx.FileDialog`为主流的平台使用本地操作系统对话框,对其它操作系统使用非本地相似的外观。微软`Windows`的版本如图9.6所示。 **图9.6** ![](https://box.kancloud.cn/2016-08-21_57b9960ddf814.gif) 你可以设置文件对话框开始在任一目录,你也可以使用通配符过滤来限制去显示某种文件类型。例9.6显示了一个基本的例子。 **例9.6** **使用`wx.FileDialog`** ``` import wx import os if __name__ == "__main__": app = wx.PySimpleApp() wildcard = "Python source (*.py)|*.py|" \ "Compiled Python (*.pyc)|*.pyc|" \ "All files (*.*)|*.*" dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", wildcard, wx.OPEN) if dialog.ShowModal() == wx.ID_OK: print dialog.GetPath() dialog.Destroy() ``` 文件对话框是我们这章已见过的最复杂的对话框,它有几个属性可以通过编程的方式读写。它的构造函数使得你能够设置它的一些属性: ``` wx.FileDialog(parent, message="Choose a file", defaultDir="", defaultFile="", wildcard="*.*", style=0, pos=wx.DefaultPosition) ``` `message`参数出现在窗口的标题栏中。`defaultDir`参数告诉对话框初始的时候显示哪个目录。如果这个参数为空或表示的目录不存在,那么对话框开始在当前目录。`defaultFile`是默认保存为的文件。`wildcard`参数使你可以基于给定的模式来过滤列表,使用通常的*和?作为通配符。通配符可以是单个模式,如*.`py`或格式如 描述 | 模式 | 描述 | 模式 的一系列模式——类似于例9.6中所用。 "`Python` `source` (*.`py)`|*.`py`|`Compiled` `Python` (*.`pyc)`|*.`pyc`| * `All` `files` (*.*)|*.*" 如果有一个多个项目的模式,那么它们显示在图9.6所示的下拉菜单中。`pos`参数不保证被基本的系统所支持。 **选择一个文件** `wx.FileDialog`的两个最重要的样式标记是`wx.OPEN`和`wx.SAVE`,它们表明对话框的类型并影响对话框的行为。 用于打开文件的对话框有两个标记,它们进一步影响对话框的行为。`wx.HIDE_READONLY`标记灰化复选框,使用户以只读模式打开文件。`wx.MULTIPLE`标记使用户可以在一个目录中选择打开多个文件。 保存文件对话框有一个有用的标记`wx.OVERWRITE_PROMPT`,它使得保存文件时,如果有相同的文件存在,则提示用户是否覆盖。 两种文件对话框都可以使用`wx.CHANGE_DIR`标记。当使用这个标记时,文件的选择也可改变应用程序的工作目录为所选文件所在的目录。这使得下次文件对话框打开在相同的目录,而不需要应用程序再在别处存储该值。 和本章迄今为止我人们所见过的其它对话框不一样,文件对话框的属性`directory`,`filename`, `style`, `message`, 和`wildcard`是可以通过方法来得到和设置的。这些方法使用`Get`/`Set`命名习惯。 在用户退出对话框后,如果返回值是`wx.OK`,那么你可以使用方法`GetPath()`来得到用户的选择,该函数的返回值是字符串形式的文件全路径名。如果对话框是一个使用了`wx.MULTIPLE`标记的打开对话框,则用`GetPaths()`代替`GetPath()`。该方法返回路径字符串的一个`Python`列表。如果你需要知道在用户选择时使用了下拉菜单中的哪个项,你可以使用`GetFilterIndex()`,它返回项目的索引。要通过编程改变索引,使用方法`SetFilterIndex()`。 这后面的是一个使用文件对话框的便利函数: ``` wx.FileSelector(message, default_path="", default_filename="", default_extension="", wildcard="*.*'', flags=0, parent=None, x=-1, y=-1) ``` `message`, `default_path`, `default_filename`, 和 `wildcard`参数意义与构造函数的基本相同,尽管参数的名字不同。`flags`参数通常被称作`style`,`default_extension`参数是保存为文件时默认的后缀(如果用户没有指定后缀的情况下)。如果用户按下`OK`,返回值是字符串形式的路径名,如果用户按下`Cancel`则返回一个空字符串。 **选择一个目录** 如果用户想去选择一个目录而非一个文件,使用`wx.DirDialog`,它呈现一个目录树的视图,如图9.7所示。 这个目录选择器比文件对话框简单些。例9.7显示了相关的代码。 **例9.7** **显示一个目录选择对话框** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() dialog = wx.DirDialog(None, "Choose a directory:", style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON) if dialog.ShowModal() == wx.ID_OK: print dialog.GetPath() dialog.Destroy() ``` **图9.7** ![](https://box.kancloud.cn/2016-08-21_57b9960e02b7f.gif) 这个对话框的所有的功能几乎都在构造函数中: ``` wx.DirDialog(parent, message="Choose a directory", defaultPath="", style=0, pos = wx.DefaultPosition, size = wx.DefaultSize, name="wxDirCtrl") ``` 由于`message`参数显示在对话框中,所以你不需要一个钩子去改变标题栏。`defaultPath`告诉对话框选择的默认路径,如果它为空,那么对话框显示文件系统的根目录。`pos`和`size`参数在微软`Windows`下被忽略,`name`参数在所有的操作系统下都被忽略。该对话框的样式标记`wx.DD_NEW_DIR_BUTTON`给对话框一个用于创建目录的一个按钮。这个标记在老版的微软`Windows`中不工作。 `wx.DirDialog`类的`path`, `message`, 和`style`属性都有相应的`get`*和`set`*方法。你可以使用`GetPath()`方法来在对话框被调用后获取用户的选择。这个对话框也有一个便利的函数: ``` wx.DirSelector(message=wx.DirSelectorPromptStr, default_path="", style=0, pos=wxDefaultPosition, parent=None) ``` 所有的参数和前面的构造函数相同。如果`OK`被按下,则该函数返回所选择的字符串形式的目录名,如果按下`Cancel`,则返回空字符串。 ### 如何使用字体选择对话框? 在`wxPython`中,字体选择对话框与文件对话框是不同的,因为它使用了一个单独的帮助类来管理它所呈现的信息。图9.8显示了微软`Windows`版的字体对话框。 例9.8 显示了产生图9.8的代码,并且与前面的对话框例子看起来也有些不同。 **例9.8** **字体对话框** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() dialog = wx.FontDialog(None, wx.FontData()) if dialog.ShowModal() == wx.ID_OK: data = dialog.GetFontData() font = data.GetChosenFont() colour = data.GetColour() print 'You selected: "%s", %d points\n' % ( font.GetFaceName(), font.GetPointSize()) dialog.Destroy() ``` **图9.8** ![](https://box.kancloud.cn/2016-08-21_57b9960e16e87.gif) `wx.FontDialog` 的构造函数比前面的那些简单的多: `wx.FontDialog(parent`, `data)` 你不能为该对话框设置一个消息或标题,并且被通常作为样式标记传递的信息被包含在`data`参数中,该参数是类`wx.FontData`。`wx.FontData`类自己只有一个有用的方法:`GetFontData()`,该方法返回字体数据的实例。 `wx.FontData`的实例使你能够设置管理字体对话框显示的值,并且也能够容纳用户输入的信息。例如,在例9.8中的代码调用了`wx.FontData`实例的两个`get`*方法来确定所选字体的细节。`wx.FontData`的构造函数没有参数——所有的属性必须通过使用表9.4中的方法来设置。 **表9.4** **`wx.FontData`的方法** | | | | --- | --- | | `GetAllowSymbols()`,`SetAllowSymbols(allowSymbols)` | 决定是否在对话框中仅显示符号字体(如`dingbats`)。参数是布尔值。只在`Windows`中有意义。该属性的初始值是`True`。 | | `GetChosenFont()`,`SetChosenFont(font)` | 以`wx.Font`对象的方式返回用户所选的字体。如果用户选择了取消,那么返回`None`。`wx.Font`类将在第12章作更详细的讨论。 | | `GetColour()`,`SetColour(colour)` | 返回在对话框的颜色选择部分所选的颜色。`set`*方法使你可以预先设定默认值。`get`*方法返回一个`wx.Colour`实例。`set`*方法中的`colour`只能是一个`wx.Colour`或一个颜色的字符串名。该属性的初始值是`black`。 | | `GetEnableEffects()`,`EnableEffects(enable)` | 在该对话框的`Windows`版本中,该属性控制是否显示字体的所选颜色、中间是否有直线通过、是否带下划线等特性。 | | `GetInitialFont()`,`SetInitialFont(font)` | 返回对话框初值的字体值(即当前所用的字体)。这个属性可以在对话框显示之前通过应用程序显式的来设置。它的初始值是`None`。 | | `SetRange(min`, `max)` | 设置字体尺寸(磅)的有效范围。仅用于微软的`Windows`系统。最初值是0~0,意味没有范围的限制。 | ||`GetShowHelp()`,`SetShowHelp()`||如果为`True`,那么该对话框的微软`Windows`版本将显示一个帮助按钮。初始值为`False`||。 有一个使用字体对话框的便利的函数,它回避了`wx.FontData`类: ``` wx.GetFontFromUser(parent, fontInit) ``` `fontInit`参数是`wx.Font`的一个实例,它用作对话框的初始值。该函数的返回值是一个`wx.Font`实例。如用户通过`OK`关闭了对话框,则方法`wx.Font.Ok()`返回`True`,否则返回`False`。 ### 如何使用颜色对话框? 颜色对话框类似于字体对话框,因为它使用了一个外部的数据类来管理它的信息。图9.9显示了这类对话框的微软版本。 例9.9显示了生成该对话框的代码,它几乎与前面的字体对话框相同。 **例9.9** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() dialog = wx.ColourDialog(None) dialog.GetColourData().SetChooseFull(True) if dialog.ShowModal() == wx.ID_OK: data = dialog.GetColourData() print 'You selected: %s\n' % str(data.GetColour().Get()) dialog.Destroy() ``` **图9.9** ![](https://box.kancloud.cn/2016-08-21_57b9960e2b3bd.gif) 用于颜色选择器的`wxPython`的类是`wx.ColourDialog`。它的构造函数很简单,没有太多的参数: `wx.ColourDialog(parent`, `data`=`None)` `data`参数是类`wx.ColourData`的实例,它比相应字体的更简单。它只包含默认的没有参数的构造函数和后面的三个属性: 1、`GetChooseFull`/`SetChooseFull(flag)`:仅在微软`Windows`下工作。当设置后,将显示完整的对话框,包括自定义颜色选择器。如果不设置,则自定义颜色选择器不被显示。 2、`GetColour`/`SetColour(colour)`:当图表被关闭后,调用`get`*来看用户的选择。最初它被设置为`black`。如果在对话框显示之前设置了它,那么对话框最初显示为该颜色。 3、`GetCustomColour(i)`/`SetCustomColour(i`, `colour)`:根据自定义的颜色数组中的索引i来返回或设置元素。i位于[0,15]之间。初始时,所有的自定义颜色都是白色。 一个回避了`wx.ColorData`的使用颜色对话框的便利函数是: `wx.GetColourFromUser(parent`, `colInit)` `colInit`是`wx.Colour`的一个实例,并且当对话框显示时它是对话框的初始的值。函数的返回值也是一个`wx.Colour`的实例。如果用户通过`OK`关闭了对话框,那么方法`wx.Colour.OK()`返回`True`。如果用户通过`Cancel`关闭了对话框,那么方法`wx.Colour.OK()`返回`False`。 ### 如何使用户能够浏览图像? 如果你在你的程序中做图形处理,那么在他们浏览文件树时使用缩略图是有帮助的。用于该目的的`wxPython`对话框被称为`wx.lib.imagebrowser.ImageDialog`。图9.10显示了一个例子。 例9.10显示了用于该图像浏览对话框的简单的代码。 **例9.10** **创建一个图像浏览对话框** ``` import wx import wx.lib.imagebrowser as imagebrowser if __name__ == "__main__": app = wx.PySimpleApp() dialog = imagebrowser.ImageDialog(None) if dialog.ShowModal() == wx.ID_OK: print "You Selected File: " + dialog.GetFile() dialog.Destroy() ``` **图9.10** ![](https://box.kancloud.cn/2016-08-21_57b9960e43b23.gif) `wx.lib.imagebrowser.ImageDialog`类是十分简单的,并有相对较少的选项供程序员去设置。要改变该对话框的行为的话,请查阅改变显示的文件类型的`Python`源码。类的构造函数要求两个参数。 `ImageDialog(parent`, `set_dir`=`None)` `set_dir`参数是对话框显示时所在的目录。如果不设置,那么使用应用程序当前的工作目录。在对话框被关闭后,`GetFile()`返回所选文件的完整路径字符串,`GetDirectory()`只返回目录部分。 ## 创建向导 向导是一系列被链接在一起的简单对话框,它使得用户一步步地跟随它们。通常它们被用于指导用户的安装或一个复杂的配置过程。图9.11显示了一个向导示例。 **图9.11** ![](https://box.kancloud.cn/2016-08-21_57b996438cc60.gif) 在`wxPython`中,一个向导是一系列的页面,它由类`wx.wizard.Wizard`的一个实例控制。向导实例管理用户的页面切换事件。这些页面自身也是类`wx.wizard.WizardPageSimple`或`wx.wizard.WizardPage`的实例。这两种类的实例,它们只不过是附加了必要的管理页面链接逻辑的`wx.Panel`的实例。已证明这两个实例之间的区别仅当用户按下`Next`按钮时。`wx.wizard.WizardPage`的实例使你能够动态地决定浏览哪页,而`wx.wizard.WizardPageSimple`的实例要求向导被显示前,顺序被预先设置。例9.11显示了产生图9.11的代码。 **例9.11** **创建一个简单的静态向导** ``` import wx import wx.wizard class TitledPage(wx.wizard.WizardPageSimple):#1 创建页面样板 def __init__(self, parent, title): wx.wizard.WizardPageSimple.__init__(self, parent) self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) titleText = wx.StaticText(self, -1, title) titleText.SetFont( wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) self.sizer.Add(titleText, 0, wx.ALIGN_CENTRE | wx.ALL, 5) self.sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 5) if __name__ == "__main__": app = wx.PySimpleApp() wizard = wx.wizard.Wizard(None, -1, "Simple Wizard")# 创建向导实例 # 创建向导页面 page1 = TitledPage(wizard, "Page 1") page2 = TitledPage(wizard, "Page 2") page3 = TitledPage(wizard, "Page 3") page4 = TitledPage(wizard, "Page 4") page1.sizer.Add(wx.StaticText(page1, -1, "Testing the wizard")) page4.sizer.Add(wx.StaticText(page4, -1, "This is the last page.")) #2 创建页面链接 wx.wizard.WizardPageSimple_Chain(page1, page2) wx.wizard.WizardPageSimple_Chain(page2, page3) wx.wizard.WizardPageSimple_Chain(page3, page4) wizard.FitToPage(page1)#3 调整向导的尺寸 if wizard.RunWizard(page1):#4 运行向导 print "Success" wizard.Destroy() ``` **#1** 为了便于移植的目的,我们创建了一个简单的小的页面,它包含了一个静态文本标题。通常情况下,这儿还包含一些表单元素,可能还有一些要用户输入的数据。 **#2** `wx.wizard.WizardPageSimple_Chain()`函数是一个便利的方法,它以两个页面为参数相互地调用它们的`SetNext()`和`SetPrev()`方法。 **#3** `FitToSize()`根据页面参数及该页链条上的所有页面调整向导的大小。该方法只能在页面链接被创建后调用。 **#4** 该方法的参数是向导开始时的页面。向导在它到达一个没有下一页的页面时知道去关闭。如果用户浏览了整个向导并通过按下`Finish`按钮退出的话,`RunWizard()`方法返回`True`。 创建`wx.wizard.Wizard`实例是使用向导的第一步。其构造函数如下: ``` wx.wizard.Wizard(parent, id=-1, title=wx.EmptyString, bitmap=wx.NullBitmap, pos=wx.DefaultPosition) ``` 在这里的`parent`, `id`, `title`, `pos`和`wx.Panel`的意义相同。如果设置了`bitmap`参数,那么该参数将显示在每一页上。这儿只有一个样式标记:`wx.wizard.WIZARD_EX_HELPBUTTON`,它显示一个帮助按钮。这是一个扩展的标记,需要使用第8章所说的两步创建过程。 通常,你将调用例9.11的#3中所示的`FitToSize()`来管理窗口的尺寸,但是你也可以通过调用带有一个元组或`wx.Size`实例的`SetPageSize()`来设置一个最小的尺寸。`GetPageSize()`方法返回当前的尺寸,在这两种情况下,该尺寸仅用于对话框中的单个的页面部分,而作为一个整体的对话框将更大一些。 你可以管理该类中的页面。方法`GetCurrentPage()`返回当前被显示的页面,如果该向导当前没有被显示,该方法返回`None`。你可以通过调用`HasNextPage()`和`HasPrevPage()`来确定当前页是否有下一页或上一页。 使用`RunWizard()`来运行该向导,如例9.11中#4的说明。 向导产生的命令事件如表9.5所示,你可以捕获这些事件,以便作专门的处理。这些事件对象属于类`wx.wizard.WizardEvent`,它们提供了两个可用的方法。`GetPage()`返回`wx.WizardPage`的实例,该实例在向导事件产生时是有效,而非作为事件结果被显示的实例。如果事件是前进一页,那么`GetDirection()`返回`True`,如果事件是后退一页,那么`GetDirection()`返回`False`。 **表9.5 `wx.wizard.WizardDialog`的事件** | | | | --- | --- | | `EVT_WIZARD_CANCEL` | 当用户按下`Cancel`按钮时产生。该事件可以使用`Veto()`来否决,这种情况下,对话框将不会消失。 | | `EVT_WIZARD_FINISHED` | 当用户按下`Finish`按钮时产生。 | | `EVT_WIZARD_HELP` | 当用户按下`Help`按钮时产生。 | | `EVT_WIZARD_PAGE_CHANGED` | 在页面被切换后产生。 | | `EVT_WIZARD_PAGE_CHANGING` | 当用户已请求了一个页面切换时产生,这时页面还没有发生切换。这个事件可以被否决(例如,如果页面上有一个必须被填写的字段)。 | `wx.wizard.WizardPageSimple`类被当作一个面板一样。它的构造函数使你可以设置上一页和下一页,如下所示: `wx.wizard.WizardPageSimple(parent`=`None`, `prev`=`None`, `next`=`None)` 如果你想在构造器中设置它们,你可以使用`SetPrev()`和`SetNext()`方法。如果那样太麻烦,你可以使用`wx.wizard.WizardPageSimple_Chain()`,它设置两页间的链接关系。 向导页的复杂版:`wx.wizard.WizardPage`,稍微不同。它没有显式地设置前一页和下一页,而是使你能够使用更复杂的逻辑去定义下一步到哪儿。它的构造函数如下: ``` wx.WizardPage(parent, bitmap=wx.NullBitmap, resource=None) ``` 如果`bitmap`参数被设置了,那么该参数覆盖父向导中所设置的位图。`resource`参数从一个`wxPython`资源装载页面。要处理页面逻辑,就要覆盖`GetPrev()`和`GetNext()`方法来返回你想要向导下一步的位置。该类的一个典型的用法是根据用户对当前页的响应动态地决定接下来的页面。 ## 显示启动提示 许多应用程序都使用启动提示来作为一种向用户介绍该程序的特性等信息的方法。在`wxPython`中有一个非常简单的机制用来显示启动提示。图9.12显示了一个提示窗口的示例。 **图9.12** ![](https://box.kancloud.cn/2016-08-21_57b99643a27f2.gif) 例9.12显示了相关代码 **例9.12** ``` import wx if __name__ == "__main__": app = wx.PySimpleApp() provider = wx.CreateFileTipProvider("tips.txt", 0) wx.ShowTip(None, provider, True) ``` 有两个便利的函数用来管理启动提示。第一个如下创建一个`wx.TipProvider`: ``` wx.CreateFileTipProvider(filename, currentTip) ``` `filename`是包含提示字符串的文件的名字。`currentTip`是该文件中用于一开始显示的提示字符的索引,该文件中的第一个提示字符串的索引是0。 提示文件是一个简单的文本文件,其中的每一行是一个不同的提示。空白行被忽略,以#开始的行被当作注释并也被忽略。下面是上例所使用的提示文件中的内容: `You` `can` `do` `startup` `tips` `very` `easily.` `Feel` `the` `force`, `Luke.` 提示的提供者(`provider`)是类`wx.PyTipProvider`的一个实例。如果你需要更细化的功能,你可以创建你自己的`wx.TipProvider`的子类并覆盖`GetTip()`函数。 显示提示的函数是`wx.ShowTip():` ``` wx.ShowTip(parent, tipProvider, showAtStartup) ``` `parent`是父窗口,`tipProvider`通常创建自`wx.CreateFileTipProvider`。`showAtStartup`控制启动提示显示时,复选框是否被选择。该函数的返回值是复选框的状态值,以便你使用该值来决定你的应用程序下次启动时是否显示启动提示。 ## 使用验证器(validator)来管理对话框中的数据 验证器是一个特殊的`wxPython`对象,它简化了对话框中的数据管理。当我们在第三章中讨论事件时,我们简要的提及到如果一个窗口部件有一个验证器,那么该验证器能够被事件系统自动调用。我们已经见过了几个`wxPython`窗口部件类的构造函数中将验证器作为参数,但是我们还没有讨论它们。 验证器有三个不相关的功能: 1、在对话框关闭前验证控件中的数据 2、自动与对话框传递数据 3、验证用户键入的数据 === 如何使用验证器来确保正确的?荩?=== 验证器对象是`wx.Validator`的子类。父类是抽象的,不能直接使用。尽管在C++ `wxWidget`集中有一对预定义的验证器类,但是在`wxPython`中,你需要定义你自己的验证器类。正如我们在别处所见的,你的`Python`类需要继承自`Python`特定的子类:`wx.PyValidator`,并且能够覆盖该父类的所有方法。一个自定义的验证器子类必须覆盖方法`Clone()`,该方法应该返回验证器的相同的副本。 一个验证器被关联到你的系统中的一个特定的窗口部件。这可以用两种方法之一来实现。第一种方法,如果窗口部件许可的话,验证器可以被作为一个参数传递给该窗口部件的构造函数。如果该窗口部件的构造函数没有一个验证器参数,你仍然可以通过创建一个验证器实例并调用该窗口部件的`SetValidator(validator)`方法来关联一个验证器。 要验证控件中的数据,你可以先在你的验证器子类中覆盖`Validate(parent)`方法。`parent`参数是验证器的窗口部件的父窗口(对话框或面板)。如果必要,可以使用这个来从对话框中其它窗口部件得到数据,或者你可以完全忽略该参数。你可以使用`self.GetWindow()`来得到正在被验证的窗口部件的一个引用。你的`Validate(parent)`方法的返回值是一个布尔值。`True`值表示验证器的窗口部件中的数据已验证了。`False`表示有问题。你可以根据`Validate()`方法来使用`x.MessageBox()`去显示一个警告,但是你不应该做其它的任何可以在`wxPython`应用程序中引发事件的事情。 `Validate()`的返回值是很重要的。它在你使用`OK`按钮(该按钮使用`wx.ID_OK` `ID`)企图关闭一个对话框时发挥作用。作为对`OK`按钮敲击处理的一部分,`wxPython`调用对话框中有验证器的窗口部件的`Validate()`函数。如果任一验证器返回`False`,那么对话框不将关闭。例9.13显示了一个带有验证器的示例对话框,它检查所有文本控件中有无数据。 **例9.13** **检查所有文本控件有无数据的验证器** ``` import wx about_txt = """\ The validator used in this example will ensure that the text controls are not empty when you press the Ok button, and will not let you leave if any of the Validations fail.""" class NotEmptyValidator(wx.PyValidator):# 创建验证器子类 def __init__(self): wx.PyValidator.__init__(self) def Clone(self): """ Note that every validator must implement the Clone() method. """ return NotEmptyValidator() def Validate(self, win):#1 使用验证器方法 textCtrl = self.GetWindow() text = textCtrl.GetValue() if len(text) == 0: wx.MessageBox("This field must contain some text!", "Error") textCtrl.SetBackgroundColour("pink") textCtrl.SetFocus() textCtrl.Refresh() return False else: textCtrl.SetBackgroundColour( wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) textCtrl.Refresh() return True def TransferToWindow(self): return True def TransferFromWindow(self): return True class MyDialog(wx.Dialog): def __init__(self): wx.Dialog.__init__(self, None, -1, "Validators: validating") # Create the text controls about = wx.StaticText(self, -1, about_txt) name_l = wx.StaticText(self, -1, "Name:") email_l = wx.StaticText(self, -1, "Email:") phone_l = wx.StaticText(self, -1, "Phone:") #2 使用验证器 name_t = wx.TextCtrl(self, validator=NotEmptyValidator()) email_t = wx.TextCtrl(self, validator=NotEmptyValidator()) phone_t = wx.TextCtrl(self, validator=NotEmptyValidator()) # Use standard button IDs okay = wx.Button(self, wx.ID_OK) okay.SetDefault() cancel = wx.Button(self, wx.ID_CANCEL) # Layout with sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(about, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(3, 2, 5, 5) fgs.Add(name_l, 0, wx.ALIGN_RIGHT) fgs.Add(name_t, 0, wx.EXPAND) fgs.Add(email_l, 0, wx.ALIGN_RIGHT) fgs.Add(email_t, 0, wx.EXPAND) fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) fgs.Add(phone_t, 0, wx.EXPAND) fgs.AddGrowableCol(1) sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) btns = wx.StdDialogButtonSizer() btns.AddButton(okay) btns.AddButton(cancel) btns.Realize() sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) app = wx.PySimpleApp() dlg = MyDialog() dlg.ShowModal() dlg.Destroy() app.MainLoop() ``` **#1** 该方法测试基本的控件有无数据。如果没有,相应的控件的背景色变为粉红色。 **#2** 这几行,对话框中的每个文本控件都关联一个验证器。 图9.13显示了一个文本域为空就企图关闭的对话框。 **图9.13** ![](https://box.kancloud.cn/2016-08-21_57b99643b78cc.gif) 明确地告诉对话框去核对验证器的代码没有出现在示例中,因为它是`wxPython`事件系统的一部分。对话框与框架之间的另一区别是对话框有内建的验证器行为,而框架没有。如果你喜欢将验证器用于不在对话框内的控件,那么调用父窗口的`Validate()`方法。如果父窗口已设置了`wx.WS_EX_VALIDATE_RECURSIVELY`额外样式,那么所有的子窗口的`Validate()`方法也被调用。如果任一验证失败,那么`Validate`返回`False`。接下来,我们将讨论如何将验证器用于数据传输。 ### 如何使用验证器传递数据? 验证器的第二个重要的功能是,当对话框打开时,它自动将数据传送给对话框显示,当该对话框关闭时,自动从对话框把数据传输到一个外部资源。图9.14显示了一个示例对话框。 **图9.14** ![](https://box.kancloud.cn/2016-08-21_57b99643cbac3.gif) 要实现这个,你必须在你的验证器子类中覆盖两个方法。方法`TransferToWindow()`在对话框打开时自动被调用。你必须使用这个方法把数据放入有验证器的窗口部件。`TransferFromWindow()`方法在使用`OK`按钮关闭对话框窗口时且数据已被验证后被自动调用。你必须使用这个方法来将数据从窗口部件移动给其它的资源。 数据传输必须发生的事实意味着验证器必须对一个外部的数据对象有一些了解,如例9.14所示。在这个例子中,每个验证器都使用一个全局数据字典的引用和一个字典内的对于相关控件重要的关键字来被初始化。 当对话框打开时,`TransferToWindow()`方法从字典中根据关键字读取数据并把数据放入文本域。当对话框关闭时,`TransferFromWindow()`方法反向处理并把数据写入字典。这个例子的对话框显示你传输的数据。 **例9.14** **一个数据传输验证器** ``` import wx import pprint about_txt = """\ The validator used in this example shows how the validator can be used to transfer data to and from each text control automatically when the dialog is shown and dismissed.""" class DataXferValidator(wx.PyValidator):# 声明验证器 def __init__(self, data, key): wx.PyValidator.__init__(self) self.data = data self.key = key def Clone(self): """ Note that every validator must implement the Clone() method. """ return DataXferValidator(self.data, self.key) def Validate(self, win):# 没有验证数据 return True def TransferToWindow(self):# 对话框打开时被调用 textCtrl = self.GetWindow() textCtrl.SetValue(self.data.get(self.key, "")) return True def TransferFromWindow(self):# 对话框关闭时被调用 textCtrl = self.GetWindow() self.data[self.key] = textCtrl.GetValue() return True class MyDialog(wx.Dialog): def __init__(self, data): wx.Dialog.__init__(self, None, -1, "Validators: data transfer") # Create the text controls about = wx.StaticText(self, -1, about_txt) name_l = wx.StaticText(self, -1, "Name:") email_l = wx.StaticText(self, -1, "Email:") phone_l = wx.StaticText(self, -1, "Phone:") # 将验证器与窗口部件相关联 name_t = wx.TextCtrl(self, validator=DataXferValidator(data, "name")) email_t = wx.TextCtrl(self, validator=DataXferValidator(data, "email")) phone_t = wx.TextCtrl(self, validator=DataXferValidator(data, "phone")) # Use standard button IDs okay = wx.Button(self, wx.ID_OK) okay.SetDefault() cancel = wx.Button(self, wx.ID_CANCEL) # Layout with sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(about, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(3, 2, 5, 5) fgs.Add(name_l, 0, wx.ALIGN_RIGHT) fgs.Add(name_t, 0, wx.EXPAND) fgs.Add(email_l, 0, wx.ALIGN_RIGHT) fgs.Add(email_t, 0, wx.EXPAND) fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) fgs.Add(phone_t, 0, wx.EXPAND) fgs.AddGrowableCol(1) sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) btns = wx.StdDialogButtonSizer() btns.AddButton(okay) btns.AddButton(cancel) btns.Realize() sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) app = wx.PySimpleApp() data = { "name" : "Jordyn Dunn" } dlg = MyDialog(data) dlg.ShowModal() dlg.Destroy() wx.MessageBox("You entered these values:\n\n" + pprint.pformat(data)) app.MainLoop() ``` 对话框中验证器的传输数据方法的调用自动发生。要在非对话框窗口中使用验证器来传输数据,必须调用父窗口部件的`TransDataFromWindow()`和`TransferDataToWindow()`方法。如果该窗口设置了`wx.WS_EX_VALIDATE_RECURSIVELY`额外样式,那么在所有的子窗口部件上也将调用该传输函数。 ### 如何在数据被键入时验证数据? 在数据被传给窗口部件之前,你也可使用验证器来在用户输入数据时验证所输入的数据。这是非常有用的,因为它可以防止将得到的坏的数据传入你的应用程序。图9.12显示了一个例子,其中对话框的文本阐明了该思想。 **图9.15** ![](https://box.kancloud.cn/2016-08-21_57b99643df4a3.gif) 验证数据的方法的自动化成份少于其它的机制。你必须显式绑定验证器的窗口部件的字符事件给一个函数,如下所示: `self.Bind(wx.EVT_CHAR`, `self.OnChar)` 该窗口部件假设事件源属于验证器。例9.15显示了这个绑定。 **例9.15 实时验证** ``` import wx import string about_txt = """\ The validator used in this example will validate the input on the fly instead of waiting until the okay button is pressed. The first field will not allow digits to be typed, the second will allow anything and the third will not allow alphabetic characters to be entered. """ class CharValidator(wx.PyValidator): def __init__(self, flag): wx.PyValidator.__init__(self) self.flag = flag self.Bind(wx.EVT_CHAR, self.OnChar)# 绑定字符事件 def Clone(self): """ Note that every validator must implement the Clone() method. """ return CharValidator(self.flag) def Validate(self, win): return True def TransferToWindow(self): return True def TransferFromWindow(self): return True def OnChar(self, evt):# 数据处理 key = chr(evt.GetKeyCode()) if self.flag == "no-alpha" and key in string.letters: return if self.flag == "no-digit" and key in string.digits: return evt.Skip() class MyDialog(wx.Dialog): def __init__(self): wx.Dialog.__init__(self, None, -1, "Validators: behavior modification") # Create the text controls about = wx.StaticText(self, -1, about_txt) name_l = wx.StaticText(self, -1, "Name:") email_l = wx.StaticText(self, -1, "Email:") phone_l = wx.StaticText(self, -1, "Phone:") # 绑定验证器 name_t = wx.TextCtrl(self, validator=CharValidator("no-digit")) email_t = wx.TextCtrl(self, validator=CharValidator("any")) phone_t = wx.TextCtrl(self, validator=CharValidator("no-alpha")) # Use standard button IDs okay = wx.Button(self, wx.ID_OK) okay.SetDefault() cancel = wx.Button(self, wx.ID_CANCEL) # Layout with sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(about, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(3, 2, 5, 5) fgs.Add(name_l, 0, wx.ALIGN_RIGHT) fgs.Add(name_t, 0, wx.EXPAND) fgs.Add(email_l, 0, wx.ALIGN_RIGHT) fgs.Add(email_t, 0, wx.EXPAND) fgs.Add(phone_l, 0, wx.ALIGN_RIGHT) fgs.Add(phone_t, 0, wx.EXPAND) fgs.AddGrowableCol(1) sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) btns = wx.StdDialogButtonSizer() btns.AddButton(okay) btns.AddButton(cancel) btns.Realize() sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) app = wx.PySimpleApp() dlg = MyDialog() dlg.ShowModal() dlg.Destroy() app.MainLoop() ``` 由于`OnChar()`方法是在一个验证器中,所以它在窗口部件响应字符事件之间被调用。该方法让你可以通过使用`Skip()`来将事件传送给窗口部件。你必须调用`Skip()`,否则验证器将妨碍正常的事件处理。验证器执行一个测试来查看用于该控件的字符是否有效。如果该字符无效,那么`Skip()`不被调用,并且事件处理停止。如果有必须的话,除了`wx.EVT_CHAR`之外的其它事件也可以被绑定,并在窗口部件响应之前验证器处理那些事件。 对于处理你`wxPython`应用程序中的数据,验证器是一个强大且灵活的机制。适当地使用它们,可以让你的应用程序的开发和维护更加的顺畅。 ## 本章小结 1、对话框被用于在有一套特殊的信息需要被获取的情况下,处理与用户的交互,这种交互通常很快被完成。在`wxPython`中,你可以使用通用的`wx.Dialog`类来创建你自己的对话框,或者你也可以使用预定义的对话框。大多数情况下,通常被使用的对话框都有相应的便利函数,使得这种对话框的使用更容易。 2、对话框可以显示为模式对话框,这意味在对话框可见时,该应用程序中的用户所有的其它输入将被阻塞。模式对话框通过使用`ShowModal()`方法来被调用,它的返回值依据用户所按的按钮而定(`OK`或`Cancel`)。关闭模式对话框并不会销毁它,该对话框的实例可以被再用。 3、在`wxPython`中有三个通用的简单对话框。`wx.MessageDialog`显示一个消息对话框。`wx.TextEntryDialog`使用户能够键入文本,`wx.SingleChoiceDialog`给用户一个基于列表项的选择。 4、当正在执行一个长时间的后台的任务时,你可以使用`wx.ProgressDialog`来给用户显示进度信息。用户可以通过`wx.FileDialog`使用标准文件对话框来选择一个文件。可以使用`wx.DirDialog`来创建一个标准目录树,它使得用户可以选择一个目录。 5、你可以使用`wx.FontDialog`和`wx.ColorDialog`来访问标准的字体选择器和颜色选择器。在这两种情况中,对话框的行为和用户的响应是由一个单独的数据类来控制的。 6、要浏览缩略图,可以使用`wxPython`特定的类`wx.lib.imagebrowser.ImageDialog`。这个类使用户能够通过文件系统并选择一个图像。 7、你可以通过使用`wx.wizard.Wizard`创建一个向导来将一组相关的对话框表单链接起来。对话框表单是`wx.wizard.WizardSimplePage`或`wx.wizard.WizardPage`的实例。两者的区别是,`wx.wizard.WizardSimplePage`的页到页的路线需要在向导被显示之前就安排好,而`wx.wizard.WizardPage`使你能够在运行时管理页到页的路线的逻辑。 8、使用`wx.CreateFileTipProvider`和`wx.ShowTip`函数可以很容易地显示启动提示。 9、验证器是很有用的对象,如果输入的数据不正确的话,它可以自动阻止对话框的关闭。他们也可以在一个显示的对话框和一个外部的对象之间传输数据,并且能够实时地验证数据的输入。