综合了以下的文章:
转载自:https://www.cnblogs.com/shwee/p/9427975.html
转载自:https://www.jianshu.com/p/91844c5bca78
当前流行的计算机桌面应用程序大多数为图形化用户界面(Graphic User Interface,GUI),即通过鼠标对菜单、按钮等图形化元素触发指令,并从标签、对话框等图型化显示容器中获取人机对话信息。
Python自带了tkinter 模块,实质上是一种流行的面向对象的GUI工具包 TK 的Python编程接口,提供了快速便利地创建GUI应用程序的方法。其图像化编程的基本步骤通常包括:
Tkinter 是使用 python 进行窗口视窗设计的模块。Tkinter模块(“Tk 接口”)是Python的标准Tk GUI工具包的接口。作为 python 特定的GUI界面,是一个图像的窗口,tkinter是python 自带的,可以编辑的GUI界面,我们可以用GUI 实现很多直观的功能,比如想开发一个计算器,如果只是一个程序输入,输出窗口的话,是没有用户体验的。所有开发一个图像化的小窗口,就是必要的。
对于稍有GUI编程经验的人来说,Python的Tkinter界面库是非常简单的。python的GUI库非常多,选择Tkinter,一是最为简单,二是自带库,不需下载安装,随时使用,三则是从需求出发,Python作为一种脚本语言,一种胶水语言,一般不会用它来开发复杂的桌面应用,它并不具备这方面的优势,使用Python,可以把它作为一个灵活的工具,而不是作为主要开发语言,那么在工作中,需要制作一个小工具,肯定是需要有界面的,不仅自己用,也能分享别人使用,在这种需求下,Tkinter是足够胜任的!
这篇文章主要做一个简单概述和实践编程,对于从没有接触过GUI的新手,在脑中树立一个基本的界面编程概念,同时自己也能学会如何简单的实现一些小的图形窗口功能。
对于Tkinter编程,可以用两个比喻来理解:
第一个,作画。我们都见过美术生写生的情景,先支一个画架,放上画板,蒙上画布,构思内容,用铅笔画草图,组织结构和比例,调色板调色,最后画笔勾勒。相应的,对应到tkinter编程,那么我们的显示屏就是支起来的画架,根窗体就是画板,在tkinter中则是Toplevel,画布就是tkinter中的容器(Frame),画板上可以放很多张画布(Convas),tkinter中的容器中也可以放很多个容器,绘画中的构图布局则是tkinter中的布局管理器(几何管理器),绘画的内容就是tkinter中的一个个小组件,一幅画由许多元素构成,而我们的GUI界面,就是有一个个组件拼装起来的,它们就是widget。
第二个,我们小时候都玩过积木,只要发挥创意,相同的积木可以堆出各种造型。tkinter的组件也可以看做一个个积木,形状或许不同,其本质都是一样的,就是一个积木,不管它长什么样子,它始终就是积木!所以这些小组件都有许多共性,另外,个人认为,学习界面编程,最重要的不是一开始学习每个积木的样子,不是学习每个组件怎么用,而是这些组件该怎么放。初始学习中,怎么放远远比怎么用重要的多。网上有大量的文章资料,基本全是介绍组件怎么用的,对于怎么放,也就是tkinter中的布局管理器,都是一笔带过,这对初学者有点本末倒置,或许绝大部分是转载的原因吧,极少是自己真正写的。组件怎么用不是最迫切的,用到的时候再去了解也不迟,边用边学反而更好。因此我将专门写一章,详细介绍布局管理器的使用。
根窗体是图像化应用程序的根控制器,是tkinter的底层控件的实例。当导入tkinter模块后,调用 Tk()方法可初始化一个根窗体实例 root ,用 title() 方法可设置其标题文字,用geometry()方法可以设置窗体的大小(以像素为单位)。将其置于主循环中,除非用户关闭,否则程序始终处于运行状态。执行该程序,一个窗体就呈现出来了。在这个主循环的根窗体中,可持续呈现中的其他可视化控件实例,监测事件的发生并执行相应的处理程序。下面是根窗体呈现示例:
from tkinter import *
root = Tk() # 调用Tk()方法初始化一个根窗体实例
root.title('我的第一个Python窗体') # title()方法设置标题文字
root.geometry('240x240') # geometry()方法设置窗体大小,这里的乘号不是*,而是小写英文字母x
root.mainloop()
在窗体上呈现的可视化控件,通常包括尺寸、颜色、字体、相对位置、浮雕样式、图标样式和悬停光标形状等共同属性。不同的控件由于形状和功能不同,又有其特征属性。在初始化根窗体和根窗体主循环之间,可实例化窗体控件,并设置其属性。父容器可为根窗体或其他容器控件实例。常见的控件共同属性如下表:
属性 | 说明 | 取值 |
---|---|---|
anchor | 文本起始位置 | CENTER(默认),E,S,W,N,NE,SE,SW,NW |
bg | 背景色 | 无 |
bd | 加粗(默认 2 像素) | 无 |
bitmap | 黑白二值图标 | 网上查找 |
cursor | 鼠标悬停光标 | 网上查找 |
font | 字体 | 无 |
fg | 前景色 | 无 |
height | 高(文本控件的单位为行,不是像素) | 无 |
image | 显示图像 | 无 |
justify | 多行文本的对其方式 | CENTER(默认),LEFT,RIGHT,TOP,BOTTOM |
padx | 水平扩展像素 | 无 |
pady | 垂直扩展像素 | 无 |
relief | 3D浮雕样式 | FLAT,RAISED,SUNKEN,GROOVE,RIDGE |
state | 控件实例状态是否可用 | NORMAL(默认),DISABLED |
width | 宽(文本控件的单位为行,不是像素) | 无 |
标签及常见属性示例:
from tkinter import *
root = Tk()
lb = Label(root, text = '我是第一个标签', \
bg = '#d3fbfb', \
fg = 'red', \
font = ('华文新魏', 32), \
width = 20, \
height = 2, \
relief = SUNKEN)
lb.pack()
root.mainloop()
控件的布局通常有pack()、grid() 和 place() 三种方法:
pack()方法是一种简单的布局方法,如果不加参数的默认方式,将按布局语句的先后,以最小占用空间的方式自上而下地排列控件实例,并且保持控件本身的最小尺寸。如下的例子:
from tkinter import *
root = Tk()
lbred = Label(root, text = 'Red', fg = 'Red', relief = GROOVE)
lbred.pack()
lbgreen = Label(root,text="绿色",fg="green",relief=GROOVE)
lbgreen.pack()
lbblue = Label(root,text="蓝",fg="blue",relief=GROOVE)
lbblue.pack()
root.mainloop()
from tkinter import *
root = Tk()
lbred = Label(root,text="Red",fg="Red",relief=GROOVE)
lbred.pack()
lbgreen = Label(root,text="绿色",fg="green",relief=GROOVE)
lbgreen.pack(side=RIGHT) # 本控件的布局相对于下一个控件的方位
lbblue = Label(root,text="蓝",fg="blue",relief=GROOVE)
lbblue.pack(fill=X) # 控件向水平方向填充未被占用控件
root.mainloop()
grid()方法:是基于网格的布局。先虚拟一个二维表格,再在该表格中布局控件实例。由于在虚拟表格的单元中所布局的控件实例大小不一,单元格也没有固定或均一的大小,因此其仅用于布局的定位。pack()方法与grid()方法不能混合使用。
grid()方法常用布局参数如下:
参数 | 详解 |
---|---|
column | 控件实例的起始列,最左边为第0列 |
columnspan | 控件实例所跨越的列数,默认为1列 |
ipadx,ipady | 控件实例所呈现区域内部的像素数,用来设置控件实例的大小 |
padx,pady | 控件实例所占据空间像素数,用来设置实例所在单元格的大小 |
row | 控件实例的起始行,最上面为第0行 |
rowspan | 控件实例的起始行数,默认为1行 |
看下面的例子:用grid()方法排列标签,设想有一个3x4的表格,起始行、列序号均为0。将标签lbred 至于第2列第0行;将标签lbgreen置于第0列第1行;将标签lbblue置于第1列起跨2列第2行,占20像素宽。
from tkinter import *
# 虚拟一个二维表格
root = Tk()
lbred = Label(root,text="Red",fg="Red",relief=GROOVE)
lbred.grid(column=2,row=0)
lbgreen = Label(root,text="绿色",fg="green",relief=GROOVE)
lbgreen.grid(column=0,row=1)
lbblue = Label(root,text="蓝",fg="blue",relief=GROOVE)
lbblue.grid(column=1,columnspan=2,ipadx=20,row=2)
root.mainloop()
place()方法:根据控件实例在父容器中的绝对或相对位置参数进行布局。其常用布局参数如下:
参数 | 详解 |
---|---|
x,y | 控件实例在根窗体中水平和垂直方向上的其实位置(单位为像素)。注意,根窗体左上角为0,0,水平向右,垂直向下为正方向 |
relx,rely | 控件实例在根窗体中水平和垂直方向上起始布局的相对位置。即相对于根窗体宽和高的比例位置,取值在0.0~1.0之间 |
height,width | 控件实例本身的高度和宽度(单位为像素) |
relheight,relwidth | 控件实例相对于根窗体的高度和宽度比例,取值在0.0~1.0之间 |
利用place()方法配合relx,rely和relheight,relwidth参数所得的到的界面可自适应根窗体尺寸的大小。place()方法与grid()方法可以混合使用。如下例子:利用place()方法排列消息(多行标签)。
from tkinter import *
root = Tk()
root.geometry('320x240')
msg1 = Message(root,text='''我的水平起始位置相对窗体 0.2,垂直起始位置为绝对位置 80 像素,我的高度是窗体高度的0.4,宽度是200像素''',relief=GROOVE)
msg1.place(relx=0.2,y=80,relheight=0.4,width=200)
root.mainloop()
文本的输入与输出控件通常包括:标签(Label)、消息(Message)、输入框(Entry)、文本框(Text)。他们除了前述共同属性外,都具有一些特征属性和功能。
标签(Label)和 消息(Message):除了单行与多行的不同外,属性和用法基本一致,用于呈现文本信息。值得注意的是:属性text通常用于实例在第一次呈现时的固定文本,而如果需要在程序执行后发生变化,则可以使用下列方法之一实现:
看下面的一个例子:制作一个电子时钟,用root的after()方法每隔1秒time模块以获取系统当前时间,并在标签中显示出来。
import tkinter
import time
def gettime():
timestr = time.strftime("%H:%M:%S") # 获取当前时间并转换为字符串
lb.configure(text = timestr) # 重新设置标签文本
root.after(1000, gettime) # 每隔1S调用函数 gettime 自身获取时间
root = tkinter.Tk()
root.title('时钟')
lb = tkinter.Label(root, text = '', fg = 'blue', font = ("黑体", 80))
lb.pack()
gettime()
root.mainloop()
import tkinter
import time
def gettime():
var.set(time.strftime("%H:%M:%S")) # 获取当前时间
root.after(1000,gettime) # 每隔1s调用函数 gettime 自身获取时间
root = tkinter.Tk()
root.title('时钟')
var=tkinter.StringVar()
lb = tkinter.Label(root,textvariable=var,fg='blue',font=("黑体",80))
lb.pack()
gettime()
root.mainloop()
文本框的常用方法如下:
方法 | 功能 |
---|---|
delete (起始位置,[,终止位置]) | 删除指定区域文本 |
get(起始位置,[,终止位置]) | 获取指定区域文本 |
insert(位置,[,字符串]…) | 将文本插入到指定位置 |
see(位置) | 在指定位置是否可见文本,返回布尔值 |
index(标记) | 返回标记所在的行和列 |
mark_names() | 返回所有标记名称 |
mark_set(标记,位置) | 在指定位置设置标记 |
mark_unset(标记) | 去除标记 |
上表位置的取值可为整数,浮点数或END(末尾),例如0.0表示第0列第0行如下一个例子:每隔1秒获取一次当前日期的时间,并写入文本框中,如下:本例中调用 datetime.now()获取当前日期时间,用insert()方法每次从文本框txt的尾部(END)开始追加文本。
from tkinter import *
import time
import datetime
def gettime():
s=str(datetime.datetime.now())+'\n'
txt.insert(END,s)
root.after(1000,gettime) # 每隔1s调用函数 gettime 自身获取时间
root=Tk()
root.geometry('320x240')
txt=Text(root)
txt.pack()
gettime()
root.mainloop()
输入框(Entry):通常作为功能比较单一的接收单行文本输入的控件,虽然也有许多对其中文本进行操作的方法,但通常用的只有取值方法get()和用于删除文本的delete(起始位置,终止位置),例如:清空输入框为delete(0,END)。
需要用户输入用户信息时,比如我们平时使用软件、登录网页时,用户交互界面让我们登录账户信息等时候可以用到。
from tkinter import *
root = Tk()
root.title('My Window')
root.geometry('500x300')
e1 = Entry(root, show='*', font=('Arial', 14)) # 显示成密文形式
e1.pack()
e2 = Entry(root, show=None, font=('Arial', 14)) # 显示成明文形式
e2.pack()
root.mainloop()
按钮(Button):主要是为响应鼠标单击事件触发运行程序所设的,故其除控件共有属性外,属性command是最为重要的属性。通常,将按钮要触发执行的程序以函数形式预先定义,然后可以用一下两种方法调用函数。Button按钮的状态有:‘normal’,‘active’,‘disabled’
看下面的例子:
from tkinter import *
def run1():
a = float(inp1.get())
b = float(inp2.get())
s = '%0.2f+%0.2f=%0.2f\n' % (a, b, a + b)
txt.insert(END, s) # 追加显示运算结果
inp1.delete(0, END) # 清空输入
inp2.delete(0, END) # 清空输入
def run2(x, y):
a = float(x)
b = float(y)
s = '%0.2f+%0.2f=%0.2f\n' % (a, b, a + b)
txt.insert(END, s) # 追加显示运算结果
inp1.delete(0, END) # 清空输入
inp2.delete(0, END) # 清空输入
root = Tk()
root.geometry('460x240')
root.title('简单加法器')
lb1 = Label(root, text='请输入两个数,按下面两个按钮之一进行加法计算')
lb1.place(relx=0.1, rely=0.1, relwidth=0.8, relheight=0.1)
inp1 = Entry(root)
inp1.place(relx=0.1, rely=0.2, relwidth=0.3, relheight=0.1)
inp2 = Entry(root)
inp2.place(relx=0.6, rely=0.2, relwidth=0.3, relheight=0.1)
# 方法-直接调用 run1()
btn1 = Button(root, text='方法一', command=run1)
btn1.place(relx=0.1, rely=0.4, relwidth=0.3, relheight=0.1)
# 方法二利用 lambda 传参数调用run2()
btn2 = Button(root, text='方法二', command=lambda: run2(inp1.get(), inp2.get()))
btn2.place(relx=0.6, rely=0.4, relwidth=0.3, relheight=0.1)
# 在窗体垂直自上而下位置60%处起,布局相对窗体高度40%高的文本框
txt = Text(root)
txt.place(rely=0.6, relheight=0.4)
root.mainloop()
单选按钮(Radiobutton):是为了响应故乡排斥的若干单选项的单击事件以触发运行自定义函数所设的,该控件排除具有共有属性外,还具有显示文本(text)、返回变量(variable)、返回值(value)、响应函数名(command)等重要属性。响应函数名“command=函数名”的用法与Button相同,函数名最后也要加括号。返回变量variable=var通常应预先声明变量的类型var=IntVar()或var=StringVar(),在所调用的函数中方可用var.get()方法获取被选中实例的value值。例如下面:
from tkinter import *
def Mysel():
dic = {0:'甲',1:'乙',2:'丙'}
s = "您选了" + dic.get(var.get()) + "项"
lb.config(text = s)
root = Tk()
root.title('单选按钮')
lb = Label(root)
lb.pack()
var = IntVar()
rd1 = Radiobutton(root,text="甲",variable=var,value=0,command=Mysel)
rd1.pack()
rd2 = Radiobutton(root,text="乙",variable=var,value=1,command=Mysel)
rd2.pack()
rd3 = Radiobutton(root,text="丙",variable=var,value=2,command=Mysel)
rd3.pack()
root.mainloop()
复选框(Checkbutton) :是为了返回多个选项值的交互控件,通常不直接触发函数的执行。该控件除具有共有属性外,还具有显示文本(text)、返回变量(variable)、选中返回值(onvalue)和未选中默认返回值(offvalue)等重要属性。返回变量variable=var 通常可以预先逐项分别声明变量的类型var=IntVar() (默认)或 var=StringVar(), 在所调用的函数中方可分别调用 var.get()方法 取得被选中实例的 onvalue或offvalue值。复选框实例通常还可分别利用 select()、deselect()和 toggle() 方法对其进行选中、清除选中和反选操作。
如下的例子: 利用复选框实现,单击OK,可以将选中的结果显示在标签上。效果如下:
from tkinter import *
import tkinter
def run():
if(CheckVar1.get()==0 and CheckVar2.get()==0 and CheckVar3.get()==0 and CheckVar4.get()==0):
s = '您还没选择任何爱好项目'
else:
s1 = "足球" if CheckVar1.get()==1 else ""
s2 = "篮球" if CheckVar2.get() == 1 else ""
s3 = "游泳" if CheckVar3.get() == 1 else ""
s4 = "田径" if CheckVar4.get() == 1 else ""
s = "您选择了%s %s %s %s" % (s1,s2,s3,s4)
lb2.config(text=s)
root = tkinter.Tk()
root.title('复选框')
lb1=Label(root,text='请选择您的爱好项目')
lb1.pack()
CheckVar1 = IntVar()
CheckVar2 = IntVar()
CheckVar3 = IntVar()
CheckVar4 = IntVar()
ch1 = Checkbutton(root,text='足球',variable = CheckVar1,onvalue=1,offvalue=0)
ch2 = Checkbutton(root,text='篮球',variable = CheckVar2,onvalue=1,offvalue=0)
ch3 = Checkbutton(root,text='游泳',variable = CheckVar3,onvalue=1,offvalue=0)
ch4 = Checkbutton(root,text='田径',variable = CheckVar4,onvalue=1,offvalue=0)
ch1.pack()
ch2.pack()
ch3.pack()
ch4.pack()
btn = Button(root,text="OK",command=run)
btn.pack()
lb2 = Label(root,text='')
lb2.pack()
root.mainloop()
列表框(Listbox) :可供用户单选或多选所列条目以形成人机交互。列表框控件的主要方法见下面的表:
方法 | 功能描述 |
---|---|
curselection() | 返回光标选中项目编号的元组,注意并不是单个的整数 |
delete(起始位置,终止位置) | 删除项目,终止位置可省略,全部清空为delete(0,END) |
get(起始位置,终止位) | 返回范围所含项目文本的元组,终止位置可忽略 |
insert(位置,项目元素) | 插入项目元素(若有多项,可用列表或元组类型赋值),若位置为END,则将项目元素添加在最后 |
size() | 返回列表框行数 |
执行自定义函数时,通常使用“实例名.surselection()” 或 “selected” 来获取选中项的位置索引。由于列表框实质上就是将Python 的列表类型数据可视化呈现,在程序实现时,也可直接对相关列表数据进行操作,然后再通过列表框展示出来,而不必拘泥于可视化控件的方法。
看下面的一个例子:实现列表框的初始化、添加、插入、修改、删除和清空操作,如下:
from tkinter import *
def ini():
Lstbox1.delete(0,END)
list_items = ["数学","物理","化学","语文","外语"]
for item in list_items:
Lstbox1.insert(END,item)
def clear():
Lstbox1.delete(0,END)
def ins():
if entry.get() != '':
if Lstbox1.curselection() == ():
Lstbox1.insert(Lstbox1.size(),entry.get())
else:
Lstbox1.insert(Lstbox1.curselection(),entry.get())
def updt():
if entry.get() != '' and Lstbox1.curselection() != ():
selected=Lstbox1.curselection()[0]
Lstbox1.delete(selected)
Lstbox1.insert(selected,entry.get())
def delt():
if Lstbox1.curselection() != ():
Lstbox1.delete(Lstbox1.curselection())
root = Tk()
root.title('列表框实验')
root.geometry('320x240')
frame1 = Frame(root,relief=RAISED)
frame1.place(relx=0.0)
frame2 = Frame(root,relief=GROOVE)
frame2.place(relx=0.5)
Lstbox1 = Listbox(frame1)
Lstbox1.pack()
entry = Entry(frame2)
entry.pack()
btn1 = Button(frame2,text='初始化',command=ini)
btn1.pack(fill=X)
btn2 = Button(frame2,text='添加',command=ins)
btn2.pack(fill=X)
btn3 = Button(frame2,text='插入',command=ins) # 添加和插入功能实质上是一样的
btn3.pack(fill=X)
btn4 = Button(frame2,text='修改',command=updt)
btn4.pack(fill=X)
btn5 = Button(frame2,text='删除',command=delt)
btn5.pack(fill=X)
btn6 = Button(frame2,text='清空',command=clear)
btn6.pack(fill=X)
root.mainloop()
组合框(Combobox): 实质上是带文本框的上拉列表框,其功能也将是Python 的列表类型数据可视化呈现,并提供用户单选或多选所列条目以形成人机交互。在图形化界面设计时,由于其具有灵活的界面,因此往往比列表框更受喜爱。但该控件并不包含在 tkinter 模块中,而是与 TreeView、Progressbar、Separator等控件一同包含在tkinter 的子模块ttk中。如果使用该控件,应先与from tkinter import ttk 语句引用ttk子模块,然后创建组合框实例: 实例名=Combobox(根对象,[属性列表])指定变量var=StringVar(),并设置实例属性 textvariable = var,values=[列表…]。组合框控件常用方法有:获得所选中的选项值get()和获得所选中的选项索引current()。
看下面的一个例子:实现四则运算计算器,将两个操作数分别填入两个文本框后,通过选择组合框中的算法触发运算。
from tkinter.ttk import *
from tkinter import *
def calc(event):
a = float(t1.get())
b = float(t2.get())
dic = {0:a+b,1:a-b,2:a*b,3:a/b}
c = dic[comb.current()]
lbl.config(text=str(c))
root = Tk()
root.title('四则运算')
root.geometry('320x240')
t1 = Entry(root)
t1.place(relx=0.1,rely=0.1,relwidth=0.2,relheight=0.1)
t2 = Entry(root)
t2.place(relx=0.5,rely=0.1,relwidth=0.2,relheight=0.1)
var = StringVar()
comb = Combobox(root,textvariable=var,values=['加','减','乘','除',])
comb.place(relx=0.1,rely=0.5,relwidth=0.2)
comb.bind('<>' ,calc)
lbl=Label(root,text='结果')
lbl.place(relx=0.5,rely=0.7,relwidth=0.2,relheight=0.3)
root.mainloop()
滑块(Scale): 是一种直观地进行数值输入的交互控件,其主要属性见下表:
属性 | 功能描述 |
---|---|
from | 起始值(最小可取值) |
lable | 标签文字,默认为无 |
length | 滑块控件实例宽(水平方向)或 高(垂直方向),默认为100像素 |
orient | 滑块控件实例呈现方向,VERTCAL或HORIZONTAL(默认) |
repeatdelay | 鼠标响应延时,默认为 300ms |
resolution | 分辨精度,即最小值间隔 |
sliderlength | 滑块宽度,默认为30 像素 |
state | 状态,若设置 state=DISABLED,则滑块控件实例不可用 |
tickinterval | 标尺间隔,默认为0,若设置过小,则会重叠 |
to | 终止值(最大可取值) |
variable | 返回数值类型,可为IntVar(整数)、DoubleVar(浮点数)、或 StringVar(字符串) |
width | 控件实例本身的宽度,默认为15像素 |
滑块控件实例的主要方法比较简单,有 get()和set(值),分别为取值和将滑块设在某特定值上。滑块实例也可绑定鼠标左键释放事件,并在执行函数中添加参数event来实现事件响应。
例如:在一个窗体上设计一个200像素宽的水平滑块,取值范围为1.0~5.0,分辨精度为0.05,刻度间隔为 1,用鼠标拖动滑块后释放鼠标可读取滑块值并显示在标签上。效果如下:
from tkinter import *
def show(event):
s = '滑块的取值为' + str(var.get())
lb.config(text=s)
root = Tk()
root.title('滑块实验')
root.geometry('320x180')
var=DoubleVar()
scl = Scale(root,orient=HORIZONTAL,length=200,from_=1.0,to=5.0,label='请拖动滑块',tickinterval=1,resolution=0.05,variable=var)
scl.bind('' ,show)
scl.pack()
lb = Label(root,text='')
lb.pack()
root.mainloop()
菜单(Menu):用于可视化地为一系列的命令分组,从而方便用户找到和触发执行这些命令。这里Menu所实例化别的主要是菜单,其通式为:
菜单实例名=Menu(根窗体)
菜单分组1=Menu(菜单实例名)
菜单实例名.add_cascade(<label=菜单分组1 显示文本>,<menu=菜单分组1>)
菜单分组1.add_command(<label=命令1文本>,<command=命令1函数名>)
其中较为常见的方法有:add_cascade()、add_command()和add_separator(),分别用于添加一个菜单分组、添加一条菜单命令和添加一条分割线。
利用Menu控件也可以创建快捷菜单(又称为上下文菜单)。通常需要右击弹出的控件实例绑定鼠标右击响应事件,并指向一个捕获event参数的自定义函数,在该自定义函数中,将鼠标的触发位置event.x_root 和 event.y_root以post()方法传给菜单。
例子:仿照window自带的“记事本”中的文件和编辑 菜单,实现在主菜单个快捷菜单上触发菜单命令,并相应改变窗体上的标签的文本内容。效果如下:
from tkinter import *
def new():
s = '新建'
lb1.config(text=s)
def ope():
s = '打开'
lb1.config(text=s)
def sav():
s = '保存'
lb1.config(text=s)
def cut():
s = '剪切'
lb1.config(text=s)
def cop():
s = '复制'
lb1.config(text=s)
def pas():
s = '粘贴'
lb1.config(text=s)
def popupmenu(event):
mainmenu.post(event.x_root,event.y_root)
root = Tk()
root.title('菜单实验')
root.geometry('320x240')
lb1 = Label(root,text='显示信息',font=('黑体',32,'bold'))
lb1.place(relx=0.2,rely=0.2)
mainmenu = Menu(root)
menuFile = Menu(mainmenu) # 菜单分组 menuFile
mainmenu.add_cascade(label="文件",menu=menuFile)
menuFile.add_command(label="新建",command=new)
menuFile.add_command(label="打开",command=ope)
menuFile.add_command(label="保存",command=sav)
menuFile.add_separator() # 分割线
menuFile.add_command(label="退出",command=root.destroy)
menuEdit = Menu(mainmenu) # 菜单分组 menuEdit
mainmenu.add_cascade(label="编辑",menu=menuEdit)
menuEdit.add_command(label="剪切",command=cut)
menuEdit.add_command(label="复制",command=cop())
menuEdit.add_command(label="粘贴",command=pas())
root.config(menu=mainmenu)
root.bind('Button-3',popupmenu) # 根窗体绑定鼠标右击响应事件
root.mainloop()
子窗体:用Toplevel可新建一个显示在最前面的子窗体,其通式为: 字体实例名=Toplevel(根窗体),子窗体与根窗体类似,也可设置title、geomerty等属性,并在画布上布局其他控件。
如下的例子:在根窗体上创建菜单,触发创建一个新的窗体
from tkinter import *
def newwind():
winNew = Toplevel(root)
winNew.geometry('320x240')
winNew.title('新窗体')
lb2 = Label(winNew,text='我在新窗体上')
lb2.place(relx=0.2,rely=0.2)
btClose=Button(winNew,text='关闭',command=winNew.destroy)
btClose.place(relx=0.7,rely=0.5)
root = Tk()
root.title('新建窗体实验')
root.geometry('320x240')
lb1 = Label(root,text='主窗体',font=('黑体',32,'bold'))
lb1.place(relx=0.2,rely=0.2)
mainmenu = Menu(root)
menuFile = Menu(mainmenu)
mainmenu.add_cascade(label='菜单',menu=menuFile)
menuFile.add_command(label='新窗体',command=newwind)
menuFile.add_separator()
menuFile.add_command(label='退出',command=root.destroy)
root.config(menu=mainmenu)
root.mainloop()
关闭窗体程序运行的方法通常用 destory(),而不建议用 quit()。用Toplevel 所创建的子窗体是非模式(Modeless)的窗体,虽然初建时子窗体在最前面,但根窗体上的控件实例也是可以被操作的。
模式对话框(Modal):是相对于前面介绍的非模式窗体而言的,所弹出的对话框必须应答,在关闭之前无法操作其后面的其他窗体。常见的模式对话框有消息对话框、输入对话框、文件选择对话框、颜色选择对话框等。
(一)、消息对话框: 引用 tkinter.messagebox 包,可使用消息对话框函数。执行这些函数,可弹出模式消息对话框,并根据用户的响应但会一个布尔值。其通式为:
消息对话框函数(<title=标题文本>,<message=消息文本>,[其他参数])
看下面的例子:单击按钮,弹出确认取消对话框,并将用户回答显示在标签中。效果如下:
from tkinter import *
import tkinter.messagebox
def xz():
answer=tkinter.messagebox.askokcancel('请选择','请选择确定或取消')
if answer:
lb.config(text='已确认')
else:
lb.config(text='已取消')
root = Tk()
lb = Label(root,text='')
lb.pack()
btn=Button(root,text='弹出对话框',command=xz)
btn.pack()
root.mainloop()
(二)、输入对话框: 引用tkinter.simpledialog包,可弹出输入对话框,用以接收用户的简单输入。输入对话框常用 askstring()、askfloat()和askfloat() 三种函数,分别用于接收字符串、整数和浮点数类型的输入。
如下面的例子:单击按钮,弹出输入对话框,接收文本输入显示在窗体的标签上。如下:
from tkinter.simpledialog import *
def xz():
s=askstring('请输入','请输入一串文字')
lb.config(text=s)
root = Tk()
lb = Label(root,text='')
lb.pack()
btn=Button(root,text='弹出输入对话框',command=xz)
btn.pack()
root.mainloop()
文件选择对话框:引用tkinter.filedialog包,可弹出文件选择对话框,让用户直观地选择一个或一组文件,以供进一步的文件操作。常用的文件选择对话框函数有 askopenfilename()、askopenfilenames()和asksaveasfilename(),分别用于进一步打开一个文件、一组文件和保存文件。其中,askopenfilename()和asksaveasfilenamme()函数的返回值类型为包含文件路径的文件名字符串,而askopenfilenames()函数的返回值类型为元组。
例如:单击按钮,弹出文件选择对话框(“打开”对话框),并将用户所选择的文件路径和文件名显示在窗体的标签上。如下:
from tkinter import *
import tkinter.filedialog
def xz():
filename=tkinter.filedialog.askopenfilename()
if filename != '':
lb.config(text='您选择的文件是'+filename)
else:
lb.config(text='您没有选择任何文件')
root = Tk()
lb = Label(root,text='')
lb.pack()
btn=Button(root,text='弹出文件选择对话框',command=xz)
btn.pack()
root.mainloop()
颜色选择对话框:引用tkinter.colorchooser包,可使用 askcolor()函数弹出模式颜色选择对话框,让用户可以个性化地设置颜色属性。该函数的返回形式为包含RGB十进制浮点元组和RGB十六进制字符串的元组类型,例如:“((135.527343.52734375,167.65234375,186.7265625)),’#87a7ba’”。通常,可将其转换为字符串类型后,再截取以十六进制数表示的RGB颜色字符串用于为属性赋值。
例如:单击按钮,弹出颜色选择对话框,并将用户所选择的颜色设置为窗体上标签的背景颜色,如下:
from tkinter import *
import tkinter.colorchooser
def xz():
color=tkinter.colorchooser.askcolor()
colorstr=str(color)
print('打印字符串%s 切掉后=%s' % (colorstr,colorstr[-9:-2]))
lb.config(text=colorstr[-9:-2],background=colorstr[-9:-2])
root = Tk()
lb = Label(root,text='请关注颜色的变化')
lb.pack()
btn=Button(root,text='弹出颜色选择对话框',command=xz)
btn.pack()
root.mainloop()
Canvas:画布,提供绘图功能(直线、椭圆、多边形、矩形) 可以包含图形或位图,用来绘制图表和图,创建图形编辑器,实现定制窗口部件。
在比如像用户交互界面等,需要提供设计的图标、图形、logo等信息是可以用到画布。
from tkinter import *
root = Tk()
root.title('My Window')
root.geometry('500x400')
canvas = Canvas(root, bg = 'green', height = 200, width = 500)
image_file = PhotoImage(file = 'pic.gif')
image = canvas.create_image(250, 0, anchor = 'n', image = image_file)
x0, y0, x1, y1 = 100, 100, 150, 150
line = canvas.create_line(x0 - 50, y0 - 50, x1 - 50, y1 - 50) # 画直线
oval = canvas.create_oval(x0 + 120, y0 + 50, x1 + 120, y1 + 50, fill='yellow') # 画圆 用黄色填充
arc = canvas.create_arc(x0, y0 + 50, x1, y1 + 50, start=0, extent=180) # 画扇形 从0度打开收到180度结束
rect = canvas.create_rectangle(330, 30, 330 + 20, 30 + 20) # 画矩形正方形
canvas.pack()
def moveit():
canvas.move(rect, 2, 2) # 移动正方形rect(也可以改成其他图形名字用以移动一起图形、元素),按每次(x=2, y=2)步长进行移动
b = Button(root, text = 'move item', command = moveit).pack()
root.mainloop()
Frame框架,用来承载放置其他GUI元素,就是一个容器,是一个在 Windows 上分离小区域的部件, 它能将 Windows 分成不同的区,然后存放不同的其他部件. 同时一个 Frame 上也能再分成两个 Frame, Frame 可以认为是一种容器.
在比如像软件或网页交互界面等,有不同的界面逻辑层级和功能区域划分时可以用到,让交互界面逻辑更加清晰。
from tkinter import *
root = Tk()
root.geometry('500x300')
# 在图形界面上创建一个标签用以显示内容并放置
Label(root, text='on the window', bg='red', font=('Arial', 16)).pack()
# 创建一个主frame,长在主window窗口上
frame = Frame(root)
frame.pack()
# 创建第二层框架frame,长在主框架frame上面
frame_l = Frame(frame) # 第二层frame,左frame,长在主frame上
frame_r = Frame(frame) # 第二层frame,右frame,长在主frame上
frame_l.pack(side='left')
frame_r.pack(side='right')
# 创建三组标签,为第二层frame上面的内容,分为左区域和右区域,用不同颜色标识
Label(frame_l, text='on the frame_l1', bg='green').pack()
Label(frame_l, text='on the frame_l2', bg='green').pack()
Label(frame_l, text='on the frame_l3', bg='green').pack()
Label(frame_r, text='on the frame_r1', bg='yellow').pack()
Label(frame_r, text='on the frame_r2', bg='yellow').pack()
Label(frame_r, text='on the frame_r3', bg='yellow').pack()
# 第8步,主窗口循环显示
root.mainloop()
用tkinter 可将用户事件与自定义函数绑定,用键盘或鼠标的动作事件来响应触发自定义函数的执行。其通式为:
控件实例.bind(<事件代码>,<函数名>)
其中,事件代码通常以半角小于号“<”和大于号“>” 界定,包括事件和按键等 2~3个部分,它们之间用减号分隔,常见事件代码见下表:
事件 | 事件代码 | 备注 |
---|---|---|
单击鼠标左键 | ButtonPress-1 | 可简写为Button-1或 1 |
单击鼠标中键 | ButtonPress-2 | 可简写为Button-2 或 2 |
单击鼠标右键 | ButtonPress-3 | 可简写为Button-3 或 3 |
释放鼠标左键 | ButtonRelease-1 | — |
释放鼠标中键 | ButtonRelease-2 | — |
释放鼠标右键 | ButtonRelease-3 | — |
按住鼠标左键移动 | B1-Motion | — |
按住鼠标中键移动 | B2-Motion | — |
按住鼠标右键移动 | B3-Motion | — |
转动鼠标滚轮 | MouseWheel | — |
双击鼠标左键 | Double-Button-1 | — |
鼠标进入控件实例 | Enter | 注意与回车事件的区别 |
鼠标离开控件实例 | Leave | — |
键盘任意键 | Key | — |
字母和数字 | Key-字母,例如key-a、Key-A | 简写不带小于和大于号,例如:a,A和1等 |
回车 | Return | Tab,Shift,Control(注意不能用Ctrl),Alt等类同 |
空格 | Space | — |
方向键 | Up ,Down,Left,Right | — |
功能键 | Fn例如:F1等 | — |
组合键 | 键名之间以减号链接,例如Control-k,Shift-6,Alt-Up等 | 注意大小写 |
例如,将框架控件实例frame 绑定鼠标右键单击事件,调用自定义函数 myfunc()可表示为"frame.bind(’’,myfunc)",注意: myfunc后面没有括号。将控件实例绑定到键盘事件和部分光标不落在具体控件实例上的鼠标事件时,还需要设置该实例执行focus_set() 方法获得焦点,才能对事件持续响应。例如: frame.focus_set()。所调用的自定义函数若需要利用鼠标或键盘的响应值,可将event作为参数,通过event的属性获取。event的属性见下表:
event属性 | 意义 |
---|---|
x或y(注意是小写) | 相对于事件绑定控件实例左上角的坐标值(像素) |
root_x或root_y(注意是小写) | 相对于显示屏幕左上角的坐标值(像素) |
char | 可显示的字符,若按键不可显示,则返回为空字符串 |
keysysm | 字符或字符型按键名,如:“a”或“Escape” |
keysysm_num |
例如:将标签绑定键盘任意键触发事件并获取焦点,并将按键字符显示在标签上
from tkinter import *
def show(event):
s=event.keysym
lb.config(text=s)
root=Tk()
root.title('按键实验')
root.geometry('200x200')
lb=Label(root,text='请按键',font=('黑体',48))
lb.bind('' ,show)
lb.focus_set()
lb.pack()
root.mainloop()
编写一个用户登录界面,用户可以登录账户信息,如果账户已经存在,可以直接登录,登录名或者登录密码输入错误会提示,如果账户不存在,提示用户注册,点击注册进去注册页面,输入注册信息,确定后便可以返回登录界面进行登录。
import tkinter as tk # 使用Tkinter前需要先导入
import tkinter.messagebox
import pickle
# 第1步,实例化object,建立窗口window
window = tk.Tk()
# 第2步,给窗口的可视化起名字
window.title('Wellcome to Hongwei Website')
# 第3步,设定窗口的大小(长 * 宽)
window.geometry('400x300') # 这里的乘是小x
# 第4步,加载 wellcome image
canvas = tk.Canvas(window, width=400, height=135, bg='green')
image_file = tk.PhotoImage(file='pic.gif')
image = canvas.create_image(200, 0, anchor='n', image=image_file)
canvas.pack(side='top')
tk.Label(window, text='Wellcome', font=('Arial', 16)).pack()
# 第5步,用户信息
tk.Label(window, text='User name:', font=('Arial', 14)).place(x=10, y=170)
tk.Label(window, text='Password:', font=('Arial', 14)).place(x=10, y=210)
# 第6步,用户登录输入框entry
# 用户名
var_usr_name = tk.StringVar()
var_usr_name.set('[email protected]')
entry_usr_name = tk.Entry(window, textvariable=var_usr_name, font=('Arial', 14))
entry_usr_name.place(x=120, y=175)
# 用户密码
var_usr_pwd = tk.StringVar()
entry_usr_pwd = tk.Entry(window, textvariable=var_usr_pwd, font=('Arial', 14), show='*')
entry_usr_pwd.place(x=120, y=215)
# 第8步,定义用户登录功能
def usr_login():
# 这两行代码就是获取用户输入的usr_name和usr_pwd
usr_name = var_usr_name.get()
usr_pwd = var_usr_pwd.get()
# 这里设置异常捕获,当我们第一次访问用户信息文件时是不存在的,所以这里设置异常捕获。
# 中间的两行就是我们的匹配,即程序将输入的信息和文件中的信息匹配。
try:
with open('usrs_info.pickle', 'rb') as usr_file:
usrs_info = pickle.load(usr_file)
except FileNotFoundError:
# 这里就是我们在没有读取到`usr_file`的时候,程序会创建一个`usr_file`这个文件,并将管理员
# 的用户和密码写入,即用户名为`admin`密码为`admin`。
with open('usrs_info.pickle', 'wb') as usr_file:
usrs_info = {'admin': 'admin'}
pickle.dump(usrs_info, usr_file)
usr_file.close() # 必须先关闭,否则pickle.load()会出现EOFError: Ran out of input
# 如果用户名和密码与文件中的匹配成功,则会登录成功,并跳出弹窗how are you? 加上你的用户名。
if usr_name in usrs_info:
if usr_pwd == usrs_info[usr_name]:
tkinter.messagebox.showinfo(title='Welcome', message='How are you? ' + usr_name)
# 如果用户名匹配成功,而密码输入错误,则会弹出'Error, your password is wrong, try again.'
else:
tkinter.messagebox.showerror(message='Error, your password is wrong, try again.')
else: # 如果发现用户名不存在
is_sign_up = tkinter.messagebox.askyesno('Welcome! ', 'You have not sign up yet. Sign up now?')
# 提示需不需要注册新用户
if is_sign_up:
usr_sign_up()
# 第9步,定义用户注册功能
def usr_sign_up():
def sign_to_Hongwei_Website():
# 以下三行就是获取我们注册时所输入的信息
np = new_pwd.get()
npf = new_pwd_confirm.get()
nn = new_name.get()
# 这里是打开我们记录数据的文件,将注册信息读出
with open('usrs_info.pickle', 'rb') as usr_file:
exist_usr_info = pickle.load(usr_file)
# 这里就是判断,如果两次密码输入不一致,则提示Error, Password and confirm password must be the same!
if np != npf:
tkinter.messagebox.showerror('Error', 'Password and confirm password must be the same!')
# 如果用户名已经在我们的数据文件中,则提示Error, The user has already signed up!
elif nn in exist_usr_info:
tkinter.messagebox.showerror('Error', 'The user has already signed up!')
# 最后如果输入无以上错误,则将注册输入的信息记录到文件当中,并提示注册成功Welcome!,You have successfully signed up!,然后销毁窗口。
else:
exist_usr_info[nn] = np
with open('usrs_info.pickle', 'wb') as usr_file:
pickle.dump(exist_usr_info, usr_file)
tkinter.messagebox.showinfo('Welcome', 'You have successfully signed up!')
# 然后销毁窗口。
window_sign_up.destroy()
# 定义长在窗口上的窗口
window_sign_up = tk.Toplevel(window)
window_sign_up.geometry('300x200')
window_sign_up.title('Sign up window')
new_name = tk.StringVar() # 将输入的注册名赋值给变量
new_name.set('[email protected]') # 将最初显示定为'[email protected]'
tk.Label(window_sign_up, text='User name: ').place(x=10, y=10) # 将`User name:`放置在坐标(10,10)。
entry_new_name = tk.Entry(window_sign_up, textvariable=new_name) # 创建一个注册名的`entry`,变量为`new_name`
entry_new_name.place(x=130, y=10) # `entry`放置在坐标(150,10).
new_pwd = tk.StringVar()
tk.Label(window_sign_up, text='Password: ').place(x=10, y=50)
entry_usr_pwd = tk.Entry(window_sign_up, textvariable=new_pwd, show='*')
entry_usr_pwd.place(x=130, y=50)
new_pwd_confirm = tk.StringVar()
tk.Label(window_sign_up, text='Confirm password: ').place(x=10, y=90)
entry_usr_pwd_confirm = tk.Entry(window_sign_up, textvariable=new_pwd_confirm, show='*')
entry_usr_pwd_confirm.place(x=130, y=90)
# 下面的 sign_to_Hongwei_Website
btn_comfirm_sign_up = tk.Button(window_sign_up, text='Sign up', command=sign_to_Hongwei_Website)
btn_comfirm_sign_up.place(x=180, y=120)
# 第7步,login and sign up 按钮
btn_login = tk.Button(window, text='Login', command=usr_login)
btn_login.place(x=120, y=240)
btn_sign_up = tk.Button(window, text='Sign up', command=usr_sign_up)
btn_sign_up.place(x=200, y=240)
# 第10步,主窗口循环显示
window.mainloop()