Graphical User Interface
在 GUI 中,并不只是 键入文本和返回文本,用户可以看到窗口、按钮、文本框等图形,而且可以用鼠标点击,还可以通过键盘键入。我们目前为止完成的程序都是命令行或文本模式程序。GUI 是与程序交互的一种不同的方式。有 GUI 的程序仍然有 3 个基本要素:输入、处理和输出,但它们的输入和输出更丰富、更有趣一些。
EasyGui 是一个 Python 模块,利用这个模块可以很容易地建立简单的 GUI。模块就是一种扩展方法,通过它可以向 Python 增加非内置的内容。
EasyGui.msgbox() 函数用于创建一个消息框。大多数情况下,EasyGui 函数的名就是相应英语单词的缩写。
>>> import easygui>>> easygui.msgbox("Hello There!")'OK'

GUI 输入
'OK' 部分就是 Python 和 EasyGui 在告诉你:用户点击了 OK 按钮。EasyGui 会返回信息来告诉你用户在 GUI 中做了什么:点击了什么按钮,键入了哪些内容等等。可以为这个响应指定一个名字(把它赋给一个变量)。
>>> user_response = easygui.msgbox("Hello there!")>>> print(user_response)OK
现在用户的响应(OK)有了一个变量名 user_response。
消息框实际上只是对话框(dialog box)的一个例子。对话框包含一些 GUI 元素,用来告诉用户某些信息,或者从用户得到一些输入。输入可以是按钮点击(如 OK),或者文件名,也可以是某个文本(字符串)。
EasyGui.msgbox 就是包含一条消息和一个OK按钮的对话框。不过还可以有不同类型的对话框,包含更多的按钮和其他内容。
多个按钮
下面来创建一个包含多个按钮的对话框(如消息框)。具体做法是使用一个按钮框(button box,buttonbox)。
import easyguititle = "What is your favorite ice cream flavor?"options = ['Vanilla', 'Chocolate', 'Strawberry']flavor = easygui.buttonbox(title, choices = options)easygui.msgbox("You picked " + flavor)

选择框
EasyGui 提供了一种选择框(choice box, choicebox),它会显示一个选择列表。用户可以选择其中之一,然后点击 OK 按钮。
import easyguititle = "What is your favorite ice cream flavor?"options = ['Vanilla', 'Chocolate', 'Strawberry']flavor = easygui.choicebox(title, choices = options)easygui.msgbox("You picked " + flavor)

选择一个口味然后点击 OK 时,你会看到与前面相同的消息框。注意,除了用鼠标点击选择,还可以用键盘上的上下箭头键选择一个口味。
文本输入
如果想像 input() 一样(也就是让用户键入文本),该怎么做呢?这样用户就可以输入自己喜欢的任何口味了。EasyGui 提供了一种输入框(enter box ,enterbox)能够做到这一点。
import easyguiflavor = easygui.enterbox("What is your favorite ice cream flavor?")easygui.msgbox ("You entered " + flavor)

这就类似于 input(),同样可以从用户得到文本(一个字符串)。
默认输入
有时用户输入信息时,可能会期望得到某个答案,或者有一个很常见或最可能输人的答案。这称为默认值(default)。这个最常见的答案可以由你为用户自动输人,这样用户就不用再键入了。有了默认值,只有当用户有不同的输入时才有必要键入。
import easyguiflavor = easygui.enterbox("What is your favorite ice cream flavor?", default = 'Vanilla')easygui.msgbox ("You entered " + flavor)

现在运行这个程序时,输人框中已经输入了“Vanilla”(香草)。可以把它删掉,再输入你想要的内容,不过如果你最喜欢的口味确实是香草,就不用再键人任何内容,只需点击0K。
数字
如果想在EasyGui中输入一个数,完全可以先通过输人框得到一个字符串,然后使用int()或者float()由这个字符串创建一个数。
EasyGui 还提供了一种整数框(integer box ,integerbox),可以用它来输入整数。还可以对所输入的数设置一个下界和上界。
不过,整数框不允许输入浮点数(小数)。要输入小数,必须先通过输入框得到字符串,然后再使用 float() 转换这个字符串。
猜数字游戏
我们之前创建了一个简单的猜数程序。下面再来完成这个程序,不过这一次要使用 EasyGui 完成输入和输出。
#!/usr/local/bin/env python3# encoding: utf-8import randomimport easyguisecret = random.randint(1, 99)guess = 0tried = 0easygui.msgbox("AHOY! I'm the Dread Pirate Roberts, and I have a secret! It is a number from 1 to 99. I'll give you 6 tries.")while guess != secret and tried < 6:guess = easygui.integerbox("What's your guess? ")if guess < secret:easygui.msgbox("Too low, you lazy dog!")elif guess > secret:easygui.msgbox ("Too high, landlubber!")tried = tried + 1if guess == secret:easygui.msgbox("Yeah! You got it! Found my secret, you did it!")else:easygui.msgbox("No more guesses! Better luck next time, boy!")easygui.msgbox("The secret number was " + str(secret))


EasyGui 还提供了另外一些 GUI 组件,包括允许多重选择(而不是只选择一项)的选择框,还有一些特殊的对话框用来得到文件名等内容。不过,对现在来说,前面介绍的 GUI 组件已经足够了。
利用 EasyGui,我们可以非常容易地生成一些简单的 GUI,而且它隐藏了 GUI 涉及的很多复杂性,使你不用再操心这些问题。后面我们将会讨论建立 GUI 的另一种方法,它可以提供更多的灵活性和控制。
help()
Python 提供了一个内置的帮助系统,在交互模式中,可以在交互提示符后面键入 help(),就会进入这个帮助系统。现在提示符会变成 help >:
>>>he1p ()help >
一旦进入帮助系统,你想要得到哪方面的帮助,只需要键入相应的名字,例如:help> easygui.msgbox 你就会得到你想要的一些信息。
要退出帮助系统,重回正常的交互提示符,只需要键入 quit:
help> quit>>>
Revision
- 如何利用
EasyGui建立简单的GUI。 - 如何使用消息框
msgbox显示消息。 - 如何使用按钮、选择框和文本输入框(
buttonbox、choicebox、enterbox、integerbox)得到输入。 - 如何为一个文本框设置默认输入。
- 如何使用
Python的内置帮助系统。
测试题
- 如何使用
EasyGui生成消息框? - 如何使用
EasyGui得到字符串(一些文本)输入? - 如何使用
EasyGui得到整数输入? - 如何使用
EasyGui得到浮点数(小数)输入? - 什么是默认值?给出一个可能使用默认值的例子。
动手试一试
- 试着修改温度转换程序,这一次要用
GUI输入和输出而不是input()和print。 - 编写一个程序,询问你的姓名,然后是房间号、街道和城市,接下来是省/地区/州,最后是邮政编码(所有这些都放在
EasyGui对话框中)。然后这个程序要显示一个寄信格式的完整地址,类似于:John Snead28 Main StreetAkron, Ohio12345
Skier
学习编程有一种惯常的做法,就是先键入一些代码,然后运行它,尽管你可能完全不理解这些代码,没有关系!
有时仅仅键入代码就能让你对程序如何工作找到一点“感觉”,虽然并不是每一行或每一个关键字都理解。我们在第 1 章就是这么做的,就是那个猜数游戏。现在还是用这个老办法建立一个程序,不过这个程序更长也更有意思。
Skier(滑雪的人)是一个非常简单的滑雪游戏,在这个游戏中,你要滑下小山,努力避开树而且要尽量捡起小旗。捡起一个小旗得 10 分;碰到树则会丢掉 100 分。Skier 使用一个名叫 Pygame 的模块来帮助实现图形。
import pygameimport sysimport randomskier_images = ["skier_down.png", "skier_right1.png", "skier_right2.png", "skier_left2.png", "skier_left1.png"]#This creates the skierclass SkierClass(pygame.sprite.Sprite):def __init__(self):pygame.sprite.Sprite.__init__(self)self.image = pygame.image.load("skier_down.png")self.rect = self.image.get_rect()self.rect.center = [320, 100]self.angle = 0def turn(self, direction): #the skier turn aroundself.angle = self.angle + directionif self.angle < -2: self.angle = -2if self.angle > 2: self.angle = 2center = self.rect.centerself.image = pygame.image.load(skier_images[self.angle])self.rect = self.image.get_rect()self.rect.center = centerspeed = [self.angle, 6 - abs(self.angle)* 2]return speeddef move(self, speed): #the skier move onself.rect.centerx = self.rect.centerx + speed[0]if self.rect.centerx < 20: self.rect.centerx = 20if self.rect.centerx > 620: self.rect.centerx = 620#the obstacle: tree or flagclass ObstacleClass(pygame.sprite.Sprite):def __init__(self, image_file, location, type):pygame.sprite.Sprite.__init__(self)self.image_file = image_fileself.image = pygame.image.load(image_file)self.location = locationself.rect = self.image.get_rect()self.rect.center = locationself.type = typeself.passed = Falsedef scroll(self, t_ptr): #moving the obstacleself.rect.centery = self.location[1] - t_ptrdef create_map(start, end): #the game mapobstacles = pygame.sprite.Group()gates = pygame.sprite.Group()locations = []for i in range(10):row = random.randint(start, end)col = random.randint(0, 9)location = [col * 64 + 20, row * 64 + 20]if not (location in locations):locations.append(location)type = random.choice(["tree", "flag"])if type == "tree": img = "skier_tree.png"elif type == "flag":img = "skier_flag.png"obstacle = ObstacleClass(img, location, type)obstacles.add(obstacle)return obstaclesdef animate(): #draw on screenscreen.fill([255, 255, 255])pygame.display.update(obstacles.draw(screen))screen.blit(skier.image, skier.rect)screen.blit(score_text, [10, 10])pygame.display.flip()def updateObstacleGroup(map0, map1):obstacles = pygame.sprite.Group()for ob in map0: obstacles.add(ob)for ob in map1: obstacles.add(ob)return obstaclespygame.init()screen = pygame.display.set_mode([640,640])clock = pygame.time.Clock()skier = SkierClass()print("created a skier")speed = [0, 6]map_position = 0points = 0map0 = create_map(20, 29)map1 = create_map(10, 19)activeMap = 0obstacles = updateObstacleGroup(map0, map1)font = pygame.font.Font(None, 50)while True:print ("we are in the game loop")clock.tick(30)for event in pygame.event.get():print("event loop")if event.type == pygame.QUIT: sys.exit()if event.type == pygame.KEYDOWN:if event.key == pygame.K_LEFT:speed = skier.turn(-1)elif event.key == pygame.K_RIGHT:speed = skier.turn(1)skier.move(speed)map_position += speed[1]if map_position >=640 and activeMap == 0:activeMap = 1map0 = create_map(20, 29)obstacles = updateObstacleGroup(map0, map1)if map_position >=1280 and activeMap == 1:activeMap = 0for ob in map0:ob.location[1] = ob.location[1] - 1280map_position = map_position -1280map1 = create_map(10,19)obstacles = updateObstacleGroup(map0, map1)for obstacle in obstacles:obstacle.scroll(map_position)hit = pygame.sprite.spritecollide(skier, obstacles, False)if hit:if hit[0].type == "tree" and not hit[0].passed:point = points - 100skier.image = pygame.image.load("skier_crash.png")animate()pygame.time.delay(1000)skier.image = pygame.image.load("skier_down.png")skier.angle = 0speed = (6, 0)hit[0].passed = Trueelif hit[0].type == "flag" and not hit[0].passed:points += 10obstacles.remove(hit[0])score_text = font.render("score: " + str(points), 1, (0, 0, 0))animate()pygame.quit()

动手试一试
这一章你要做的只是键入这个 Skier 程序,再运行试试看。如果运行时遇到错误,看看错误消息,试着找出错误究竟出现在哪里。
祝你好运!
Skier 进阶
滑雪者
当运行 Skier 程序的时候,应该注意到了滑雪者本身只能在屏幕上左右来回移动,而不能上下移动。滑雪者滑“下”小山的错觉则是通过将场景(树和小旗)向上滚动来实现的。
方向
在滑雪者滑下小山的场景实现中,需要 5 张不同的图片:一张滑雪者一直向下滑,两张滑雪者向左转(一张稍向左转,一张大幅度左转),两张滑雪者向右转(一张稍向右转,一张大幅度右转)。在程序的开头,我们为这些图片创建了一个列表,然后将图片按特定的顺序放入列表。
skier_images = [ "skier_down.png", "skier_right1.png","skier_right2.png","skier_left2.png","skier_left1.png"]
使用变量 angle 来标记滑雪者当前面对的方向,它的值从 -2 到 +2,分别如下:
- -2 = 向左急转
- -1 = 稍向左转
- 0 = 一直向下
- +1 = 稍向右转
- +2 = 向右急转
注意,这里的“左”和“右”是相对屏幕的方向,即我们看到的方向,而不是 滑雪者的左和右。
使用 angle 的值来确定要使用哪张图片。我们可以直接使用 angle 的值作为列表的索引来指定图片:
- skier_images[0] 是滑雪者向下滑的图片。
- skier_images[1] 是滑雪者稍向右转的图片。
- skier_images[2] 是滑雪者向右急转的图片。
- skier_images[-1] 是滑雪者稍向左转的图片(通常会使用 skier_images[4])。
- skier_images[-2] 是滑雪者向左急转的图片(通常会使用 skier_images[3])。
转弯
用一个类来改变滑雪者的状态,它会在 angle 的值改变时载入正确的图片,并设置好滑雪者的速度。速度由 x 和 y 两个值构成。我们只改变左右方向的速度(x 方向的速度,x-speed),但是 y 方向的速度(y-speed)决定了场景向上滚动的速度(滑雪者向“下”滑的速度)。当他垂直向下运动时,y 方向的速度比较快,而当他转向时,y 方向的速度则比较慢。速度的计算公式如下:
speed = [self.angle, 6 - abs(self.angle) * 2]
此行代码中的 abs 用于取得 angle 的绝对值,也就是说我们忽略符号(+ 和 -)后的值。对于 y 方向的速度来说,滑雪者左转还是右转都没有影响,只要知道转的程度就行了。
移动
使用方向键来控制滑雪者左右运动,所以要添加 Pygame 初始化和事件循环的代码,这样就可以让只有滑雪者的程序运行起来。
障碍物
创建了一个名为 ObstacleClass 的类。和滑雪者一样,这也是一个 Pygame Sprite 类。
地图
不要尝试将两个障碍物放在同一位置,所以我们需要知道哪些位置是已经使用过的。变量 locations 是一个用于记录使用过的位置的列表。当想要在某个位置放置一个新的障碍物时,我们首先要查看这个位置是否已有一个障碍物。
用 animate() 函数来重绘屏幕。
在障碍物类的 update() 方法中,添加一个判断逻辑,看看障碍物是否已经移出屏幕。如果是的话,就移除它。Pygame 有一个名为 kill() 的原 生方法可用来做这件事。
现在可以将滑雪者和障碍物的代码放到一起了:
- 需要
SkierClass和ObstacleClass。 -
animate()函数需要同时绘制滑雪者和障碍物。 - 初始化代码需要创建滑雪者和初始地图。
- 主循环需要同时包括滑雪者的键盘事件绑定和障碍物场景的创建。
最后需要处理的两项是:
- 检测滑雪者是否碰到了树或者是否捡到了小旗
- 记录并显示分数
直接用 spritecollide() 函数来检测滑雪者是否碰到树或者小旗。接下来需要知道这个障碍物是什么(树还是小旗),然后:
- 如果是树,则将滑雪者的图像切换为“碰撞”的图像,并将分数减去 100。
- 如果是小旗,则将分数加 10,然后将小旗从屏幕中移除。
变量 hit 告诉我们滑雪者撞到了哪个障碍物。它是一个列表,但在这里列表中只有一个条目,因为滑雪者一次只可能碰到一个障碍物,所以他碰到的障碍物是 hit[0]。
passed 变量用于标记是否碰撞过树。这能保证当滑雪者在撞树之后继续往下滑时,不会立刻运行撞到同一棵树的逻辑。
现在需要显示分数。在主循环中,使用一个新的分数文本来渲染 font 对象。animate() 函数中,在左上角显示分数:
font = pygame.font.Font(None, 50)score_text = font.render("Score: " + str(points), 1, (0, 0, 0))screen.blit(score_text, [10, 10])
Revision
Skier游戏的各个部分是如何工作的- 怎样创建一个滚动的背景
动手试一试
- 尝试修改
Skier游戏,使游戏的难度随着游戏的进行逐渐增大。可以尝试以下建议:
- 使游戏的速度随着游戏的进行逐渐加快。
- 滑雪者滑下小山时树越来越多。
- 添加障碍物“冰”,使得滑雪者转向时更加困难。
- 在个游戏中增加一个讨厌的雪人,它会随机出现并追赶滑雪者。你需要找到或者创建一个新的图片,并且修改代码以获得你想要的行为。
