你已经在第 3 章“动手试一试”部分中建立了第一个温度转换程序。第 5 章中,我们又为它增加了用户输入,这样一来,需要转换的温度就不必硬编码写在程序中了。在第 6 章中,我们使用了 EasyGui 来得到输入并且显示输出。现在我们要使用 PythonCard 来建立这个温度转换程序的一个图形化版本。
TempGUI 组件
我们的温度转换 GUI 相当简单,只需要提供以下内容:
输入温度的位置(摄氏度和华氏度);
完成温度转换的按钮;
向用户显示有关信息的一些标签。
只是为了好玩,下面对摄氏度和华氏度分别使用两种不同类型的输入部件。在实际程序中绝对不要这么做(这只会把人们搞糊涂),不过这里我们的目的是学习如何使用这些部件!
术语箱
部件(widget)是对不同类型组件(按钮、滚动条、下拉列表等)的另一种称呼,有时也称为控件(control)。
建立了 GUI 布局后,会得到类似右图这样的结果:
也许你自己就可以完成,因为资源编辑器非常友好,很容易使用。不过没准你会需要一些帮助,所以这里还是会对相应步骤做些解释。这样也可以确保我们对组件使用相同的名字,便于更好地理解后面的代码。
并不要求组件完全对齐,也不必完全按这样来摆放组件,只要大致相同就可以了。
创建新的 GUI
第一步是建立一个新的 PythonCard 工程。打开资源编辑器,它会打开一个新的工程。如果前面的第一个 GUI 还是打开的,现在需要先关闭资源编辑器,然后再次打开。
下面开始增加组件:摄氏度输入框是一个 TextField,华氏度输入框是一个 Spinner,各个温度输入框下面的标签都是 StaticText 组件,另外还有两个 Button 组件。建立这个 GUI 的步骤如下。
选择 Component Button。为按钮指定下面的属性。
name: btnCtoF
label: Celsius to Fahrenheit >>>
点击 OK。把这个按钮拖到窗口中间的某个位置。
选择 Component Button。为按钮指定下面的属性。
name: btnFtoC
label: <<< Fahrenheit to Celsius
点击 OK。拖动这个按钮,把它放在前一个按钮的下面。
选择 Component TextField。为这个文本域指定下面的属性。
- name: tfCel
保留这个文本域为空,点击 OK。把这个文本框向下拖一点,使它在 Celsius to Fahrenheit 按钮的左边。
选择 Components Spinner。为这个微调控件(有时也称为一个微调框)指定以下名字。
- name: spinFahr
点击 OK。把它向下拖一点,使它位于 Celsius to Fahrenheit 按钮的右边。
选择 Components StaticText。保留原来的名字不变,不过要改变文本。
- text: Celsius
点击 OK。把这个 StaticText 拖到摄氏度文本域的下面。
选择 Components StaticText。保留原来的名字不变,不过要改变文本。
- text: Fahrenheit
点击 OK。把这个 StaticText 拖到华氏度微调框的下面。
现在所有 GUI 元素(组件,也称为控件或部件)都已经摆放好,并且赋予了我们想要的名字和标签。把这个资源文件保存为 TempGui.rsrc.py(在资源编辑器中选 择 File Save As)。
接下来,在代码编辑器(SPE 或 IDLE)中新建一个文件,键入基本的 PythonCard 代码(或者也可以从我们的第一个程序复制):
from PythonCard import modelclass MainWindow(model.Background):app = model.Application(MainWindow)app.MainLoop
先不用考虑 pass 关键字,因为这只是块中没有定义任何内容时的一个占位符。我们将会为 MainWindow 类定义多个事件处理器。
摄氏度转换为华氏度
首先来完成摄氏度到华氏度的转换。将摄氏度转换为华氏度的公式是:
fahr = cel * 9.0 / 5 + 32
我们需要从 tfCel 文本框得到摄氏度,完成计算,再把结果放在 spinFahr 华氏度微调框中。这些应当在用户点击 Celsius to Fahrenheit 按钮时发生,所以要把完成这些工作的代码放在 Celsius to Fahrenheit 按钮的事件处理器中:
def on_btnCtoF_mouseClick(self, event):
为了从摄氏度框中得到值,我们使用了 self.components.tfCel.text。这个值是一个字符串,所以必须把它转换为一个浮点数:
cel = float(self.components.tfCel.text)
然后完成转换:
fahr = Cel * 9.0 / 5 + 32
接下来要把这个值放在华氏度框中。这里有一个小技巧:微调框中只能有整数值,不能有浮点数。所以把值放入微调框之前必须先把它转换为一个 int。微调框中显示的数是它的 value 属性,所以代码如下:
self.components.spinFahr.value = int(fahr)
华氏度转换为摄氏度
要完成另一个方向的转换(从华氏度转换为摄氏度),代码很类似。这个转换的公式是:
cel = (fahr - 32) * 5.0 / 9
这个代码要放在 Fahrenheit to Celsius 按钮的事件处理器中:
def on_btnFtoC_mouseClick(self, event):
首先从微调框得到华氏度:
fahr = self.components.spinFahr.value
这个值已经是一个整数,所以我们不必做任何类型的转换。然后应用公式:
cel = (fahr - 32) * 5.0 / 9
最后,把它转换为一个字符串,放在摄氏度文本框中:
self.components.tfCel.text = str(cel)
整个程序见代码清单 20-2。
代码清单 20-2 完成的温度转换程序
把这个程序保存为 TempGui.py。可以运行程序,试试这个 GUI。
小改进
运行这个程序时,你可能会注意到一点:将华氏度转换为摄氏度时,结果里有很多小数位,应该可以在文本框中去掉其中一些小数位。要解决这个问题有一个办法,称为打印格式化(print formatting)。我们还没有讨论到这个内容,你可以直接跳到第 21 章,那里对打印格式化的工作给出了一个完备的解释,或者也可以先直接键入这里给出的代码。在代码清单 20-2 的第 12 行(cel = (fahr - 32) * 5.0 / 9)和第 13 行(self.components.tfCel.text =str(cel))之间增加下面这行代码:
celStr = ‘%.2f’ % cel
这样显示一个数时会带两位小数。第 13 行中不再需要 str 函数(因为前面增加的代码已经为我们提供了一个字符串),所以代码应该变成这样:
self.components.tfCel.text = celStr
嗯……也许该调试了(debugging)。如果用户想转换南极洲的温度会怎么样呢?转换冥王星上的温度呢?
消灭错误
前面我们说过,要看程序中发生了什么,有一种很好的方法,就是在程序运行时打印出一些变量的值。下面就来试试看。
看起来是摄氏度到华氏度转换中的华氏度值有问题,所以就从这里开始。把下面这行代码增加到代码清单 20-2 的第 7 行 (fahr = cel * 9.0 / 5 + 32) 后面:
print /'cel = /', cel, /' fahr = /', fahr
现在,一旦点击 Celsius to Fahrenheit 按钮,可以看到 IDLE(或 SPE)shell 窗口中会打印出 cel 和 fahr 变量的值。对 cel 取几个不同的值,看看会发生什么。我得到了下面的结果:
>>> ============================ RESTART ============================>>>cel = 50.0 fahr = 122.0cel = 0.0 fahr = 32.0cel = -10.0 fahr = 14.0cel = -50.0 fahr = -58.0
看起来 fahr 值计算得很正确。那为什么华氏度框不能显示小于 0 的数呢?
再回到资源编辑器,点击用来显示华氏度的 spinFahr 微调框(必须点击带上下箭头的部分)。现在来看属性编辑器窗口,滚动属性编辑器查看不同的属性。有没有看到两个名为 min 和 max 的属性?它们的值是什么?现在你能不能猜出问题出在哪里?