20.6 TempGUI
你已经在第 3 章“动手试一试”部分中建立了第一个温度转换程序。第 5 章中,我们又为它增加了用户输入,这样一来,需要转换的温度就不必硬编码写在程序中了。在第 6 章中,我们使用了 EasyGui 来得到输入并且显示输出。现在我们要使用 PyQt 来建立这个温度转换程序的一个图形化版本。
TempGUI 组件
我们的温度转换 GUI 相当简单,只需要提供以下内容:
输入温度的位置(摄氏度和华氏度);
完成温度转换的按钮;
向用户显示有关信息的一些标签。
只是为了好玩,下面对摄氏度和华氏度分别使用两种不同类型的输入部件。在实际程序中绝对不要这么做(这只会把人们搞糊涂),不过这里我们的目的是学习如何使用这些部件!
建立了 GUI 布局后,会得到类似下图这样的结果:

也许你自己就可以完成,因为 Qt Designer 非常友好,很容易使用。不过没准你会需要一些帮助,所以这里还是会对相应步骤做些解释。这样也可以确保我们对组件使用相同的名字,便于更好地理解后面的代码。
并不要求组件完全对齐,也不必完全按这样来摆放组件,只要大致相同就可以了。
创建新的 GUI
第一步是建立一个新的 PyQt 工程。当你关闭打开的 UI(MyFirstGui) 时,Designer 会重新打开 New Form 窗口,在确保选中 Main Window 的情况下,点击 Create 即可。
下面开始增加组件:摄氏度输入框是一个 Line Edit 组件,华氏度输入框是一个 Spin Box,各个温度输入框下面的标签都是 Label 组件,另外还有两个 push Button 组件。在 Widget Box 的左侧向下滚动,以找到这些组件。建立这个 GUI 的步骤如下。
- 在 Widget Box 中找到 Push Button 组件,将它拖到窗口上,窗口上会出现一个新的按钮,然后:
通过拖动操作点或者在 geometry 属性中输入新的数字(在 MyFirstGui 中见过的),将按钮改成你想要的大小。
将按钮的 objectName 属性改为 btnFtoC。
将按钮的 text 属性改为 <<
- 向窗口中再拖入一个 Push Button,将它放到第一个按钮上方,并将尺寸改为你想要的大小,然后修改以下设置:
将按钮的 objectName 属性改为 btnCtoF。
将按钮的 text 属性改为 Celsius to Fahrenheit >>>。
将按钮的 font size 改为 12。
- 向窗口中拖入一个 Line Edit 组件,然后将它放到两个按钮的左边:
- 将组件的 objectName 属性改为 editCel。
- 向窗口中拖入一个 Spin Box 组件,然后将它放在两个按钮的右边:
- 将组件的 objectName 属性改为 spinFahr。
- 向窗口中拖入一个 Label 组件,然后将它放在 Line Edit 组件的下方:
将组件的 text 属性改为 Celsius。
将组件的 font size 改为 10。
- 向窗口中拖入一个 Label 组件,然后将它放在 Spin Box 的下方:
将挂件的 text 属性改为 Fahrenheit。
将挂件的 font size 改为 10。
现在所有 GUI 元素(组件,也称为控件或部件)都已经摆放好,并且赋予了我们想要的名字和标签。把这个 UI 文件保存为 tempconv.ui(在 Qt Designer 中选择 File > Save As)。记得将文件路径改为你通常保存 Python 程序的位置。
接下来,在 IDLE 中新建一个文件,键入基本的 PyQt 代码(或者也可以从我们的第一个程序复制):
import sys
from PyQt4 import QtCore, QtGui, uic
formclass = uic.loadUiType("tempconv.ui")[0]
class MyWindowClass(QtGui.QMainWindow, formclass):
def init(self, parent=None):
QtGui.QMainWindow._init(self, parent)
self.setupUi(self)
app = QtGui.QApplication(sys.argv)
myWindow = MyWindowClass()
myWindow.show()
app.exec()
摄氏度转换为华氏度
首先来完成摄氏度到华氏度的转换。将摄氏度转换为华氏度的公式是:
fahr = cel * 9.0 / 5 + 32
我们需要 从 editCel Line Edit 组件得到摄氏度,完成计算,再把结果放在 spinFahr 华氏度 Spin Box 组件中。这些应当在用户点击 Celsius to Fahrenheit 按钮时发生,所以要把完成这些工作的代码放在 Celsius to Fahrenheit 按钮的事件处理器中:
首先,我们需要将按钮的 clicked 事件连接到事件处理器:
self.btn_CtoF.clicked.connect(self.btn_CtoF_clicked)
像我们在第一个程序中所做的那样,将这段代码放在 MyWindow 类的 init() 方法中。
然后我们需要定义事件处理器。我们使用 self.editCel.text() 来从 Celsius 输入框(名为 editCel 的 Line Edit 组件)中取值。取到的值为字符串,所以我们要将它转换为浮点数:
cel = float(self.editCel.text()
然后我们来做单位转换:
fahr = cel * 9.0 / 5 + 32
接下来,我们需要将这个值放到 Fahrenheit 输入框中,也就是名为 spinFahr 的 Spin Box 组件。这里需要注意一点:Spin Box 组件的值只能是整数,不能是浮点数。所以在放入 Spin Box 之前需要将值转换为 int。Spin Box 的数字就是它的 value 属性,所以代码是这样的:
self.spinFahr.setValue(int(fahr))
我们要给结果加上 0.5,这样在使用 int() 将浮点数转换为整数时,才会用四舍五入以确保得到最近的整数值,而不是向下取整。将这些放到一起就是这样的:

华氏度转换为摄氏度
要完成另一个方向的转换(从华氏度转换为摄氏度),代码很类似。这个转换的公式是:
cel = (fahr - 32) * 5.0 / 9
这个代码要放在 Fahrenheit to Celsius 按钮的事件处理器中。我们将事件处理器连接到按钮上(在窗口的 init() 方法中):
self.btn_FtoC.clicked.connect(self.btn_FtoC_clicked)
然后,我们需要在事件处理器中从 Spin Box 获取华氏温度:
fahr = self.spinFahr.value()
这个值已经是一个整数,所以我们不必做任何类型的转换。然后应用公式:
cel = (fahr - 32) * 5.0 / 9
最后,把它转换为一个字符串,放在摄氏度文本框中:
self.editCel.setText(str(cel))
整个程序见代码清单 20-3。
代码清单 20-3 温度转换程序

把这个程序保存为 TempGui.py。可以运行程序,试试这个 GUI。
小改进
运行这个程序时,你可能会注意到一点:将华氏度转换为摄氏度时,结果里有很多小数位,应该可以在文本框中去掉其中一些小数位。要解决这个问题有一个办法,称为打印格式化(print formatting)。我们还没有讨论到这个内容,你可以直接跳到第 21 章,那里对打印格式化的工作给出了一个完备的解释,或者也可以先直接键入这里给出的代码。用下面两行代码替换 btn_FtoC_clicked 事件处理器的最后一行。
cel_text = ‘%.2f’ % cel
self.editCel.setText(cel_text)
这样显示一个数时会带两位小数。

嗯……也许该调试了(debug)。如果用户想转换南极洲的温度会怎么样呢?转换冥王星上的温度呢?

消灭错误
前面我们说过,要看程序中发生了什么,有一种很好的方法,就是在程序运行时打印出一些变量的值。下面就来试试看。
看起来是摄氏度到华氏度转换中的华氏度值有问题,所以就从这里开始。把下面这行代码增加到代码清单 20-3 中 btn_CtoF_clicked 事件处理器的最后一行之后:
print 'cel = ', cel, ' fahr = ', fahr
现在,一旦点击 Celsius to Fahrenheit 按钮,可以看到 IDLE shell 窗口中会打印出 cel 和 fahr 变量的值。对 cel 取几个不同的值,看看会发生什么。我得到了下面的结果:
>>> ============================ RESTART ============================
>>>
cel = 50.0 fahr = 122.0
cel = 0.0 fahr = 32.0
cel = -10.0 fahr = 14.0
cel = -50.0 fahr = -58.0
看起来 fahr 值计算得很正确。那为什么华氏度框不能显示小于 0(或大于 99)的数呢?
再回到 Qt Designer,点击用来显示和输入华氏度的 spinFahr 微调框。现在来看属性编辑器窗口,滚动属性编辑器查看不同的属性。有没有看到两个名为 minimum 和 maximum 的属性(位于微调框属性列表底端附近)?它们的值是什么?现在你能不能猜出问题出在哪里?
