12月10, 2019

Python 做 UI 超 easy(4)

上一节已经实现了一个简单的天气预报小程序,但是还遗留了一些小问题:

  • 只能查询一次,然后程序就会退出。

电脑上的程序不都可以一直活着吗,它怎么这么早夭?这一节我们来解决这个问题。

PySimpleGUI 的视窗程序分类

title

我们已经实现的程序,点击按钮触发以后,一闪而过就消失了,这种程序在 PySimpleGUI 属于 One-shot 应用, “一次性” 程序。它们打一枪换个地方,创建窗口,获得用户输入就完事儿了,窗口会被销毁的无影无踪。比如 Windows 下的错误信息提示框,点击一下确认就挥手讲拜拜了,不带走一片云彩。

另一种程序就长情的多,比如大多数桌面应用,除非你主动退出,否则它会一直响应你的点击、输入,像暖男一样: “我在”。这类程序称为 Persistent 程序,持久化程序。它的生命周期很长。

TIPS
某些 “渣男” 软件,即使你退出界面,它也还会偷偷的运行。做过 GUI 你就明白,这种流氓程序只是将 window.close() 了而已,程序却没有真正退出,真正的表面一套,背后一套。

One-shot VS Persistent

一次性和持久化程序在 PySimpleGUI 中没有本质区别:

  • 一次性程序只会执行有限次数(通常就是一次)window.read()
  • 持久化程序则会永续执行 window.read(),直到天荒地老或者用户退出。

持久化程序会多次(相当多次)调用 window.read() 与用户交互。持续调用 windows.read() 函数,是它如此 “持久化” 的原因。

title

新手写循环的时候经常会出现程序进入 “死循环”,迟迟无法退出。持久化程序正是利用了这种特性,将 window.read() 放在循环中,它永远不会退出了。

这个循环称为 “事件循环”,因为在这个循环里,我们不停的读取 event (和 values),并且处理它们。事件和与之对应的数据,才是我们真正关心的事,主要的功能代码都在这里,比如查询天气。

一个永远无法退出的程序实在太不友好了,最好在循环中设置出口,在满足条件的情况下 break 循环。

上次我们讲过,event 是触发 read 函数的组件 key,如果用户点击的是 window 上的 X,那这个 eventNone,并且这是 eventNone 的唯一情况,所以在循环中必须妥善处理 eventNone 的情况,否则会出现异常情况。

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 才会退出。

如果尝试多次查询天气,可以看到命令行多次输出,我们直到,成功了!

title

特别注意下面这些内容不要放到循环里:

  • layout 的定义,它只是一个变量,重复定义没有意义;
  • window 的创建,否则会报错;

总结

这已经是一个相当健全、典型的 GUI 程序了!大多数情况下,我们需要的是持久化的程序,重复接受用户的输入。

下一节,我们试试,让天气预报程序更好一点,顺便认识一点新的组件。

本文链接:http://www.thinkinpython.com/post/PySimpleGUIDemo4.html

-- EOF --