好,我们的第一个 GUI 程序用大家一般在 Python 学习阶段都用过的一个简单的模拟登录。
- 预设账号和密码
- 输入账号
- 输入密码
- 点击提交
- 判断账号和密码是否匹配,都匹配则返回登录成功,否则登录失败。
基本逻辑如下:
user = input('请输入账号:')
password = input('请输入密码:')
if user == 'admin' and password == '123':
print('登录成功!')
else:
print('登录失败!')
相信很多同学都在控制台上做过这个练习。接下来呢,我们用 GUI 实现。
写一个 GUI 程序有以下步骤:
- 理清楚需求
- 画出原型图
- 分解原型图中的元素,构造布局生成窗口
- 获取窗口事件和数据
- 运行事件和数据处理逻辑
- 反馈结果
- 重复 4~6 直到点击退出
- 退出并销毁窗口
定义需求
定义需求:
用户在界面上输入账号和密码,当点击提交时判断账号和密码是否匹配,匹配则弹出登录成功,失败则弹出红字标识的登录失败。
画原型图
好,接下来我们画一个原型图,画原型图大家可以使用一些需求工具:
- Axure:很强大的原型图工具,但是用起来学习成本也比较高
- Balsamiq Mockups:草图工具,需要购买才能使用
- MockPlus:国产的原型图工具,画原型图免费,但是要导出需要收费
还有其他很多相关的原型图工具,请大家自行下载,当然你也可以直接在纸上画:
丑是丑了点,意思到了就行。简单的我们就直接画了,如果复杂的界面还是需要借助工具。
分解原型图并构建布局
我们来看一下原型图中可能会用到的界面元素(也有地方叫做控件、部件、组件等,我们这里统一叫做元素好了)。
- 窗口标题,还有窗口上的
-
缩小、□
适应屏幕、x
关闭。除了标题,其他自动构建; - 账号输入框,这里有两个元素:一个文本,显示字符串“账号”,一个输入框
- 密码输入框,这里也有两个元素:一个文本,显示字符串“密码”,一个密码输入框
- 一个提交按钮
- 两个弹出窗口,一个弹出“登录成功”,另一个弹出红色文字的“登录失败”。
在PySimpleGUI 中布局是按行来处理的。每个元素都处于布局中的某一行。其中第 1 点中的标题栏不属于布局中的内容,第 5 点的弹出窗口属于特殊元素,不用加入布局。
我们来看看在 PySimpleGUI 中如何通过行来管理的:
- 整个布局是一个大的列表
list
; - 每一行是这个大列表中的子列表;
- 每一行中的元素是这个列表的元素。
比如第 2 点的账号输入框,有两个元素,我们需要构建一个列表:
[文本, 输入框]
整个布局应该是这样:
[
[文本, 输入框],
[文本, 输入框],
[按钮]
]
如果更复杂的布局,就在这个列表中添加,比如你想在账号输入框后面添加一个复选框 checkbox
用来勾选是否保存账号,那么布局会变成这样:
[
[文本, 输入框, 复选框],
[文本, 输入框],
[按钮]
]
是不是很简单?
好了,我们还是回到我们这个小程序。
这里我们用到了三个元素,文本、输入框和按钮:
- 文本: Text 或者 T,它接收的第一个参数是要显示的文本;
- 输入框:InputText 或 Input 或 In,可以不用输入参数,是用来接收输入的单行文本。如果要用作密码输入框,那么用
password_char
参数来指定替换的字符串,比如你要用*
作为屏蔽字符,就写为password_char='*'
; - 按钮: Button 或 Btn 或 B,接收的第一个参数是按钮显示的文本。
这里我们可以看到作者给出了很多简写,方便你使用但其实也增加了一些记忆负担,你可以在可读性的基础上记住其中一个就行。
好,那么接下来,我们就把这些元素替换到我们布局的列表中去:
import PySimpleGUI as sg # 引入PySimpleGUI,注意大小写
# 创建布局
layout = [
[sg.Text('账号'), sg.Input(key='_USER_')],
[sg.Text('密码'), sg.Input(password_char='*', key='_PWD_')],
[sg.Btn('提交', key='_LOGIN_')]
]
这里的一些元素加上了
key
参数,这有什么用?是不是都要加?key有什么用? key 参数主要用于后面接收事件和获取用户输入。
是不是都要加? 像文本元素这种后续基本不会再处理的,可以不加。但是后续你如果要接收它的事件(比如按钮被点击),获取用户输入(输入框中录入的数据),改变元素(比如某种情况触发需要改变字体颜色等),那么这些被操作的元素一定要用 key 参数指定一个唯一的标识。
如何加? 作者建议,使用大写字母,前后加下划线的方式增加标识的可读性和可识别性。
好了,一个布局就生成了,是不是很简单。
接下来把布局列表作为参数传入窗口实例就可以了:
# 创建窗口,生成窗口实例
window = sg.Window('登录', layout=layout, finalize=True)
Window 第一个参数是窗口标题,第二个参数我们传入了前面定义的布局列表,第三个参数finalize
不是必须的,用来定型窗口,这主要有一些操作必须在窗口定型后才能执行。
接下来你运行一下就可以看到一个窗口闪了一下。
《震惊!这样写代码居然会让窗口闪一下就没了!》
好吧,如果你不加 finalize
参数,甚至闪都不会闪。我们还是把过程写完吧,这本来就是半成品。
事件循环
事件循环包含三个我们需要处理的过程:
- 获取窗口事件和数据
- 运行事件和数据处理逻辑
- 反馈结果
为什么要用循环?任何 GUI 窗口都是利用循环机制,不断循环接收用户输入和用户操作事件,然后处理,直到用户点击退出为止。如果不循环,窗口运行一次就会关闭。
在循环过程中,我们需要使用 Window
的 Read
方法来接收用户的事件和输入数据:
event, value = window.Read()
# event, value 的值分别是 _LOGIN_ {'_USER_': 'admin', '_PWD_': '123'}
返回的第一个值 event,其结果是接收界面的事件,这里是接收按钮点击事件。如果按钮被点击,event 的值将是被点击元素的 key 参数设置的标识符,比如我们这里的提交按钮的 key 设置为 _LOGIN_
,那么当提交按钮被点击时,event 的值就是 _LOGIN_
。
如果没有设置 key,event 的值将会是按钮文本,那这里就会是中文的
提交
。
第二个返回值是 value,这是一个以字典形式记录接收到界面上的用户输入。我们之前在元素中设置的 key 参数将作为字典的键,比如_USER_
和_PWD_
,可以通过这些 key 来取对应的输入值。
如果你没有为输入元素设置 key,那么字典的键为按照元素在 layout 中的顺序,用下标 0, 1, 2... 来记录用户输入的值。你就需要用下标按顺序去读取,如果元素很多的情况下,你很难保证正确性,因此我 建议一定要在元素定义的时候加上 key 参数,并且为元素设置一个唯一的标识。
好了,接下来我们就对事件进行处理。
if event == '_LOGIN_': # 当获取到事件是提交按钮被点击时,处理账号密码判断
user = value['_USER_'] # 从返回值字典中提取账号输入框的值
password = value['_PWD_'] # 从返回值字典中提取密码输入框的值
if user == 'admin' and password == '123':
sg.popup('登录成功!') # 弹出框
else:
sg.popup('登录失败!', text_color='red') # 弹出框的字体设置为红色
popup()
是 PySimpleGUI 中提供的弹出框,用text_color
参数设置字体颜色。
如果你用的是 Pycharm,直接 Ctrl+鼠标悬停 在类名、函数名上都会看到其参数及返回值。你在调用时,也可以看到有哪些参数,大部分参数命名都能见名知义,用的时候多留心。大致你想要的绝大部分功能都能实现。
代码敲完,运行一下,是不是就可以进行输入了。但是输入一次窗口就关闭了,因为上面说过,窗口值运行一次就结束。要想持续保持窗口运行状态就需要加入循环,在特定情况(比如右上角的x
和定义的元素事件)下退出循环,关闭窗口。
while True: # 设置一个循环
# 在特定条件下
break
那么什么特定条件下退出呢?比如某个按钮被点击或者右上角的x
被点击。特定按钮(比如界面上定义了一个按钮,其 key 为 _EXIT_
)的话,你直接写为:
while True:
if event == '_EXIT_':
break
对于右上角的x
被点击,window.Read() 会接收到一个事件None
。对,没错就是你熟悉的那个 None。那我们要响应右上角的x
退出怎么写呢?
while True:
if event is None:
break
或者你想两个地方(退出按钮被点击,右上角x
被点击)接收到事件都退出,那么写为:
while True:
if event in ['_EXIT_', None]:
break
然后在循环退出后,销毁窗口。
其实你也发现了,就算我们不做任何处理,窗口也会自动关闭。为什么还要多写一句呢?这是因为在某些系统中会出现异常,因此保持良好的习惯,在循环后加上退出代码。
window.close()
是不是比你用 input 写命令行复杂不了多少呢?
点这里获取本节完整代码。
网友评论