我们的温度转换 GUI 上有一些按钮,用来完成转换。很多程序还会提供一个菜单来完成某些功能。有时这些工作也可以通过点击一个按钮来完成,那为什么要采用两种不同方法完成同样的事情呢?
是这样的,有些用户更习惯使用菜单,而不喜欢点击按钮。另外,菜单还可以通过键盘来操作,有些人发现,与把手从键盘拿开再使用鼠标相比,直接使用菜单速度会更快。
下面来增加一些菜单项,为用户提供另外一种完成温度转换的途径。
PythonCard 包含一个菜单编辑器。我们的程序已经有一个非常简单的菜单(只有 File Exit)。下面使用菜单编辑器为我们的 GUI 菜单系统增加菜单项。
如果关闭了资源编辑器,现在再启动起来,并打开 TempGui.rsrc.py。选择 Edit(编辑) Menu Editor(菜单编辑器)。你会看到如右图所示的内容。
可以看到 File 菜单,它下面还有一个 Exit 菜单项。我们打算增加一个名为 Convert 的菜单,然后再增加两个菜单项 Celsius to Fahrenheit 和 Fahrenheit to Celsius。
增加菜单
要增加新的菜单,需要点击 New Menu(新建菜单)按钮。你会看到菜单编辑器中已经为我们填入了名字和标签,不过我们想在这里放入自己的值。把 Name 改为 menuConvert,并把 Label 改为 &Convert。现在菜单编辑器应该像右图所示的这样。
取决于点击 New Menu(新建菜单)按钮时当时选中的菜单项, Convert 菜单可能位于左侧列表的最上面、中间或者最下面。我们想让它位于最下面。点击列表中的 &Convert 项,然后点击 Down(向下)按钮,直到把 &Convert 菜单移到列表的最下面,如右图所示。
这个奇怪的符号是什么
为什么我们要在 Convert 的 C 前面加上一个 & 符号?这样做是为了告诉菜单编辑器这个菜单使用哪个热键。还记得吧?我们刚刚讲过,可以用键盘来控制菜单。其实,利用热键就可以做到这一点。
要激活一个菜单,可以保持按下 ALT 键,同时按下键盘上的一个字母。你按下的字母就是菜单标签中有下划线的那个字母。例如,要进入 File 菜单,可以使用 ALT-F。&Convert 中 C 前面的 & 符号告诉菜单编辑器:我们想将 C 用作 Convert 菜单的热键。这说明 PythonCard 会在程序运行时自动为它显示一个下划线。
在 Mac OS X 和 Linux 中,热键的做法稍有不同。这里我不打算深入讨论所有细节,不过如果你使用的是其中某个操作系统,可能对这个操作系统中的热键如何工作已经很熟悉了。如果还不熟悉,可以找一个了解的人问一问。
增加菜单项
现在来增加菜单项。在菜单编辑器的左侧窗口中,点击你刚增加的 &Convert 菜单。然后点击 New Menu Item(新建菜单项)按钮。这会在 Convert 菜单下面增加一个新的菜单项。同样的,菜单编辑器会填入一些默认值,不过我们想用自己的值。把 Name 改为 menuConvertCtoF,并把 Label 改为 &Celsius to Fahrenheit。
再增加一项,名为 menuConvertFtoC,标签设置为 &Fahrenheit to Celsius。现在菜单编辑器应该像右图显示的这样。
嗯,Carter,要使用菜单项的热键,需要使用 Alt 键(如果是 Windows 系统)。前面说过,按下 Alt-F 就会进入 File 菜单。一旦进入 File 菜单,就可以使用 File 菜单中菜单项的热键,在这里 Exit 的热键是 X。
我们现在有了一个新的菜单,如果运行程序,应该能点击 Convert 菜单,并看到出现两个菜单项。甚至可以点击这些菜单项,不过现在什么也不会发生。这是因为我们还没有为它们创建事件处理器。
菜单事件处理器
现在需要在代码中增加事件处理器。选择一个菜单项时发生的事件是 select。就像按钮事件处理器一样,这些事件处理器以 on_ 开头,后面是事件名(也就是菜单项名),然后是事件类型,在这里就是 _select。所以事件处理器的代码应该从下面这行代码开始:
def on_menuConvertCtoF_select(self, event):
下面要增加转换代码。这与代码清单 20-2 中 btnCtoF 事件处理器中所用的代码相同,所以可以直接复制这段代码。
对另一个菜单项做同样的处理。事件处理器应该从下面这行代码开始:
def on_menuConvertFtoC_select(self, event):
它应当包含 btnFtoC 事件处理器中同样的代码。最后完成的代码应当类似于代码清单 20-3。
代码清单 20-3 增加菜单事件处理器
from PythonCard import modelclass MainWindow(model.Background): def on_btnCtoF_mouseClick(self, event): cel = float(self.components.tfCel.text) fahr = cel * 9.0 / 5 + 32 print /'cel = /', cel, /' fahr = /', fahr self.components.spinFahr.value = int(fahr) def on_btnFtoC_mouseClick(self, event): fahr = self.components.spinFahr.value cel = (fahr - 32) * 5.0 / 9 cel = /'%.2f/' % cel self.components.tfCel.text = cel def on_menuConvertCtoF_select(self, event): cel = float(self.components.tfCel.text) fahr = cel * 9.0 / 5 + 32 print /'cel = /', cel, /' fahr = /', fahr self.components.spinFahr.value = int(fahr) def on_menuConvertFtoC_select(self, event): fahr = self.components.spinFahr.value cel = (fahr - 32) * 5.0 / 9 cel = /'%.2f/' % cel self.components.tfCel.text = celapp = model.Application(MainWindow)app.MainLoop
试着运行这个程序,确保它能正常工作。
整理
尽管这个代码能很好地工作,但还是有些地方困扰着我。我们在两个地方都使用了相同的两个代码块。我们把按钮事件处理器中的代码复制到菜单事件处理器,因为菜单项做的工作与两个按钮完全相同。对于这样一个小程序来说,这种做法的问题不大,不过最好还是对这个程序重新组织一下。
要改进这个程序,一种方法是把完成转换的代码块写成函数。然后从各个事件处理器调用这个转换代码。代码清单 20-4 显示了采用这种做法的代码。
代码清单 20-4 整理代码
from PythonCard import modeldef CtoF(self): cel = float(self.components.tfCel.text) fahr = cel * 9.0 / 5 + 32 print /'cel = /', cel, /' fahr = /', fahr self.components.spinFahr.value = int(fahr)def FtoC(self): fahr = self.components.spinFahr.value cel = (fahr - 32) * 5.0 / 9 cel = /'%.2f/' % cel self.components.tfCel.text = celclass MainWindow(model.Background): def on_btnCtoF_mouseClick(self, event): CtoF(self) def on_btnFtoC_mouseClick(self, event): FtoC(self) def on_menuConvertCtoF_select(self, event): CtoF(self) def on_menuConvertFtoC_select(self, event): FtoC(self)app = model.Application(MainWindow)app.MainLoop
这比前面好多了,不过还有一种更好的方法来进行整理。每个 PythonCard 组件都有一个名为 command 的属性,可以利用这个属性为多个组件创建一个共同的事件处理器。例如,可以为 Celsius to Fahrenheit 按钮和 Convert Celsius to Fahrenheit 菜单项指定一个名为 cmdCtoF 的命令。点击这个按钮或者选择这个菜单项时都会运行这个命令。
为了做到这一点,进入资源编辑器,选择 btnCtoF 组件。滚动属性列表,直到看到 command 属性。把这个属性值从 None 改为 cmdCtoF。对另一个按钮做同样的处理,不过将命令命名为 cmdFtoC。然后启动菜单编辑器,选择 &Celsius to Fahrenheit 菜单项。你会注意到有一个对应 Command 的文本框,它现在是空的。在这个文本框中键入 cmdCtoF。对 &Fahrenheit to Celsius 菜单项做同样的处理,不过将命令命名为 cmdFtoC。
现在按钮和菜单项的 command 属性都已设置为 cmdCtoF。另一个按钮和菜单项的 command 属性都设置为 cmdFtoC。我们只需要改变事件处理器的名字。因为按钮和菜单项会共享一个事件处理器,所以总共只需要两个事件处理器,而不是 4 个。代码应当类似于代码清单 20-5。
代码清单 20-5 进一步整理代码
from PythonCard import modelclass MainWindow(model.Background): def on_cmdCtoF_command(self, event): Cel = float(self.components.tfCel.text) Fahr = Cel * 9.0 / 5 + 32 print /'cel = /', Cel, /' fahr = /', Fahr self.components.spinFahr.value = int(Fahr) def on_cmdFtoC_command(self, event): Fahr = self.components.spinFahr.value Cel = (Fahr - 32) * 5.0 / 9 Cel = /'%.2f/' % Cel self.components.tfCel.text = Celapp = model.Application(MainWindow)app.MainLoop
现在只有两个事件处理器,而且不再需要额外的函数。如果两个或多个组件要共享一个事件处理器(如果这些组件必须做同样的事情,例如一个按钮和一个菜单项),就可以利用 command 属性,这是一种很好的方法。
温度转换 GUI 就谈到这里。在第 22 章中,我们还将使用 PythonCard 建立一个新版本的 Hangman 游戏。
你学到了什么
在这一章,我们学到了以下内容。
PythonCard。
资源编辑器,用来建立 GUI 的布局。
构成 GUI 的组件——按钮、文本等等。
菜单编辑器。
菜单项和热键。
事件处理器——让组件具体做事情。
command 属性,可以用于共享事件处理器。
测试题
构成 GUI 的按钮、文本域等元素有哪 3 个名字?
要进入一个菜单,可以与 ALT 同时按下哪个字母?
PythonCard 资源文件的文件名末尾必须加上什么?
使用 PythonCard 的 GUI 中可以包含哪 5 种组件类型?
要让组件(如按钮)完成某项工作,它需要有一个_______________________________________。
菜单编辑器中使用哪个特殊字符来定义热键?
PythonCard 中微调控件(或微调框)的内容总是一个__________。
动手试一试
我们在第 1 章建立了一个基于文本的猜数程序,另外在第 6 章建立了这个程序的一个简单的 GUI 版本。试着使用 PythonCard 建立这个猜数程序的 GUI 版本。
前面出现微调框无法显示低于 0 的值的问题(Carter 在代码清单 20-2 中找出了这个 bug),你发现了吗?修改微调框属性来解决这个问题。确保对范围上下界都做修改,使微调框不仅能显示非常高的温度,也能显示非常低的温度。(也许你的用户除了想转换冥王星上的温度,还想转换水星和金星上的温度呢!)