上一节已经实现了一个简单的天气预报小程序,但是还遗留了一些小问题:
- 只能查询一次,然后程序就会退出。
电脑上的程序不都可以一直活着吗,它怎么这么早夭?这一节我们来解决这个问题。
PySimpleGUI 的视窗程序分类
我们已经实现的程序,点击按钮触发以后,一闪而过就消失了,这种程序在 PySimpleGUI 属于 One-shot 应用, “一次性” 程序。它们打一枪换个地方,创建窗口,获得用户输入就完事儿了,窗口会被销毁的无影无踪。比如 Windows 下的错误信息提示框,点击一下确认就挥手讲拜拜了,不带走一片云彩。
另一种程序就长情的多,比如大多数桌面应用,除非你主动退出,否则它会一直响应你的点击、输入,像暖男一样: “我在”。这类程序称为 Persistent 程序,持久化程序。它的生命周期很长。
TIPS
某些 “渣男” 软件,即使你退出界面,它也还会偷偷的运行。做过 GUI 你就明白,这种流氓程序只是将 window.close() 了而已,程序却没有真正退出,真正的表面一套,背后一套。
One-shot VS Persistent
一次性和持久化程序在 PySimpleGUI 中没有本质区别:
- 一次性程序只会执行有限次数(通常就是一次)window.read();
- 持久化程序则会永续执行 window.read(),直到天荒地老或者用户退出。
持久化程序会多次(相当多次)调用 window.read() 与用户交互。持续调用 windows.read() 函数,是它如此 “持久化” 的原因。
新手写循环的时候经常会出现程序进入 “死循环”,迟迟无法退出。持久化程序正是利用了这种特性,将 window.read() 放在循环中,它永远不会退出了。
这个循环称为 “事件循环”,因为在这个循环里,我们不停的读取 event (和 values),并且处理它们。事件和与之对应的数据,才是我们真正关心的事,主要的功能代码都在这里,比如查询天气。
一个永远无法退出的程序实在太不友好了,最好在循环中设置出口,在满足条件的情况下 break 循环。
上次我们讲过,event 是触发 read 函数的组件 key,如果用户点击的是 window 上的 X,那这个 event 是 None,并且这是 event 为 None 的唯一情况,所以在循环中必须妥善处理 event 为 None 的情况,否则会出现异常情况。
TIPS
总之,对用户好一点,尽量自己处理异常,不要把问题抛给用户。
这绝对是开发者的金玉良言。
大手笔改造,从 One-shot 到 Persistent
你也可以在线运行本节代码
https://repl.it/@alvendarthy/PySimpleGUIDemo4
上一期做的天气预报小工具其实是 One-shot 程序,虽然我额外增加了一次 window.read() 防止它立刻退出,但也不改变本质。我们对它做一点小调整,就可以变成持久化程序。
import PySimpleGUI as sg
import requests
import json
def get_weather(city):
# 在线环境不支持中文输入,所以我改用城市编号的接口,如果你在电脑上开发,就不受影响
r = requests.get("http://wthrcdn.etouch.cn/weather_mini?citykey=" + city)
result = json.loads(r.text)
return result["data"]["forecast"][0]["type"]
# 让所有文本居中
sg.SetOptions(text_justification='center')
# 蓝图不需要放到循环中
layout = [
[ sg.Text("City", size = (20, 1)), sg.Input(key = "-CITY-") ],
[ sg.Text("Weather", size = (20, 1)), sg.Input(key = "-WEATHER-") ],
[ sg.Button("Submit")]
]
# 视窗也只需要创建一次,不要放到循环里
window = sg.Window("Weather App", layout)
while True:
event, values = window.read()
print(event, values)
# 当点击 window 右上角的 X,event 是 None,此时应当退出循环
if event is None:
break
# values 是一个字典,访问可输入组件的 key(定义蓝图时指定了这个参数)可以获得组件的输入
city = values["-CITY-"]
weather = get_weather(city)
print(weather)
# 找到天气输入框
weather_wind = window["-WEATHER-"]
# 将天气更新到输入框
weather_wind.update(weather)
# 程序已经不会直接退出了,下一行就不需要了
# window.read()
# 当退出循环的时候,就是程序退出之时,关掉 window
window.close()
现在试一下,是不是可以一直获取天气了,点击 X 才会退出。
如果尝试多次查询天气,可以看到命令行多次输出,我们直到,成功了!
特别注意下面这些内容不要放到循环里:
- layout 的定义,它只是一个变量,重复定义没有意义;
- window 的创建,否则会报错;
总结
这已经是一个相当健全、典型的 GUI 程序了!大多数情况下,我们需要的是持久化的程序,重复接受用户的输入。
下一节,我们试试,让天气预报程序更好一点,顺便认识一点新的组件。