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 easygui
title = "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 easygui
title = "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 easygui
flavor = easygui.enterbox("What is your favorite ice cream flavor?")
easygui.msgbox ("You entered " + flavor)
这就类似于 input()
,同样可以从用户得到文本(一个字符串)。
默认输入
有时用户输入信息时,可能会期望得到某个答案,或者有一个很常见或最可能输人的答案。这称为默认值(default)。这个最常见的答案可以由你为用户自动输人,这样用户就不用再键入了。有了默认值,只有当用户有不同的输入时才有必要键入。
import easygui
flavor = 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-8
import random
import easygui
secret = random.randint(1, 99)
guess = 0
tried = 0
easygui.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 + 1
if 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 Snead
28 Main Street
Akron, Ohio
12345
Skier
学习编程有一种惯常的做法,就是先键入一些代码,然后运行它,尽管你可能完全不理解这些代码,没有关系!
有时仅仅键入代码就能让你对程序如何工作找到一点“感觉”,虽然并不是每一行或每一个关键字都理解。我们在第 1 章就是这么做的,就是那个猜数游戏。现在还是用这个老办法建立一个程序,不过这个程序更长也更有意思。
Skier(滑雪的人)是一个非常简单的滑雪游戏,在这个游戏中,你要滑下小山,努力避开树而且要尽量捡起小旗。捡起一个小旗得 10 分;碰到树则会丢掉 100 分。Skier
使用一个名叫 Pygame
的模块来帮助实现图形。
import pygame
import sys
import random
skier_images = ["skier_down.png", "skier_right1.png", "skier_right2.png", "skier_left2.png", "skier_left1.png"]
#This creates the skier
class 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 = 0
def turn(self, direction): #the skier turn around
self.angle = self.angle + direction
if self.angle < -2: self.angle = -2
if self.angle > 2: self.angle = 2
center = self.rect.center
self.image = pygame.image.load(skier_images[self.angle])
self.rect = self.image.get_rect()
self.rect.center = center
speed = [self.angle, 6 - abs(self.angle)* 2]
return speed
def move(self, speed): #the skier move on
self.rect.centerx = self.rect.centerx + speed[0]
if self.rect.centerx < 20: self.rect.centerx = 20
if self.rect.centerx > 620: self.rect.centerx = 620
#the obstacle: tree or flag
class ObstacleClass(pygame.sprite.Sprite):
def __init__(self, image_file, location, type):
pygame.sprite.Sprite.__init__(self)
self.image_file = image_file
self.image = pygame.image.load(image_file)
self.location = location
self.rect = self.image.get_rect()
self.rect.center = location
self.type = type
self.passed = False
def scroll(self, t_ptr): #moving the obstacle
self.rect.centery = self.location[1] - t_ptr
def create_map(start, end): #the game map
obstacles = 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 obstacles
def animate(): #draw on screen
screen.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 obstacles
pygame.init()
screen = pygame.display.set_mode([640,640])
clock = pygame.time.Clock()
skier = SkierClass()
print("created a skier")
speed = [0, 6]
map_position = 0
points = 0
map0 = create_map(20, 29)
map1 = create_map(10, 19)
activeMap = 0
obstacles = 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 = 1
map0 = create_map(20, 29)
obstacles = updateObstacleGroup(map0, map1)
if map_position >=1280 and activeMap == 1:
activeMap = 0
for ob in map0:
ob.location[1] = ob.location[1] - 1280
map_position = map_position -1280
map1 = 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 - 100
skier.image = pygame.image.load("skier_crash.png")
animate()
pygame.time.delay(1000)
skier.image = pygame.image.load("skier_down.png")
skier.angle = 0
speed = (6, 0)
hit[0].passed = True
elif hit[0].type == "flag" and not hit[0].passed:
points += 10
obstacles.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
游戏,使游戏的难度随着游戏的进行逐渐增大。可以尝试以下建议:
- 使游戏的速度随着游戏的进行逐渐加快。
- 滑雪者滑下小山时树越来越多。
- 添加障碍物“冰”,使得滑雪者转向时更加困难。
- 在个游戏中增加一个讨厌的雪人,它会随机出现并追赶滑雪者。你需要找到或者创建一个新的图片,并且修改代码以获得你想要的行为。