python是编程语言,如果要实现GUI窗体设计,还需要单独的GUI模块。 其余的编程语言里貌似除了微软系列产品外,其他的都得依赖于GUI设计软件。例如Java需要Swing工具包,go语言需要GTK等。对于Python而言,如果要开发窗体应用程序,除了借助于django或flask等与HTML前端技术结合外,同时也有一些GUI工具包可以使用,例如tkinter、wxpython,还可以使用pyQT来完成。
本篇以tkinter内置模块为例,介绍一下python窗体设计过程和方法。同时将以开发一个爬虫模块窗体实战为例,帮助大家快速上手实践。
(1)tkinter基本用法
tkinter是python内置的一个GUI开发模块,使用的时候直接使用import方法就可以导入该模块。
import tkinter
# 或者 from tkinter import *
如果有习惯读取该模块源代码的话,可以直接进入python安装目录下的Lib文件夹里,下面就是tkinter模块目录,打开后发现有如下py文件,功能简介如下:
直接使用idle就可以打开这些py文件,当然读取源代码来理解还是比较痛苦的,还是上手实践来得快。
在spyder开发页面内或者IDLE内输入如下代码,启动第一个hello窗口:
import tkinter #导入tkiner模块
root=tkinter.Tk() #实例化一个窗体对象
root.wm_title('hello,python') #设置该窗体的标题为hello,python
root.geometry('300x200') #设置窗体大小为宽300,高200,中间用键盘上的x表示
root.mainloop() #窗体对象运行,消息循环
运行后就会弹出第一个窗体hello:
这样我们就设计了一个窗体了。窗体也是一个二维平面容器,容器的意思就是里面可以放置其他的东西,对于窗体而言,可以依附在上的就是UI控件了。UI控件也就是窗体上那些基本元素,如标签、按钮、文本输入框、单选框、多选框、列表选择框、图片容器、表格等,英文名叫widgets。
如下窗体里就包括标签、按钮、输入框、列表选择框:
很显然,标签主要用于文本内容显示,如上面图片中的姓名、身高等信息;按钮就用于点击后形成触发事件,执行下一步的动作;输入框用于信息的输入,提供给使用者来进行输入;列表框、单选框、多选框属于同一类型,通过选择一种或者多种来准备下一步的事件。
对于python如果使用tkinter模块,上述图片中这些控件需要通过代码编写来实现,而不是如VB或者VC那样提供控件库,在窗体上拖拽即可快速实现布局。所以在实现控件的布局方面还是有一定挑战的,除了要使用代码生成这些控件外,还得使用代码来实现布局,也就是这些控件的摆放位置和顺序。好在是记住规律,多实践也能搞定。
(2)tkinter基本控件的绘制
下面我们来使用代码生成常用的控件,同时还需要使用布局方法。
绘制Label标签控件: Label(root)
标签是窗体中最基本的控件,用于清楚标注窗体各个组成部分的内容。在tkinter中采用Label函数来在窗体中设计标签,基本用法为:
label=Label( rootWindow) -- 括号中的参数表示在rootWindow窗体上绘制Label标签,返回一个标签对象。
标签的基本属性包括:text -- 标签内容 ; font -- 字体属性 ; fg -- 字体颜色 ; bg -- 背景颜色 ; image -- 背景图片。配置这些基本属性时,可以与绘制标签一起,也可以后续配置。如果与创建标签同时配置属性,写法为:
label=Label(rootWindow,text = 'hello', fg= 'red' )
也可以将创建过程与配置属性过程分离,即:
label=Label(rootWindow) label['text']='hello'
有了控件后,如何摆放布局也是需要设计的。在tkinter中有三种布局方法,pack、grid、place。
其中pack方法为自适应布局方法,即根据控件大小和顺序来自动实现布局,默认让一个控件独立占一行并居中显示,也可以加入参数调整位置,其基本用法为:
ui.pack() #ui为控件名
或者:
ui1.pack(side='LEFT') #设定ui显示在窗体左侧,这时右侧也需要有一个控件 ui2.pack(side='RIGHT')
grid方法为网格布局,即基于设定将窗体分割为相等大小的单元格,用行和列位置来布局控件,其基本用法为:
ui.grid(row=m,column=n) #m,n为设定控件所在的行数和列数位置
place方法为空间布局,可以自由设定控件元素的起始x和y坐标:
ui.place(x=100,y=100) #设定该控件左上角坐标为x:100,y:100
下面进入第一个示例事件,即在窗体上绘制一个标签,并设置基本属性和布局方式:
from tkinter import *
root=Tk()
root.wm_title('hello,python')
root.geometry('300x200')
#在窗体root上添加label标签
label=Label(root) #调用Label绘制函数,root参数为根窗体对象,即在root窗体上绘制label控件
label['text']='welcome to the first GUI program using python!' #设置text属性,即显示内容
label['font']=14 #设置font属性,包括字体大小、字体类型等
lable['fg']='red' #设置fg前景颜色,这里就是字体颜色
label.pack() #使用pack方法实现空间的自动布局
root.mainloop() #窗体消息循环,运行窗体对象
上述代码中使用Label函数绘制了一个标签,设置了标签的显示内容和字体大小。该标签是依附于root窗体中的,所以Label函数里窗体对象为root。标签内容显示使用其text属性key配置,字体大小使用font属性。创建了标签,还需要设置其显示的位置。这里先使用pack方法来实现空间的默认自动布局。运行该代码后,可以看到创建的标签自动居中显示:
绘制Button按钮控件: Button(root)
接下来采用相同的结构来创建一个按钮,按钮是使用频率最高的控件类型。有了按钮,就可以设置当点击按钮时将会执行的事件,也即响应事件。
对于按钮,其基本属性包括:
text--按钮显示文本;command--点击响应事件 ; fg-- 前景字体颜色 ; bg--背景颜色
如下代码:
from tkinter import *
root=Tk()
root.wm_title('hello,python')
root.geometry('300x200')
#在窗体root上添加label标签
label=Label(root)
label['text']='welcome to the first GUI program using python!'
label['font']=14 #标签字体大小
label['fg']='red' #标签文本颜色为红色
label.pack() #标签pack方法布局
#在窗体root上添加Button按钮,同时给定点击事件函数
def callback(): #定义回调函数,当点击按钮时,设置标签对象的文本颜色为蓝色
label['fg']='blue' #将label标签对象的文本设置为蓝色
btn=Button(root) #在root窗体上绘制一个Button按钮对象
btn['text']='点击我,标签字体颜色将变成蓝色哦' #按钮上的文本内容
btn['font']=14 #设置文本大小为14号字体
btn['fg']='white' #设置fg属性,按钮文本颜色为白色
btn['bg']='blue' #设置bg属性,按钮背景颜色为蓝色
btn['command']=callback #定义点击按钮时响应事件为callback函数
btn.pack() #设置按钮对象在窗体上的位置
root.mainloop() #运行窗体对象
代码中注解的还是比较详细,在按钮这块我们分了两部分:第一块为定义的按钮点击回调函数,也就是响应事件,使用command属性配置;第二块为绘制按钮及其布局,这里依然使用pack方法布局。注意的是按钮的回调函数需要写在绘制按钮代码块之前。后面我们将采用类的写法,可以将绘制和回调函数封装起来。执行上述代码,效果如下:
绘制文本框Entry: Entry(root)
上面介绍了绘制标签和按钮的基本方法,接下来我们换个例子引入文本输入框Entry对象控件。在许多软件或者app当访问时都需要用户注册或登录,其中控件类型就包括标签、输入框和按钮。基本布局如下:
如果要实现上述窗体设计,需要两个标签、两个文本输入框和两个按钮。
在tkinter中,输入框绘制采用Entry方法,将其依附于root窗体对象,即Entry(root)绘制一个输入框对象,所以使用: entry1= Entry(root) 生成输入框对象entry1,然后再配置entry1的必要属性,最后再进行entry1的布局。
对于输入框而言,输入文本内容后如何取得其中的值肯定是必须考虑的。tkinter中我们可以直接使用输入框对象entry1的get方法获得,即: input_str=entry1.get()。如果想清除文本框的输入,可以使用entry1对象的delete方法,使用格式为: entry1.delete(0,n),式中的n为结束的字符位置,0为输入框中的起始字符。如果要全部清除,直接将n替换为END即可。即:entry1.delete(0,END), 为整个清除。
由于需要多个控件合理并排排列,因此这里选用了另外一种布局方式,就是grid方法,见名知意,grid就是网格。网格就会使用行和列来布局,在使用时就必须给定行和列的位置,即:grid(row=m,column=n),这里的m和n为单元格所在的行和列的位置。tkinter会自动根据当前窗体大小,依据设定的行和列总数将窗体分割为设定的网格,然后依据网格控件的布局来完成内容和组件的显示。
结合前面介绍的绘制标签和绘制按钮的方法,我们可以先采用顺序方式组织代码完成这个登录界面的开发:
from tkinter import *
root=Tk()
#1.绘制两个标签说明及两个文本输入框
#采用grid布局,可以使标签与文本输入框并排显示,
#row=0,column=0,表示第一行第一格,这里放置第一个用户名标签
#row=0,column=1,表示第一行第二格,这里放置第一个文本输入框
#row=1,column=0,表示第二行第一格,这里放置第二个用户密码标签
#row=1,column=1,表示第二行第二格,这里放置第二个文本输入框
label=Label(root)
label['text']='用户名'
label['font']=14
label.grid(row=0,column=0)
entry_name=Entry(root) #Entry为输入框控件绘制方法,在root窗体上绘制
entry_name.grid(row=0,column=1)
label=Label(root)
label['text']='用户密码'
label['font']=14
label.grid(row=1,column=0)
entry_pwd=Entry(root) #Entry为输入框控件绘制方法,在root窗体上绘制
entry_pwd.grid(row=1,column=1)
#定义点击按钮响应函数
def toLogin(): #点击登录按钮时的响应事件
name=entry_name.get() #获取用户名右侧文本输入框的输入值,采用get方法
pwd=entry_pwd.get() #获取用户密码右侧文本输入框的输入值,采用get方法
if name=='admin' and pwd=='admin': #用户名和密码验证
print('welcome to my app, dear'+name)
else:
print('your information is error!')
def toReset(): #点击重置按钮时的响应事件
entry_name.delete(0,END) #清空输入框内容使用delete方法,参数为0,END,表示整个输入框都清空
entry_pwd.delete(0,END) #清空密码输入框内容
#绘制点击登录和重置按钮
btn_log=Button(root)
btn_log['text']='登录'
btn_log['font']=14
btn_log['fg']='yellow'
btn_log['bg']='green'
btn_log['padx']=15
btn_log['command']=toLogin #登录按钮的响应函数为toLogin
btn_log.grid(row=2,column=0) #表示第三行第一格,这里放置第1个登录按钮
btn_reset=Button(root)
btn_reset['text']='重填'
btn_reset['font']=14
btn_reset['padx']=15
btn_reset['fg']='white'
btn_reset['bg']='green'
btn_reset['command']=toReset #重置按钮的响应函数为toReset
btn_reset.grid(row=2,column=1) #表示第三行第二格,这里放置第2个重填按钮
root.mainloop() #运行窗体对象
执行上述代码,可以获得如下效果:
这个效果没有设计图那样美观,但基本实现了当初的设计布局。但我们再回过去看上述实现代码时按顺序组织下来会觉得比较混乱,案例中还只是非常简单控件组合,如果有更多的控件,这样组织代码肯定不容易理解。因此还需要使用类的编写方法将代码进行封装,便于阅读和理解。上述案例代码可以改写如下:
# -*- coding: utf-8 -*-
"""
Created on Sat Nov 23 19:57:56 2019
@author: caojianhua
"""
from tkinter import *
class myWindow():
#定义构造函数,绘制窗体及控件
def __init__(self,master=None):
self.master=master
self.master.wm_title('hello,python')
self.master.geometry('300x200')
self.createWidgets()
#定义绘制控件函数
def createWidgets(self):
label=Label(self.master)
label['text']='用户名'
label['font']=14
label.grid(row=0,column=0)
entry_name=Entry(self.master)
entry_name.grid(row=0,column=1)
label=Label(self.master)
label['text']='用户密码'
label['font']=14
label.grid(row=1,column=0)
entry_pwd=Entry(self.master)
entry_pwd.grid(row=1,column=1)
btn_log=Button(self.master)
btn_log['text']='登录'
btn_log['font']=14
btn_log['fg']='yellow'
btn_log['bg']='green'
btn_log['padx']=15
btn_log['command']=self.toLogin
btn_log.grid(row=2,column=0)
btn_reset=Button(self.master)
btn_reset['text']='重填'
btn_reset['font']=14
btn_reset['padx']=15
btn_reset['fg']='white'
btn_reset['bg']='green'
btn_reset['command']=self.toReset
btn_reset.grid(row=2,column=1)
#定义点击按钮回调函数
def toLogin(self):
name=entry_name.get() #获取文本输入框的get方法
pwd=entry_pwd.get()
if name=='admin' and pwd=='admin':
print('welcome to my app, dear'+name)
else:
print('your information is error!')
def toReset(self):
entry_name.delete(0,END) #清空输入框内容使用delete方法
entry_pwd.delete(0,END)
#实例化一个窗体及控件组合
root=Tk()
UserLogWindow=myWindow(root)
root.mainloop()
选择框在窗体设计中也很常见,选择框类型包括单选、多选和列表选择。选择框是给定选项值,选中时获取选项的值为后续操作做准备。这三种类型基本处理方法一样,所以放在一起来进行实践体验。
绘制单选框Radiobutton
Radiobutton单选框:与普通按钮不同,单选框需要在选择后直接获取设置的内容选项,如点击A选择,就获得A选项的值,不过也有其他类型的触发响应。其创建方法与前述控件思路一致,使用Radiobutton函数,将单选框依附于窗体即可,如下:
radioChoice=Radiobutton(rootWindow) radioChoce.pack()
然后其属性包括text--选项显示内容,value -- 选项自带的值,variable -- 选项变量参数,command -- 选中时响应事件。其中variable参数较为特殊,可以在外部设定值与其关联,然后在响应事件中通过该参数获取单选按钮的value值。
variable主要用于传参和绑定变量。主要参数有:variable
, textvariable
, onvalue
, offvalue
, value
。他是双向绑定的,也就是说如果该变量发生变化,随之绑定的控件也会变化,与他保持一致。常用的variable变量有:
x = StringVar() 保存一个 string 类型变量, 默认值为"" x = IntVar() 保存一个整型变量, 默认值为0 x = DoubleVar() 保存一个浮点型变量,默认值为0.0 x = BooleanVar() 保存一个布尔型变量,返回值为0表示假,1表示真
操作时主要包括两种:
设置他的值,用set()方法,即:x.set() 得到他的值,用get()方法,即:x.get()
下面通过代码实践来理解:
from tkinter import *
root=Tk()
root.geometry('300x400')
root.wm_title('hello,python')
label=Label(root,text='下列哪门编程语言是你最喜欢的?请选择:')
label.pack()
#准备好单选选择项,一般使用列表来设置
choiceList=[('python','A'),('C++','B'),('VB','C'),('GO','D')]
#使用tkinter中的StringVar类设定选项所在的值,将其与单选框中的variable绑定关联,当选择某一项时,
#可以使用get方法来获得该项的value值。
v=StringVar() #观察在单选按钮属性中variable属性值也为v,这两者进行关联绑定
v.set('A') #将单选按钮默认选择value为A的项,这里为第一项
#定义单选按钮回调函数
def callback():
choice=v.get() #获得选择项中的value值
label1['text']="您的选项为: " + choice #将label1的text属性设置为选项的值
label1['fg']='red' #设置label1的文本颜色为红色
#采用循环来设定单选按钮
for item,value in choiceList:
radioChoice=Radiobutton(root)
radioChoice['text']=item #设定单选按钮后面的内容
radioChoice['variable']=v #设定单选按钮变量v
radioChoice['value']=value #设定单选按钮自带的值
radioChoice['command']=callback #设定单选按钮回调函数
radioChoice.pack() #采用pack方法布局
#绘制一个标签用于显示选中的项
label1=Label(root)
label1['text']="您的选项为: "
label1.pack()
root.mainloop() #窗体消息循环
当运行代码时效果如图:
绘制多选框Checkbutton
复选框绘制方法与单选框思路是一样的,即使用Checkbutton类:checkbox=Checkbutton(rootWindow)
其属性设置与单选框也基本类似,不过此时复选框里的variable参数需要各个选项不一致,也就是单独设置variable,比较麻烦。
如下实践代码:
from tkinter import *
root=Tk()
root.geometry('300x400')
root.wm_title('hello,python')
label=Label(root,text='下列哪几门编程语言是你最喜欢的?请选择:')
label.pack()
#准备好多项选择项,使用列表来设置
choiceList=[('python','A'),('C++','B'),('VB','C'),('GO','D')]
#使用tkinter中的BooleanVar类设定选项所在的值,将其与复选框中的variable绑定关联,当选择某一项时,
#可以使用get方法来获得该项的value值。这里由于每一项都有选与不选两种状态,因此如果要获得状态值,
#需要对每一个选项单独设置variable值,如下v0,v1,v2,v3等:
v0=BooleanVar()
v1=BooleanVar()
v2=BooleanVar()
v3=BooleanVar()
def callback():
choice=''
if v0.get()==True:
choice+=choiceList[0][1]
if v1.get()==True:
choice+=choiceList[1][1]
if v2.get()==True:
choice+=choiceList[2][1]
if v3.get()==True:
choice+=choiceList[3][1]
label1['text']=choice
label1['fg']='red'
#多项选择创建及布局
choice1=Checkbutton(root,text=choiceList[0][0],variable=v0,command=callback)
choice2=Checkbutton(root,text=choiceList[1][0],variable=v1,command=callback)
choice3=Checkbutton(root,text=choiceList[2][0],variable=v2,command=callback)
choice4=Checkbutton(root,text=choiceList[3][0],variable=v3,command=callback)
choice1.pack()
choice2.pack()
choice3.pack()
choice4.pack()
#显示选中内容
label1=Label(root)
label1.pack()
root.mainloop()
运行后效果如图: