【云+社区年度征文】PySimpleGUI一个建立在tkinter之上更简单但功能强大的GUI
声明:本文原创云+社区王荣胜,未经允许,不得转载!!!
你将会学到什么?
你将会学会一个除了Pyqt5
、TK
等其它界面编程的Python
第三方库,你可以利用它更方便的做出交互的界面。
全文导图
一、安装及简单说明
1.1界面编程到底是什么?
关于程序设计,有太多的书来描述,那么界面编程又是什么东西呢?
当你学习完一本程序设计的书本时,一般情况下都学会了某个编程语言的一些基本流程,而在学习的过程中,无须牵扯到界面编程,无论是获取用户的字符输入还是文件读写甚或是线程操作等等,都只需在命令窗口执行你写的代码就好。然而,在计算机的发展史上,终于还是出现了界面,发展至今,从个人电脑到手机,从各种各样的测试台到所有智能设备,用户操作界面无处不在,优秀的界面不但承载着人类方便的命令输入,也从最初的展示收集部件发展到现在的动态、智能、并具备收集人类各种有意识和潜意识状态的工具,其细化程度之繁杂,令人叹为观止。
界面指的是用户界面,此用户是指那些非专业程序员的客户。计算机的迅猛发展,可以解放人类的劳动力,各行各业都有各种需求,但是对于非专业程序员而言,面对命令窗口这种沉默的怪物,那种不友好是可想而知的,于是从人类自身出发,手、眼、语言等功能性的外延就成了界面设计的最初构想,在友好的界面上,即使对计算机再陌生的人,也会按照傻瓜式的指导一步步完成其想要完成的任务,而程序员要做的就是将用户当成完全不懂计算机的人来制作界面,这种自以为是的程序员和不懂计算机的小白客户之间于是就会产生各种各样的不理解,这种互动性直接催生了界面的规范化操作。
1.2PySimpleGUI是什么?
然而从程序员的角度来看,所有的界面只不过是一堆零部件的组合,其功能也只是收集数据而已!基于这样的观点,PySimpleGUI
这个Python
的工具包就开发出来了,这个工具包的目的就是要将这种界面设计哲学实现出来,使得程序员从繁杂的重复性代码书写中解脱出来,让他们的精力不再花费于那些琐碎的开发,更加专注于界面的搭配及功能实现,从这个角度来看,这不得不说是界面编程的一场革命。
目前比较主流的几种界面工具包有wxWidget
,Qt
,Tcl/Tk
等等,Python
自带Tck/Tk
工具包,正如同上述,这些工具包的使用都要摆出一些架式,熟悉的程序员当然不觉得怎样,但是对于新手而言,这些东西的每一步学习都是比较吃力的,于是一种符合人性化的哲学出来的时候,必然有许多天才程序员开始为这种哲学努力,即用统一的形式将这些主流工具包一一包装,使得程序员在利用这种统一包装语言进行编程的时候,无须考虑各种界面库的不同名称、不同方法以及不同语法,这种统一化的编程方式一经推出,即得到相当多程序员的关注,毕竟,对规律化东西的追求根植于我们人类的天性之中。
需要强调的是:PySimpleGUI不是一个独立的界面工具包,它只是提供一种人性化的统一调度接口,在Python下,默认调用Tkinter。
1.3PySimpleGUI的安装
在Windows
下,首先推荐安装Python3.6
以上的版本,至于为什么不是Python2.7
或其他,那是因为从对汉字的处理方式来看,Python3
版本比Python2
版本要方便(不要问我是如何知道的,当然还有许多其他的区别),而从进化来看,Python3
肯定是版本越高越好,也许最新的版本可能会有一些小问题,但是能解决的问题还是问题吗?人活着不就是为了解决问题来的吗?
Python3.6
自带pip
版本,用pip
来安装的命令如下:
pip3 install PySimpleGUI
在linux
下,需要将pip
换成pip3
,因为一般linux
下默认安装的python
版本是2。
1.4一个简单的界面小程序。
先来一个直观的吧,如果你看懂了,恭喜你,你真的具备编程天赋!没有看明白?没关系,好吧,开始这个程序的分析。
import PySimpleGUI as sg
layout = [[sg.Text('输入你的名字'),sg.InputText()],[sg.OK()]]
window = sg.Window('我的第一个GUI窗口').Layout(layout)
button,(name,) = window.Read()
window.Close()
将上述代码复制到你的ipython窗口/jupyter notebook窗口/vscode窗口/idle都是可以的,然后回车即可产生一个小窗口,当你在窗口中输入名字后,按OK按钮窗口即关闭。
二、一个简单程序的分析
2.1回顾
在上一段中简单介绍了PySimpleGUI
这个工具包,在结束的时候给出一个简单的例子,这个例子展示了一个简单的窗口,虽然只有短短五行代码,但是已经将如何创建一个窗口的几乎所有重要元素全部涵盖,本段将详细分析一下这五行代码。
import PySimpleGUI as sg
layout = [ [sg.Text('输入你的名字'), sg.InputText()], [sg.OK()] ]
window = sg.Window('我的第一个GUI窗口').Layout(layout)
button, values = window.Read()
window.Close()
2.2分析
第1句是导入语句,这是标准的Python
导入格式,as
只是另取一个名字而已,目的只是为了书写方便。
第2句是创建界面上的内容,从运行代码所得的窗口界面,可以看到其上所有的控件,在这个界面上一共有三个控件,一是展示提示文字,二是接收用户的输入,三是一个OK按钮。
为了创建这三个按钮,PySimpleGUI
创建了一种独特的列表方法,即将这些控件放在一个列表之中。
那么问题来了,如果将所有的控件只是简单的依次放在列表中,那之后的创建程序如何区分谁在上谁在下谁在左谁在右呢?为了解决这个问题,天才程序员们想出一个奇妙的规定,完美的解决了这个问题:将界面先按从上到下来排列,这样就分出若干行,然后在每行的控件中从左向右排列,这有些象二维矩阵的样子,于是只需要在大列表之中再嵌套一些子列表即可,每一个子列表即代表每一行的控件组,而子列表的排列顺序恰恰就是界面从上到下的排列顺序,其中每个子列表中控件排列顺序即是每行从左到右的排列顺序。
好了,PySimpleGUI的主要秘密之一就是这个,众所周知,Python
的列表处理功能极其强大,强大到在列表中可以写语句,那么这个工具包选用这种方式来构建整个界面元素,必将极大地方便了程序员对于界面控件的控制。
第3句即调用该工具包中的Window
函数来创建一个窗体,本身可以分步来写,即:
window = sg.Window('我的第一个GUI窗口')
window.Layout(layout)
这两句表明先创建一个窗口,然后将刚才创建的控件列表摆放到这个窗口上即可。原程序中只用一句,是利用了Python
的强大的语法功能将其合二为一。
至此,这个界面已经创建完成。那么为什么我们还没有看到呢?这是一个好问题,要知道界面创建出来的最主要目的就是和用户进行交互,即界面要承载所有用户的输入,如果将界面看做一个小机器人,那么它一旦显示出来就是要不断读取用户的数据,根据这个逻辑,天才的程序员们又为窗体实现了一个强大的Read
接口,这个接口函数可以读取用户对于界面的所有行为,然后将这些乱七八糟的行为以有序的方式返回到程序中,并且为了容易区分起见,将用户行为分为两类:一类是用户点击的控件名称,一类是用户输入的数据,这段小程序的第4句即展示了这一技术细节:
button, values = window.Read()
这个语句的第一个返回值只是OK按钮
的名字,第二个values
是一个列表,其中包含用户在文本框中输入的字符串。
如果你按照这段代码运行的时候,会发现一个问题,即当你点击OK按钮时,窗体即可消失。但是真正关闭窗体的语句其实是由第5句语句执行的,即:
window.Close()
那么又出现一个问题:即为什么用户只点击OK按钮
,窗体就不见了呢?
对了,这又引出另一个话题,即在PySimpleGUI
的包设计中,窗体可分为两类:一类是一次性窗口(One-shot Window),即展示一下获取用户输入后即消失的,最常见的比如常用的聊天软件的登录界面,而我们的例子就是一个典型的一次性窗口;另一类是持久性窗口(Persistent window),这类窗口将一直保留着,直到用户发出关闭指令才关闭,比如某聊天工具的聊天窗口、某办公软件的录入界面等等,这类窗口相对复杂,然而值得一提的是,天才程序们员仅仅用一个死循环的等待方式即得到了简便的处理方法,有关这一方法,下次再详细讨论。
三、创建一个持续性窗口(Persistent window)
3.1回顾
在上一段中说到,在PySimpleGUI
这个工具包中,窗口有两类,一类是一次性获取用户输入,另一类就是今天要谈到的持续性窗口,从上一次文章中分析可知,一次性窗口最大的特点是在用户点击某个按钮之后,程序只有一次读取机会,而用户的这一次点击即触发了该次读取,于是界面便不再接收用户输入信息了,即用户在事实上已经无法对该界面进行任何操作,除了将其关闭。
3.2探讨
那么解决持续性窗口的核心问题就在于要创建一个无穷的循环,不断来读取用户的输入行为,直到用户明确发出结束指令方才退出界面,这是一种可行的解决方法。
下面先来体验一下如何将上一篇中那个简单的一次性窗口改造成持续性窗口。
import PySimpleGUI as sg
layout = [ [sg.Text('输入你的名字'), sg.InputText()], [sg.OK()] ]
window = sg.Window('我的第二个GUI窗口').Layout(layout)
while True:
button, values = window.Read()
if values[0] == "退出":
break
else:
print(values[0])
window.Close()
上述代码将原来的窗口读取语句windows.Read()
放在了一个死循环之中,只有当用户在输入框中明确输入一个退出词语时,窗口才会关闭,否则程序直接在控制台上打印用户输入的字符串。
如果你将上述代码粘帖在ipython
命令窗口进行运行,即可得到如下图所示的样子:
如果要退出窗口,只需要在文本输入框中输入退出两个字,再点击OK按钮即可使界面消失关闭。
3.3分析
从上面的小程序可以看出,当用户每一次点击OK按钮时,都会触发一次界面读写事件,于是在while True
的死循环中就会循环一次,之后程序将停留在Read语句上,等待着用户的下一次输入。
可能聪明的读者这时候会有质疑:为什么这里的这个死循环和以前学习的Python
中死循环不一样呢?在普通的Python程序中,一旦进入死循环,程序立刻进入一种死的状态,即不再理会之后的程序而是疯狂地在循环中狂奔而无法逃逸,但这里的死循环却可以停在一个语句上,岂不是很奇怪吗?
的确,这是另一个小秘密,界面编程的本质就是等待用户输入指令从而执行,而这个等待的本身就是一个死循环,事实上,就操作系统本身而言,只要开机指令下达,即进入一个死循环,只是这种所谓的死循环是不断侦测用户的输入,而对于我们这个小程序而言,Read
语句的功能就是以刚才所创建的界面来侦测用户的行为,这种侦测是一种对用户行为的响应,我们通常用一个名词交互来对这种行为进行描述。
所以,在界面编程中,和传统的命令行编程有一些类似的地方,比如命令行中也有等待用户输入的命令input()
,如果将该命令也包含在while True
的循环之中,同样可以使得每一次循环都要在此语句上停留,以等待用户输入。
界面编程,是将用户的交互行为进行扩展,扩展到鼠标、键盘、声音、触摸等等方式都可以与程序进行交互,是对人类更容易操控计算机的一种改进。
3.4小结
这一篇简单介绍了持续性窗口的写法,再来回顾一下创建一个界面的步骤:
- 第一步:导入
PySimpleGUI
- 第二步:创建界面元素列表,该列表是嵌套的,其每一个子列表表明一行元素排列
- 第三步:用
Window
函数创建界面,并用其Layout
接口将刚创建的界面元素放置在界面上(此时并没有显示任何界面) - 第四步:调用窗口的
Read
方法,显示界面接收输入 第五步:针对用户的行为,进行各种处理
3.4实战
根据上面两次教程内容,创建一个简易加法器界面:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(), sg.Text("+"), sg.InputText(),sg.Text("=")], [sg.Button("计算"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
else:
tmp_a = values[0]
tmp_b = values[1]
print("%s与%s和是%s" % (tmp_a, tmp_b, eval(tmp_a)+eval(tmp_b)))
window.Close()
这里的编码有些错误,我先不予以更正~
这个小程序可以获取用户在两个InputText
控件中的输入值,并对这两个数进行求和运算,只不过是将结果打印在命令行。
读者觉得正常是应该将结果显示在用户界面上才对啊,嘿,不要着急,下一段就是要用一个小小的魔法将这个结果显示到界面上去。
四、动态更新窗口控件上的文字
4.1回顾
上一段的最后实战了一个小程序,这个小程序的功能是实现两数相加并将结果显示在命令窗口,目前看来,这当然只是一个过渡产品,界面编程的要求是要将所有数据在界面上表现出来,这样不仅仅出于一致性的考虑,更重要的是,这是界面编程的意义所在。
为了方便起见,现将上一篇教程中的代码抄录如下:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(), sg.Text("+"), sg.InputText(),sg.Text("=")], [sg.Button("计算"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
else:
tmp_a = eval(values[0])
tmp_b = eval(values[1])
print("%s与%s和是%s" % (tmp_a, tmp_b, eval(tmp_a)+eval(tmp_b)))
window.Close()
4.2进化
为了让程序计算结果显示在界面上,需要引入控件的一个方法Update
,该方法的功能是用新的字符串来替代原控件上的字符串,但是应该在哪里来显示这个结果呢?
聪明的读者应该能很快想到好办法,这里只是简单在=号后面添加一个Text
控件,首先将该控件上显示为空,待结果计算出来后,立刻将结果在该控件上显示就好了。
可是,新的问题又来了,在用户没有点击到这个控件时候,程序在运行时如何知道这个控件呢?
嗯,在PySimpleGUI
工具包中,Window这个窗口类提供了一个查找控件的方法FindElement
,但是这个方法需要依照一个关键字,所以,我们的办法就出来了,即给新增的标签控件增加一个新的关键字就好:
sg.Text("", key="_RESULT_")
上面代码中的_RESULT_
就是为FindElement
方法提供的关键字,在程序运行时通过以下语句来实现查找:
window.FindElement('_RESULT_')
这样就可以找到了我们需要更新其显示的标签控件。
整体代码如下:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(), sg.Text("+"), sg.InputText(),sg.Text("="), sg.Text("", key="_RESULT_")], [sg.Button("计算"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
else:
tmp_a = eval(values[0])
tmp_b = eval(values[1])
tmp_result = tmp_a+tmp_b
window.FindElement("_RESULT_").Update(str(tmp_result))
window.Close()
细心的人可以将两次代码仔细对比,自然会发现其中的不一样之处。
4.3继续进化
等等,在我们运行上述程序后,会出现一个界面,但是当我们输入两个加数,再点击计算按钮时,结果是计算出来了,但是两个加数的输入框却被清空了,那么能不能在运算结束时还将两个加数输入框中的数字保留下来呢?
答案当然是可以,而且很简单,只需要在创建的两个加数文本框中添加一个do_not_clear
的标识即可,默认情况下,该标识是False
,即界面只要读取一次用户点击,即将当前文本框清空,倘若不想清空,只需要将该标识设置为True
即可:
sg.InputText(do_not_clear=True)
修正后的代码如下:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(do_not_clear=True), sg.Text("+"), sg.InputText(do_not_clear=True),sg.Text("="), sg.Text("", key="_RESULT_")], [sg.Button("计算"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
else:
tmp_a = eval(values[0])
tmp_b = eval(values[1])
tmp_result = tmp_a+tmp_b
window.FindElement("_RESULT_").Update(str(tmp_result))
window.Close()
运行结果如下:
4.4如何更加完美
程序现在已经按照我们的意思完美运行,但是在计算完一道题目后,如何将这些已经填入的数据和计算的数据清空呢?
这时候就可以使用 '清除' 按钮了
代码如下:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(do_not_clear=True, key="_SHU1_"), sg.Text("+"), sg.InputText(do_not_clear=True, key="_SHU2_"),sg.Text("="), sg.Text("", key="_RESULT_")], [sg.Button("计算"),sg.Button("清空"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
elif button=="清空":
window.FindElement("_SHU1_").Update("")
window.FindElement("_SHU2_").Update("")
window.FindElement("_RESULT_").Update("")
else:
tmp_a = eval(values["_SHU1_"])
tmp_b = eval(values["_SHU2_"])
tmp_result = tmp_a+tmp_b
window.FindElement("_RESULT_").Update(str(tmp_result))
window.Close()
仔细观察上述代码,你会发现许多不同的地方,尤其是返回值的引用,如果你没有按照上述代码来运行,只是更新一下原来的代码的话,会报出一个KeyError
,这就是引入了关键字key后的一个小变化。
五、窗口响应的返回值是列表还是字典?
5.1回顾
在上一段中我们在最后改进程序时发生了一个意外,即程序在运行时报出了KeyError
错误,经过仔细检查,发现在窗口的控件创建时,如果指定了key
关键字,那么在引用其返回值时,还用value0或value1时,就会出错,在本篇文章中,我们将仔细研究一下,界面窗口对于用户的点击在何时采用何种方式来返回。
5.2例1返回值为列表(list)
import PySimpleGUI as sg
layout = [[sg.Text("姓名"), sg.InputText("浪迹天涯")], [sg.Text("单位"), sg.InputText("天上人间")], [sg.Text("地址"), sg.InputText("四海为家")], [sg.Button("打印")]]
window = sg.Window("测试界面返回值的例子").Layout(layout)
button, values = window.Read()
print(values)
print("类型是{}".format(type(values)))
window.Close()
界面显示如下图所示:
当用户点击打印按钮时,程序将在命令窗口上打印出窗体上输入控件中的内容,并打印该返回值的类型。
从打印可以看出,此时程序对于用户的点击响应返回值为list类型,根据以前学过的知识可以知道,对于list
类型中元素的引用需要以其序列来索引,即可以用values0、value1来获取。
5.3例2返回值为字典(dict)
回顾上一篇文章最后的例子中,我们采用了value"SHU1"、value"SHU2"这样的形式来获取用户的输入值,这种是典型的字典(dict
)引用方式。将上述例子稍做改动,做为例子2:
import PySimpleGUI as sg
layout = [[sg.Text("姓名"), sg.InputText("浪迹天涯",key="_NAME_")], [sg.Text("单位",), sg.InputText("天上人间", key="_UNIT_")], [sg.Text("地址"), sg.InputText("四海为家", key="_ADDRESS_")], [sg.Button("打印")]]
window = sg.Window("测试界面返回值的例子").Layout(layout)
button, values = window.Read()
print(values)
print("类型是{}".format(type(values)))
window.Close()
这段代码的界面显示与上一个完全相同,所不同的是打印的结果不一样:
从上图的打印结果可以看出,这一次返回值的类型已经变成了字典(dict
)类型。
5.4小结
从上面两个小例子可以看出,当你给程序中的控件指定了关键字标识时,界面对用户行为的返回值是以字典(dict
)的形式给出,而当用户不加任何key
关键字时,界面对于用户行为的返回值是列表(list
)。
在学习了这么多之后,有人可能会问,为什么做一个加法器时,输入框这么长呢,能不能缩短一些呢?比如原来竟然是这么长:
答案当然是可以啦,只需要在创建控件时加入size
这个参数即可,于是上一段最后一个小程序可修改为:
import PySimpleGUI as sg
layout = [[sg.Text("加法器")], [sg.InputText(size=(10,1),do_not_clear=True, key="_SHU1_"), sg.Text("+"), sg.InputText(size=(10,1), do_not_clear=True, key="_SHU2_"),sg.Text("="), sg.Text("", key="_RESULT_")], [sg.Button("计算"),sg.Button("清空"), sg.Button("退出")]]
window = sg.Window("加法器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button == "退出":
break
elif button=="清空":
window.FindElement("_SHU1_").Update("")
window.FindElement("_SHU2_").Update("")
window.FindElement("_RESULT_").Update("")
else:
tmp_a = eval(values["_SHU1_"])
tmp_b = eval(values["_SHU2_"])
tmp_result = tmp_a+tmp_b
window.FindElement("_RESULT_").Update(str(tmp_result))
window.Close()
运行结果如图所示:
六、一个文件浏览对话框
6.1回顾
在前几段文章中,我们分析了用 PySimpleGUI
这个工具包来创建界面的基本方法,并且探讨了一些具体的细节问题,如果读者能一一理解前面的内容,那么接下来我们就要用这个工具包来展示一个常用的文件浏览对话框。
6.2文件浏览对话框
我们的日常应用中,经常会要打开或是保存某个文件,在特定的软件中,比如办公软件中,经常要用打开、保存等对话框来供用户来选择文件存放位置,在PySimpleGUI
这个工具包中,创建文件对话框是很容易的一件事,下面代码可以弹出该对话框:
import PySimpleGUI as sg
import sys
if len(sys.argv) == 1:
event, values = sg.Window("我的脚本对话框").Layout([[sg.Text("打开文档")],
[sg.Input(), sg.FileBrowse()], [sg.Button("打开"), sg.Button("退出")]]).Read()
fname = values[0]
else:
fname = sys.argv[1]
try:
if not fname:
sg.Popup("关闭", "没有提供文件名!")
raise SystemExit("程序关闭:没有提供文件名")
print("你要打开的文件名是:",fname)
except SystemExit as err:
print(err)
print("系统输入:", sys.argv)
这段代码既可以在命令行运行,也可以直接在Python
环境下运行,如果用命令行来执行,带有文件名参数时,运行如下图所示:
如果不带参数在命令行下运行,则会弹出对话框如下图所示:
在点击 browse
按钮时,程序将弹出文件选择对话框供用户选择,当选中某文件后,该文件的名称自动会填充入此按钮左边的文本输入框,如图所示:
在选中文件后,其路径将自动填充在文本输入框中:
6.3分析
对于该对话框程序进行仔细分析后,会发现该程序既可以在命令行运行,也可提供对话框界面让用户输入,一种代码,两种运行方式,相当酷。
这里面主要用到了一个 sys
的包,这个包里提供一些命令行输入参数等的调用,在该程序中,主要用到了sys.argv
这个变量,该变量是一个 list
,其第1个元素是当前运行的脚本名称,这可以从我们程序的最后打印语句中看到,从第2个元素开始,就是在命令窗口运行该命令时紧跟其后的参数,本程序中将其带的第1个参数认定为要打开的文件名。
需要注意的是,程序中用到了 try...catch...
语句,而且在对话框弹出后,用户依然没有选择文件而点击打开按钮时,程序将弹出 SystemExit
告警信息,然后在catch
中进行捕获该异常,将其附带的告警字符串打印出来,这种方式使得程序更加健壮,告警信息如图:
在该程序中,还有值得注意的地方是,在界面元素设定中,只要将一个 InputText()
元素和 FileBrowse()
放置一起,则后者调用后的返回值自动关联到前一个文本输入框中,这是非常方便的一种试,无须手动绑定。
根据测试可知, FileBrowse()
只将其返回的文件路径放置在离它最近的一个文本框中,下图明显提示了这一特点:
6.4小结
本段探讨的小程序虽然简单,但是 麻雀虽小,五脏俱全,不但同时提供了两种运行方式,而且还有设置抛出异常并主动捕获,这些方式需要仔细研究才能领会。
七、目前能用的控件有哪些?
7.1回顾
上一段中我们实现了一个文件浏览对话框,从程序来看,核心代码只有一句,即:
event, values = sg.Window("我的脚本对话框").Layout([[sg.Text("打开文档")],
[sg.Input(), sg.FileBrowse()], [sg.Button("打开"), sg.Button("退出")]]).Read()
但是为了能使程序能够在命令行也可以执行,所以引入了 sys
这个包来检测用户的输入信息。
7.2探索
目前为止,我们已经接触到的窗口控件有 Text
、InputText
、 Button
、FileBrowse
等,但是对于大量复杂的任务而言,只有这几个控件不足以完成任务,所以一般情况下,任何一个比较成熟的界面工具包都提供许多大同小异的控件,比如列表框、比如表格控件、比如进度条等等,这一篇我们就来探索一下目前PySimpleGUI已经将多少标准控件调用方式转换完成。
下面我们将粘贴一段 PySimpleGUI
网站中的一段代码来做一个简略演示:
#!/usr/bin/env Python3
# import PySimpleGUIQt as sg
import PySimpleGUI as sg
sg.ChangeLookAndFeel('GreenTan')
# ------ Menu Definition ------ #
menu_def = [['File', ['Open', 'Save', 'Exit', 'Properties']],
['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
['Help', 'About...'], ]
# ------ Column Definition ------ #
column1 = [[sg.Text('Column 1', background_color='#F7F3EC', justification='center', size=(10, 1))],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]]
layout = [
[sg.Menu(menu_def, tearoff=True)],
[sg.Text('All graphic widgets in one window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)],
[sg.Text('Here is some text.... and a place to enter text')],
[sg.InputText('This is my text')],
[sg.Frame(layout=[
[sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)],
[sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')],
[sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)),
sg.Multiline(default_text='A second multi-line', size=(35, 3))],
[sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)],
[sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
[sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)),
sg.Frame('Labelled Group',[[
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
sg.Column(column1, background_color='#F7F3EC')]])],
[sg.Text('_' * 80)],
[sg.Text('Choose A Folder', size=(35, 1))],
[sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'),
sg.InputText('Default Folder'), sg.FolderBrowse()],
[sg.Submit(tooltip='Click to submit this window'), sg.Cancel()]
]
window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout)
event, values = window.Read()
sg.Popup('Title',
'The results of the window.',
'The button clicked was "{}"'.format(event),
'The values are', values)
这段代码运行后的结果如下图所示:
从运行结果来看,这里用到了菜单、滚动条、复选框等,其用法也都比较简单,愿意用的同学可以仔细阅读一下代码即可。
7.3实战
在学习了这许多内容后,我们来做一个计时器小程序,这个程序很简单,当用户开始运行时,在界面窗口中用 Text
控件将时间按分、秒、毫秒的方式展现,需要注意的是,这个小程序是不断刷新界面的。具体代码如下:
import PySimpleGUI as sg
layout = [[sg.Text("计时器", size=(20,2), justification="center")],
[sg.Text("", size=(10,2), font=("宋体", 20), justification="center", key="_OUTPUT_")],
[sg.Text(" "*5), sg.Button("启动/停止", focus=True), sg.Button("退出")]]
window = sg.Window("计时器").Layout(layout)
timer_running = True
i = 0
while True:
i += 1 * (timer_running is True)
event, values = window.Read(timeout=10)
if event is None or event == "退出":
break
elif event == "启动/停止":
timer_running = not timer_running
window.FindElement('_OUTPUT_').Update('{:02d}:{:02d}:{:02d}'.format(i//100//60, (i//100)%60, i%100))
window.Close()
这段代码是模拟一个计时器,并不是真实的计时器,因为只是用一个循环变量简单模拟,不同的电脑运行会感觉每秒时长有问题,有兴趣的同学可以引入time
时间包进行细化。具体界面如下:
八、回调函数的模拟以及进度条的演示
8.1回顾
在前面几段文章中我们整体认知了 PySimpleGUI
这个工具包的基本使用方法,也见识了其中的一些控件使用。这个工具包主要是简化界面的编写,将界面对于用户输入的采集自动化完成。
8.2回调函数模拟
在传统的界面编程中,程序员需要对控件的每一个响应编写一个回调函数,这个意思是指当用户点击某个按钮或是某个控件的状态改变时,程序需要做出的反应。
事实上,在 PySimpleGUI
这个工具包中,并不需要对专门的按钮去做一个回调函数编写,但是如果想实现也是一件容易的事情,下面的代码对这个进行一个简单的展示。
import PySimpleGUI as sg
def button1():
print("按钮1被点击")
def button2():
print("按钮2被点击")
layout = [[sg.Text("请点击一个按钮")],
[sg.Button("1"), sg.Button("2"), sg.Button("退出")]]
window = sg.Window("回调函数模拟").Layout(layout)
while True:
event, values = window.Read()
if event == "1":
button1()
elif event == "2":
button2()
elif event is None or event=="退出":
window.Close()
break
sg.PopupOK("完成!")
执行程序的界面如图所示:
当用户分别点击两个按钮时,控制台上将打印出各自按钮回调函数中所预先定义的语句。如图所示:
8.3分析
由于 PySimpleGUI
这个工具包本身已经将界面的侦测自动化处理,所以上述的回调函数本身并没有真正揭示出回调函数对于普通界面编程的意义,但是从这个仿真模拟可以看出,这种比较类似于普通的回调,尽管以现在看方式来看,创建两个函数去打印两个语句有多余的感觉,但是如果从函数模块化编程的角度来考虑,将特别的功能独立出来,这种方式却也有其可取之处。
8.4实战
现在我们还做一个简单的小例子,再一次来体会一下PySimpleGUI
工具包封装的强大。
在这个小例子中,我们来试验一种特别的控件————进度条,即用一个简单的循环就可以将一个进度条创建出来,这种方式是不是特别酷?
代码如下:
import PySimpleGUI as sg
for i in range(10000):
sg.OneLineProgressMeter('一行进度条的例子', i+1, 10000, 'key')
这个神奇的例子将显示如下截图:
当然,我们很多时候希望自己能够来将进度条添加在我们的界面上,那就要用到该库中的 ProgressBar
这个控件,代码如下:
import PySimpleGUI as sg
layout = [[sg.Text('一个自定义的进度条例子')],
[sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')],
[sg.Button("退出")]]
window = sg.Window('自定义进度条').Layout(layout)
progress_bar = window.FindElement('progressbar')
for i in range(10000):
event, values = window.Read(timeout=0)
if event == '退出' or event is None:
break
progress_bar.UpdateBar(i + 1)
window.Close()
这段代码演示了自定义进度条是如何工作的,其运行如下图所示:
从运行来看,当用户点击退出按钮时,即使进度条没有完成,也会退出。不过从下面一个例子再来看的话,你会发现一个奇怪的事情,代码如下:
import PySimpleGUI as sg
layout = [[sg.Text('一个自定义的进度条例子')],
[sg.ProgressBar(10000, orientation='h', size=(20, 20), key='progressbar')],
[sg.Button("执行"), sg.Button("退出")]]
window = sg.Window('自定义进度条').Layout(layout)
progress_bar = window.FindElement('progressbar')
while True:
event, values = window.Read(timeout=10)
if event == '退出' or event is None:
break
elif event == "执行":
for i in range(10000):
progress_bar.UpdateBar(i+1)
window.Close()
以上代码的运行界面如图:
从运行中可知,当用户在进度条滚动时无论如何点击退出按钮,窗口也无法关闭。这是什么原因呢?原来这牵扯到另一个问题了,即同一个进程中,当界面在执行某一段代码时,是不会理会其他行为的,那么若想同时执行两个行为怎么办呢,这就是以后要讲到的线程问题了。
九、媒体播放器界面及脚本执行器
9.1回顾
在上一段中研究了 PySimpleGUI
中回调的模拟以及一个小例子。在这一段中,我们仍然通过几个例子来进一步说明利用 PySimpleGUI
进行开发的一些技术。
9.2一个媒体播放器界面的开发
媒体播放器一般要放置一些图片按钮在界面上,这样会使界面显得更加生动一些,这个例子展示了如何在一个按钮上放置图片的例子,具体代码如下:
import PySimpleGUI as sg
def MediaPlayerGUI():
background = '#F0F0F0'
sg.SetOptions(background_color=background, element_background_color=background)
image_pause = './pause.png'
image_restart = './prev.png'
image_next = './next.png'
image_exit = './exit.png'
layout= [[sg.Text('Media File Player',size=(17,1), font=("Helvetica", 25))],
[sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')],
[sg.Button('', button_color=(background,background),
image_filename=image_restart, image_size=(50, 50), image_subsample=2, border_width=0, key='Restart Song'),
sg.Text(' ' * 2),
sg.Button('', button_color=(background,background),
image_filename=image_pause, image_size=(50, 50), image_subsample=2, border_width=0, key='Pause'),
sg.Text(' ' * 2),
sg.Button('', button_color=(background,background),
image_filename=image_next, image_size=(50, 50), image_subsample=2, border_width=0, key='Next'),
sg.Text(' ' * 2),
sg.Button('', button_color=(background,background),
image_filename=image_exit, image_size=(50, 50), image_subsample=2, border_width=0, key='Exit')],
[sg.Text('_'*20)],
[sg.Text(' '*30)],
[
sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', font=("Helvetica", 15)),
sg.Text(' ' * 2),
sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', font=("Helvetica", 15)),
sg.Text(' ' * 2),
sg.Slider(range=(-10, 10), default_value=0, size=(10, 20), orientation='vertical', font=("Helvetica", 15))],
[sg.Text(' Bass', font=("Helvetica", 15), size=(9, 1)),
sg.Text('Treble', font=("Helvetica", 15), size=(7, 1)),
sg.Text('Volume', font=("Helvetica", 15), size=(7, 1))]
]
window = sg.Window('Media File Player', auto_size_text=True, default_element_size=(20, 1),
font=("Helvetica", 25)).Layout(layout)
while(True):
event, values = window.Read(timeout=100) # Poll every 100 ms
if event == 'Exit' or event is None:
break
if event != sg.TIMEOUT_KEY:
window.FindElement('output').Update(event)
MediaPlayerGUI()
关于以上媒体播放器的代码中,需要注意的有以下几个问题:一是所用的图片要注意用 png
格式,如果用的是jpg
格式,会报错。二是在各个按钮之间以空的Text来填充,这样从视觉上会有分开的效果,三是在用户点击按钮后,会将按钮的key更新显示在提前定义好的 Text
上,具体运行如下图所示:
这个例子的代码没有什么新的东西,主要是在按钮上如何放置一个图片,因为图片是圆形按钮,为了更好地显示该圆形图案,就将背景统一设置为一种颜色,这样就会使该图片的四周与周边图形融为一体。需要注意的是在图片设置中 image_subsample
属性的设置,该变量设置越小,图片在界面上就会显示越大,有兴趣的同学可以自行测试。
9.3脚本启动器
再来一个例子,在这个例子中,我们来制作一个脚本启动器,即用界面来提供一个输入框,让用户在其中键入相应的系统命令,然后由程序来调用执行该命令,并将该命令的结果返回在界面上,有点替代 CMD
窗口的感觉,相当酷,而且很简单,代码如下:
import PySimpleGUI as sg
import subprocess
def ExecuteCommand(command, *args):
try:
sp = subprocess.Popen([command, *args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = sp.communicate()
if out:
print(out.decode("gbk"))
if err:
print(err.decode("gbk"))
except:
print("所输入的命令无法有效执行!")
layout = [
[sg.Text("脚本输出...", size=(40,1))],
[sg.Output(size=(88,20), key="_OUTPUT_")],
[sg.Button("脚本1"), sg.Button("脚本2"), sg.Button("退出")],
[sg.Text("命令:",size=(15,1)), sg.InputText(focus=True), sg.Button("运行",bind_return_key=True)]
]
window = sg.Window("脚本执行器").Layout(layout)
while True:
event, values = window.Read()
if event is None or event == "退出":
break
if event == "脚本1":
ExecuteCommand('pip', 'list')
# window.FindElement("_OUTPUT_").clear()
elif event == "脚本2":
ExecuteCommand("python", "--version")
elif event == "运行":
cmdtmp = values[0]
cmdtmp = cmdtmp.split(" ")
if len(cmdtmp) == 2:
ExecuteCommand(cmdtmp[0],cmdtmp[1])
elif len(cmdtmp) == 1:
ExecuteCommand(cmdtmp[0])
else:
print("所输入的命令超过可执行能力,请输入'pip list'类似样式的命令!")
window.Close()
以上的代码运行如下图所示:
对于这个简单的程序需要说明的是,为了将命令执行的结果输出到界面的 Output
控件,引入了 subprocess
这个标准包,该包主要利用管道技术将程序的输出和错误返回管道中,之后方便在程序中使用,因为界面包中将Output
默认定义了输出,所以在该程序中所有的print
语句自动将结果打印输出至该控件中。需要注意的是在windows
下需要将信息以gbk
方式解码,在linux
下要以utf-8
方式解码。
对于subprocess
这个包的解释已经超过本篇文件的内容范畴,只是为方便理解,需要提一点:对于操作系统的任何命令,操作系统通常是有三个部分在联动,一是stdin
,即输入,二是stdout
即输出,三是stderr
即错误报警,在这个包中,利用subprocess
的Popen
命令执行完后,结果放在其PIPE
中,需要以标准的输出来获取其内容,而communicate
这个函数就可以将刚才的命令执行结果返回,当然只需要返回stdout
和stderr
即可。关于这部分的详细内容,请阅读相关操作系统的书籍。
9.4小结
在本段中,用两个小例子进一步介绍了界面编程的一些内容,同时结合一些Python常用工具包完成一些日常需要用到的功能,请细心的读者仔细体会。
十、列表的使用及一个简易计算器例子
10.1回顾
在上一段中,我们编写了一个简单的音乐播放器界面和一个脚本执行程序,展示了 PySimpleGUI
强大的功能,在这一段中,我们继续来学习新的控件,并尝试用前面学习的内容编写一个简易计算器程序。
10.2列表的使用
列表控件是我们日常用到的较多的一个控件,从表格制作到文件在文件夹中的排列,凡是需要排列的地方,我们总是第一个考虑是否需要一个列表控件来将所展示的数据进行有序化整理。下面这个小例子就展示了这个技术,为了和普通的文本区分开,将两者分别列于同一个窗体上,让读者可以自行对比。
代码如下:
import PySimpleGUI as sg
sg.ChangeLookAndFeel('BlueMono')
col = [[sg.Text('行列 1', text_color='white', background_color='blue')],
[sg.Text('行列 2', text_color='white', background_color='blue'), sg.Input('可输入文本框 1')],
[sg.Text('行列 3', text_color='white', background_color='blue'), sg.Input('可输入文本框 2')]]
layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item 3'), select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE, size=(20,3)), sg.Column(col, background_color='blue')],
[sg.Input('别担心,这只是一个小测试。')],
[sg.OK()]]
window = sg.Window('列表控件例子').Layout(layout)
event, values = window.Read()
window.Close()
sg.Popup(event, values, line_width=200)
'OK'
其运行图如下所示:
当用户点击列表控件中的某一项时,在界面关闭后,程序将弹出一个对话框来显示用户的点击选项,这种方式展示了如何获取列表值:
10.3一个简易计算器例子
在学习了这么多例子之后,我们来完成一个简易计算器的例子,这个例子可以完成整数的加减乘除运算,具体代码如下:
import PySimpleGUI as sg
def jisuanqi():
layout = [[sg.Text("计算器")],
[sg.InputText(do_not_clear=True, size=(19,3), key="_SHU1_",font=('Helvetica', 20))],
]
#将0~9及加减乘除排列成四行四列
lst0_9 = [1,2,3,"+",4,5,6,"-",7,8,9,"×","",0,"","÷"]
tmp_shuzifuhao_lst = []
for item in lst0_9:
tmp_shuzifuhao_lst.append(sg.Button(str(item),button_color=('black', 'orange'), size=(4,2),font=('Helvetica', 18)))
if len(tmp_shuzifuhao_lst) == 4:
layout.append(tmp_shuzifuhao_lst)
tmp_shuzifuhao_lst = []
layout.append([sg.Button("计算", button_color=('white', 'springgreen4'), font=('黑体', 16), size=(7,2)),
sg.Button("清空",button_color=('white', 'springgreen4'), font=('黑体', 16),size=(7,2)),
sg.Button("退出",button_color=('white', 'springgreen4'), font=('黑体', 16),size=(7,2))])
window = sg.Window("计算器").Layout(layout)
while True:
button, values = window.Read()
if button is None or button=="退出":
break
elif button=="清空":
window.FindElement("_SHU1_").Update("")
elif button=="计算":
shu1 = values["_SHU1_"]
res_str = shu1.replace("×", "*")
res_str = res_str.replace("÷", "/")
try:
result = eval(res_str)
shu1 += "="
shu1 += str(result)
window.FindElement("_SHU1_").Update(shu1)
except:
window.FindElement("_SHU1_").Update("表达式有误,请查证!")
else:
shu1 = values["_SHU1_"]
if shu1 == "" and (button not in ["+","-","×","÷"]):
shu1 = button
elif shu1 == "" and (button in ["+","-","×","÷"]):
pass
else:
if shu1[-1] in ["+","-","×","÷"] and (button in ["+","-","×","÷"]):
pass
else:
shu1 += button
window.FindElement("_SHU1_").Update(str(shu1))
window.Close()
jisuanqi()
这段代码运行结果如图所示:
用户可以点击其中的数字及运算符号进行运算,代码并没有优化,有兴趣的读者可以仔细研究,在这段代码中用到了控件的更新、控件的排列、控件背景的设置等等。
现在为止,界面设计中的一些控件已经介绍了一部分,如果有兴趣,可以到其官方网站查阅相应的文档,下一章节,我们来研究一下菜单的设计。
十一、菜单的创建
11.1简述
从 windows1.0
开始,计算机的图形用户界面就开始快速发展起来了,附加而来的许多标配元素也慢慢让几乎所有用户熟悉了,在许多人看来,一个图形界面带有菜单栏是再正常不过的一件事,那么,菜单栏究竟是什么东西呢?其实究其实际,它也并不会比一个普通的按钮有多高明的地方,只不过菜单栏往往是一组按钮,一般附加在窗体的正上方,而且其呈一行式排列,当用户点击其一时,它往往会呈抽屉式弹出一条菜单来,当然那只是诸多不同功能按钮的集合罢了。
11.2菜单在PySimpleGUI中的实现
在 PySimpleGUI
中,菜单是与窗体的创建分离开的,要创建一个菜单十分容易,和创建窗体的语法十分相似,即先定义一个列表,然后调用PySimpleGUI
的Menu
函数将该列表填入即可,当创建窗体时,将该Menu
语句创建的菜单放置于窗体的第一行,其余就和前面创建窗体的方法一样了。
11.3一个菜单的小例子
下面展示了一个简单的创建菜单的小例子:
import PySimpleGUI as sg
sg.ChangeLookAndFeel('LightGreen')
sg.SetOptions(element_padding=(0, 0))
# ------ Menu Definition ------ #
menu_def = [['File', ['Open', 'Save', 'Exit' ]],
['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
['Help', 'About...'], ]
# ------ GUI Defintion ------ #
layout = [
[sg.Menu(menu_def, )],
[sg.Output(size=(60, 20))]
]
window = sg.Window("Windows-like program", default_element_size=(12, 1), auto_size_text=False, auto_size_buttons=False,
default_button_element_size=(12, 1)).Layout(layout)
# ------ Loop & Process button menu choices ------ #
while True:
event, values = window.Read()
if event == None or event == 'Exit':
break
print('Button = ', event)
# ------ Process menu choices ------ #
if event == 'About...':
sg.Popup('About this program', 'Version 1.0', 'PySimpleGUI rocks...')
elif event == 'Open':
filename = sg.PopupGetFile('file to open', no_window=True)
print(filename)
window.Close()
其运行截图如下图所示:
当用户点击其中的菜单按钮时,凡是有定义的菜单都会做出相应的反应。
如果用户在创建菜单时,在Menu
函数的参数中添加tearoff=True
时,再次运行程序,点击菜单时会发现在每个弹出的菜单下有条虚线,如图所示:
如果用户用鼠标双击这条虚线,该弹出的菜单将会自动独立飞出成为悬浮于主窗体的一个小窗体,相当酷。如下图所示:
11.4小结
这段内容简单介绍了菜单如何创建,有兴趣的读者仔细研究这段代码,自然会发现许多有趣的东西。
十二、绘图功能的研究
12.1简述
绘图是图形用户界面的一种最常用功能之一,将复杂的几何图形、建筑设计图、天体运行轨迹等等展示在图形界面上,无疑会更便于观察,这次我们就来研究一下PySimpleGUI
中有关绘图的部分,在这部分,该包是直接调用Tkinter
中有关图形绘制函数的,所以如果直接将PySimpleGUI
更换为不同类库PySimpleGUIQt
时,程序会报错。
当然,图形的绘制我们在另一个有关pygame
的教程中会详细介绍更有效率移动图形的方法,在这一篇中,我们只是简单做一尝试即可。
12.2一个简单的示例
对于一个细节的研究,最好的办法莫过于研究一段程序,下面的程序展示了如何利用Canvas
函数来创建一个画布。
import PySimpleGUI as sg
layout = [
[sg.Canvas(size=(100, 100), background_color='red', key= 'canvas')],
[sg.T('改变圆的颜色:'), sg.Button('红色'), sg.Button('蓝色')]
]
window = sg.Window('画布测试')
window.Layout(layout)
window.Finalize()
canvas = window.FindElement('canvas')
cir = canvas.TKCanvas.create_oval(50, 50, 100, 100)
while True:
event, values = window.Read()
if event is None:
break
if event == '蓝色':
canvas.TKCanvas.itemconfig(cir, fill="Blue")
elif event == '红色':
canvas.TKCanvas.itemconfig(cir, fill="Red")
window.Close()
上段代码运行如下图所示:
当用户点击设置不同颜色的按钮时,图中绘制的圆形会改变不同的颜色,比如用户点击蓝色按钮时,圆形填充蓝色:
从以上代码可以看出一个画布如何创建,用TKcanvas
如何进行图形绘制。
12.3Graph
的使用
在图形绘制时,还可以使用另一个函数即Graph,这个函数本身就创建一个画布,在该画布上也可以绘制各种图形,下面这段代码演示了如何用该函数来创建图形,有兴趣的读者可以对两者进行比较。
import PySimpleGUI as sg
layout = [
[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0,0), graph_top_right=(400, 400), background_color='red', key='graph')],
[sg.T('改变颜色:'), sg.Button('红色'), sg.Button('蓝色'), sg.Button('移动')]
]
window = sg.Window('图形测试')
window.Layout(layout)
window.Finalize()
graph = window.FindElement('graph')
circle = graph.DrawCircle((75,75), 25, fill_color='black',line_color='white')
point = graph.DrawPoint((75,75), 10, color='green')
oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' )
rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' )
line = graph.DrawLine((0,0), (100,100))
while True:
event, values = window.Read()
if event is None:
break
if event is '蓝色':
graph.TKCanvas.itemconfig(circle, fill = "Blue")
elif event is '红色':
graph.TKCanvas.itemconfig(circle, fill = "Red")
elif event is '移动':
graph.MoveFigure(point, 10,10)
graph.MoveFigure(circle, 10,10)
graph.MoveFigure(oval, 10,10)
graph.MoveFigure(rectangle, 10,10)
window.Close()
程序运行图如下:
当用户点击移动时,会发现我们创建的几个图形元素开始移动,但是细心的同学将会发现我们用DrawPoint创建的点图形不会移动:
如果你来观察其命令行时,会发现有这个警告输出:
仔细思考后,会察觉应该是该包本身的函数Drawpoint
中应该有些问题。由于该包正在快速迭代开发中,其有些小问题是可以理解的。
但是遇到问题我们就要来查找,通过对程序的调试,发列在PySimpleGUI
的源代码中Drawpoint
这个函数在返回时,没有返回所创建的对象id
,所以造成创建成功后只返回了None
,于是该对象无法移动,代码截图如下所示:
将id的返回值添加上去后,如图所示:
再来运行一下程序,就发现问题解决了。
12.4小结
本段对PySimpleGUI
的绘图功能进行了介绍,通过查看源代码我们也可以看到,在PySimpleGUI
中的Graph
函数其实也只是对Canvas
的一种封装,同时遇到问题无须担心,只要按图索骥肯定可以找到问题并解决它。
十三、多页面控件和程序打包
13.1简述
到目前为止,我们已经介绍了PySimpleGUI
中大多数控件,也熟悉了用PySimpleGUI
来开发一个用户界面的方法,这一系列的教程到此基本上也该结束了,在最后,这一段中再介绍一种多页面控件的使用方法。
在一个程序开发好以后,最后的环节是发布,本段也将对如何发布一个软件做一个简单的介绍。
13.2TabGroup
控件的使用
在许多软件中,我们看到过多页面的排列方式,即将功能类似的一些设置放在同一个页面,而为了更紧凑地集合程序,有时我们会将同一大类的功能按其小功能分类列于同一个窗体上,这就要用到多页面控件。
创建一个多页面控件是相当容易的,下面代码展示了一个简单多页面的创建:
import PySimpleGUI as sg
tab1_layout = [[sg.T('第一个页面的文本')]]
tab2_layout = [[sg.T('第二个页面的文本')], [sg.In(key='in')]]
layout = [[sg.TabGroup([[sg.Tab('页面1', tab1_layout, tooltip='tip'), sg.Tab('页面2', tab2_layout)]], tooltip='TIP2')],
[sg.Button('读取数据')]]
window = sg.Window('多页面窗口', default_element_size=(20,1)).Layout(layout)
while True:
event, values = window.Read()
if event is None: # always, always give a way out!
break
print(event,values)
window.Close()
对代码的进一步分析,可以看出Tab函数
和Window函数
的调用类似,而TabGroup
的用法也如同Window
一样,熟悉前面教程的读者应该能很快领会这些代码的意思。
13.3打包发布程序
对于Windows
用户而言,创建一个不依赖于Python
环境而能运行的EXE文件
是必要的,事实上,对Python
程序打包是非常容易的一件事,只需要安装一个第三方包PyInstaller
即可,安装包的方法很容易,即用以下代码即可:
pip install PyInstaller
安装完毕后,可以在命令行下执行以下代码,即能生成可执行文件:
pyinstaller -wF tabex.py
在文件所在的目录下,以命令行运行上述命令后,将发会现当前目录下多了两个文件夹,一个是build
,一个是dist
,在dist目录
下,我们将找到生成的可执行文件,双击后可执行,如下图所示:
有关于PyInstaller
的打包参数了解,可以直接访问其官方网站:http://www.pyinstaller.org/
该网站有详细的打包过程描述。
14.4小结
止此,我们花了许多时间研究了PySimpleGUI
这个工具包,从各种界面程序的创建来看,其方式相当简单,对于界面编程的新手来说,这是非常好的入门工具,当然,该包目前正在快速迭代开发中,从客观上来说,将各种不同的界面工具包装成统一的接口是非常困难的事情,然而这些天才的程序员们还依然在为着这个伟大的理想努力奋斗着,我们目前只是工具的使用者,希望有一天,大家也能为该项目贡献出优秀的代码。
参考
- PySimpleGUI官方文档:https://pysimplegui.readthedocs.io/en/latest/
![](https://kz.cx/wp-content/uploads/2021/10/Pasted-11.png)