tkinter
python的PEP8规范:PEP 8 – Style Guide for Python Code | peps.python.org
1.Tkinter
tkinter(Tk interface)是Python的标准GUI库,支持跨平台的GUI程序开发。tkinter适合小型的GUI程序编写,也特别适合初学者学习GUI编程。本书以tkinter为核心进行讲解。
2.wxPython
wxPython是比较流行的GU1库,适合大型应用程序开发,功能强于tkinter,整体设计框架类似于MFC(MicrosoftFoundation Classes微软基础类库)。
3.PyQT
Qt是一种开源的GUI库,适合大型GUI程序开发,PyQT是Qt工具包标准的Python实现。我们也可以使用Qt Desginer
基于tkinter模块创建GUI程序包含如下4个核心步骤:
- 创建应用程序主窗口对象(也称:根窗口)
- 通过类
Tk
的无参构造函数
from tkinter inport *
root = Tk()
- 在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等。
btn01 = Button(root)
btn01["text"] = "点我就送花"
- 通过几何布局管理器,管理组件的大小和位置
btn01.pack()
- 事件处理
- 通过绑定事件处理程序,响应用户操作所触发的事件(比如:单机、双击)
def songhua(e):
messagebox.showinfo("Message","送你一朵玫瑰花,请你爱上我")
print("送你99朵玫瑰花")
btn01.bind("<Button-1>",songhua)
【示例】使用tkinter模块,创建GUI应用程序,并实现点击按钮的事件处理
from tkinter import *
from tkinter import messagebox
root = Tk()
btn01 = Button(root)
btn01["text"] = "点我送花"
btn01.pack()
def songhua(e):
messagebox.showinfo("Message","送你一朵玫瑰花,请你爱上我!")
print("送你99朵玫瑰花")
btn01.bind("<Button-1>", songhua)
root.mainloop() # 调用组件 mainloop 方法, 进入事件循环
tkinter 主窗口
主窗口位置和大小
通过 geometry ('wxh±x±y')进行设置。w为宽度,h为高度。+x 表示距屏幕左边的距离;-x 表示距屏幕右边的距离;+y表示距屏幕上边的距离;-y 表示 距屏幕下边的距离。
from tkinter import *
from tkinter import messagebox
root = Tk()
root.title("我的第一个GUI程序")
root.geometry("500x300+300+200")
GUI 编程整体描述
图像用户界面是由一个个组件组成,就像小孩“搭积木”一样最终组成了整个界面,有的组件还能在里面再放置其他组件,我们称为 “容器” 。Tkinter 的 GUI 组件关系图如下:
根据上图所示,我们依次简介这些类的基本作用。
- Misc 和 Wm
- Tkinter 的 GUI 组件有两个根父类,它们都直接继承了 object 类:
- Misc:它是所有组件的根父类。
- Wm:它主要提供了一些与窗口管理器通信的功能函数。
- Tk
Misc 和 Wm派生出子类Tk,它代表应用程序的主窗口,一般应用程序都需要直接或间接使用Tk。
- Pack、Place、Grid
- Pack、Place、Grid 是布局管理器,布局管理器管理组件的:大小、位置。通过布局管理器可以将容器中的组件实现合理的排布。
- BaseWidget
- BaseWidget 是 所有组件的父类
- Widget
- Widget 是 所有组件的父类,Widget 一共有四个父类:BaseWidget、Pack、Grid、Place。意味着,所有 GUI 组件同时具备这四个父类的属性和方法。
【注】想观察的层次结构可以在类定义处的类名上单击右键,选择 Diagram --> Show Diagram
常用组件汇总列表
Tkinter类 | 名称 | 简介 |
Toplevel | 顶层 | 容器类,可用于为其他组件提供单独的容器;Toplevel有点类似于窗口 |
Button | 按钮 | 代表按钮组件 |
Canvas | 画布 | 提供绘图功能,包括直线、矩形、椭圆、多边形、位图等 |
Checkbutton | 复选框 | 可供用户勾选的复选框 |
Entry | 单行输入框 | 用户可输入内容 |
Frame | 容器 | 用于装载其它 GUI 组件 |
Label | 标签 | 用于像是不可编辑的文本或图标 |
LabelFrame | 容器 | 也是容器组件,类似于Frame,但它支持添加标题 |
Listbox | 列表框 | 列出多个选项,供用户选择 |
Menu | 菜单 | 菜单组件 |
Menubutton | 菜单按钮 | 用来包含菜单的按钮(包括下拉式、层叠式等) |
OptionMenu | 菜单按钮 | Menubutton 的子类,也代表菜单按钮,可通过按钮打开一个菜单 |
Message | 消息框 | 类似于标签,但可以显示多行文本;后来当 Label 也能显示多行文本之后,该组件基本处于废弃状态 |
PanedWindow | 分区窗口 | 该容器会被划分成多个区域,每添加一个组件占一个区域,用户可通过拖动分隔线来改变各区域的大小 |
Radiobutton | 单选按钮 | 可供用户点边的单选钮 |
Scale | 滑动条 | 拖动滑块可设定起始值和结束值,可显示当前位置的精确值 |
Spinbox | 微雕选择器 | 用户可通过该组件的向上、向下箭头选择不同的值 |
Scrollbar | 滚动条 | 用于为组件(文本域、画布、列表框、文本框)提供滚动功能 |
Text | 多行文本框 | 显示多行文本 |
GUI 应用程序类的经典写法
本节程序也是 GUI 应用程序编写的一个主要结构,采用了面向对象的方式,更加合理的组织代码。
通过类Application 组织整个 GUI 程序,类Application继承了 Frame 及通过继承拥有了父类的特性。通过构造函数__init__()
初始化窗口中的对象,通过 createWidgets() 方法创建窗口中的对象。
Frame 框架是一个 tkinter 组件,表示一个矩形的区域。Frame 一般作为容器使用,可以放置其他组件,从而实现复杂的布局。
Label标签
Label (标签) 主要用于显示文本信息,也可以显示图像。
Label (标签) 有这样一些常见属性:
- width, height:
- 用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占2个字符位置,高度和英文字符一样);如果显示图像,则以像素为单位。默认值是根据具体显示的内容动态调整。
- font
指定字体和字体大小,如:font = (font_name,size) - image:
显示在Label上的图像,目前 tkinter 只支持 GIF 格式。 - fg 和 bg
fg (foreground):前景色、bg(background): 背景色 - justify
针对多行文字的对齐,可设置 justify 属性, 可选值 "left", “center” or “right”
Option 选项详解
通过学习 Label 组件, 我们发现可以通过Options 设置组件的属性,从而控制组件各种状态,比如:宽度、高度、颜色、位置等等。
我们可以通过三种方式设置Options 选项,这在各种 GUI 组件中用法都一致。
创建对象时,使用命名参数(也叫关键字参数)
fred = Button(self, fg="red", bg="blue")
创建对象后,使用字典引方式
fred["fg"] = "red"
fred["bg"] = "blue"
创建对象后,使用 config() 方法
fred.config(fg="red", bg="blue")
Button
Button(按钮)用来执行用户的单机操作。Button 可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""创建组件"""
self.btn01 = Button(root, text="登录", command=self.login)
self.btn01.pack()
# 显示图像
global photo # 把photo声明成全局变量。如果是局部变量,本方法执行完毕后,图像对象销毁,窗口显示不出图像。
photo = PhotoImage(file="picture/OGC2.gif")
self.btn02 = Button(root, image=photo, command=self.login)
self.btn02.pack()
# self.btn02.config(state="disabled") # 设置按钮为禁用
def login(self):
messagebox.showinfo("尚学堂学习系统", "登录成功!欢迎开始学习!")
if __name__ == '__main__':
root = Tk()
root.geometry("800x1000+200+300")
root.title("button测试")
app = Application(master=root)
root.mainloop()
Entry 单行文本框
Entry 用来接收一行字符串的控件。如果用户输入的文字长度长于 Entry 控件的宽度时,文字会自动向后滚动。如果想输入多行文本,需要使用 Text 控件。
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""创建登录界面的组件"""
self.label01 = Label(self, text="用户名")
self.label01.pack()
# StringVar 变量绑定到指定的组件。
# StringVar 变量的植发生变化,组件内容也变化:
# 组件内容发生变化,StringVar 变量的值也发生变化,
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
v1.set("admin")
print(v1.get());print(self.entry01.get())
# 创建密码框
self.label02 = Label(self, text="密码")
self.label02.pack()
v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.pack()
Button(self, text="登陆", command = self.login).pack()
def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("去数据库比对用户名和密码!")
print("用户名:"+username)
print("密码:"+pwd)
if username == "gaoqi" and pwd == "123456":
messagebox.showinfo("尚学堂学习系统", "登录成功!欢迎开始学习!")
else:
messagebox.showinfo("尚学堂学习系统","登录失败!用户名或密码错误!")
if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
root.title("button测试")
app = Application(master=root)
root.mainloop()
Text多行文本框
Txt(多行文本框)的主要用于显示多行文本,还可以显示网页链接,图片,HTML页面,甚至CSS样式表,添加组件等。因此,也常被当做简单的文本处理器、文本编辑器或者网页浏览器来使用。比如IDLE就是Text组件构成的。
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
import webbrowser
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
self.w1 = Text(root, width=40, height=12, bg="gray")
# 宽废20个字母(10个汉字),高度一个行高
self.w1.pack()
self.w1.insert(1.0, "0123456789\nabcdefg")
self.w1.insert(2.3, "锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦\n")
Button(self, text="重复插入文本", command=self.insertText).pack(side="left")
Button(self, text="返回文本", command = self.returnText).pack(side="left")
Button(self, text="添加图片", command = self.addImage).pack(side="left")
Button(self, text="添加组件", command = self.addwidget).pack(side="left")
Button(self, text="通过tag精确控制文本", command=self.testTag).pack(side="left")
def insertText(self):
# INSERT索引表示在光标处插入
self.w1.insert(INSERT,'Gaoqi')
# EWD索引号表示在最后插入
self.w1.insert(END, '【sxt】')
self.w1.insert(1.8, "gaoqi")
def returnText(self):
# Indexes(索引)是用来指向Text组件中文本的位置,Text的组件索引也是对应实际字符之间的位置。
# 核心:行号以1开始 列号以0开始
print(self.w1.get(1.2, 1.6))
print("所有文本内容:\n"+self.w1.get(1.0,END))
def addImage(self):
# global photo
self.photo = PhotoImage(file="picture/OGC4.gif")
self.w1.image_create(END, image=self.photo)
def addwidget(self):
b1 = Button(self.w1, text='爱尚学堂')
# 在teXt仓创建组件的命令
self.w1.window_create(INSERT, window=b1)
def testTag(self):
self.w1.delete(1.0, END)
self.w1.insert(INSERT, "good good study,day day up!\n北京尚学堂\n百战程序员\n百度,搜一下就知道了")
self.w1.tag_add("good", 1.0, 1.9)
self.w1.tag_config("good", background="yellow", foreground="red")
self.w1.tag_add("baidu", 4.0, 4.2)
self.w1.tag_config("baidu", underline=True)
self.w1.tag_bind("baidu", "<Button-1>", self.webshow)
def webshow(self, event):
webbrowser.open("http://www.baidu.com")
if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
root.title("button测试")
app = Application(master=root)
root.mainloop()
Radiobutton单选按钮
Radiobutton控件用于选择同一组单选按钮中的一个。
Radiobutton可以显示文本,也可以显示图像。
【示例】Radiobutton基础用法
"""测试Radiobutton组件的基本用法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
self.v = StringVar();
self.v.set("F")
self.r1 = Radiobutton(self, text="男性", value="M", variable=self.v)
self.r2 = Radiobutton(self, text="女性", value="F", variable=self.v)
self.r1.pack(side="left");self.r2.pack(side="left")
Button(self, text="确定", command = self.confirm).pack(side="left")
def confirm(self):
messagebox.showinfo("测试", "选择的性别:" + self.v.get())
if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
root.title("Radiobutton测试")
app = Application(master=root)
root.mainloop()
Checkbutton复选按钮
Checkbutton控件用于选择多个按钮的情况。Checkbutton可以显示文本,也可以显示图像。【示例】Checkbutton复选按钮用法
"""测试Checkbutton组件的基本用法,使用面向对象的方式"""
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
self.codeHobby = IntVar();
self.videoHobby = IntVar()
print(self.codeHobby.get()) # 默认值是0
self.c1 = Checkbutton(self, text="敲代码", variable=self.codeHobby, onvalue=1, offvalue=0)
self.c2 = Checkbutton(self, text="看视须", variable=self.videoHobby, onvalue=1, offvalue=0)
self.c1.pack(side="left");
self.c2.pack(side="left")
Button(self, text="确定", command=self.confirm).pack(side="left")
def confirm(self):
if self.videoHobby.get() == 1:
messagebox.showinfo("测试","看视频,都是正常人有的爱好I你喜欢看什么类型?")
if self.codeHobby.get() == 1:
messagebox.showinfo("测试","抓获野生程序猿一只,赶紧送给他尚学堂的视频充饥")
if __name__ == '__main__':
root = Tk()
root.geometry("400x130+200+300")
root.title("Radiobutton测试")
app = Application(master=root)
root.mainloop()
canvas画布
canvas(画布)是一个矩形区域,可以放置图形、图像、组件等。本节我们简单介绍canvas的使用,更加详细和深入的内容将在后面的“图形绘制”章节讲解.
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
self.canvas = Canvas(self, width=300, height=200, bg="green")
self.canvas.pack()
# 画一条直线
line = self.canvas.create_line(10, 10, 30, 20, 40, 50)
# 画一个矩形,
rect = self.canvas.create_rectangle(50, 50, 100, 100)
# 画一个椭圆,坐标两双。为椭圆的边界矩形左上角和底部右下角
oval = self.canvas.create_oval(50, 50, 100, 100)
# global photo
# photo = PhotoImage(file="picture/OGC2.gif")
# self.canvas.create_image(150, 170, image=photo)
Button(self, text="画10个矩形", command = self.draw50Recg).pack(side="left")
def draw50Recg(self):
for i in range(0, 10):
x1 = random.randrange(int(self.canvas["width"])/2)
y1 = random.randrange(int(self.canvas["height"])/2)
x2 = x1 + random.randrange(int(self.canvas["width"])/2)
y2 = y1 + random.randrange(int(self.canvas["height"])/2)
self.canvas.create_rectangle(x1, y1, x2, y2)
if __name__ == '__main__':
root = Tk()
root.geometry("400x300+200+300")
root.title("Radiobutton测试")
app = Application(master=root)
root.mainloop()
布局管理器
一个GU川应用程序必然有大量的组件,这些组件如何排布?这时候,就需要使用tkinter提供的布局管理器帮助我们组织、管理在父组件中子组件的布局方式。tkinter提供了三种管理器:pack、grid、place。
grid布局管理器
gid表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。
grid()方法提供的选项
选项 | 说明 | 取值范国 |
column | 单元格的列号 | 从0开始的正整数 |
columnspan | 跨列,跨越的列数 | 正整数 |
row | 单元格的行号 | 从0开始的正整数 |
rowspan | 跨行,跨越的行数 | 正整数 |
ipadx,ipady | 设置子组件之间的间隔,×方向或者y方向.默认单位为像素 | 非负浮点数,默认0.0 |
padx,pady | 与之并列的组件之间的间隔,×方向或者y方向,欧认单位是像素 | 非负浮点数,默认0.0 |
sticky | 组件紧贴所在单元格的某一角,对应于东南西北中以及4个角 | "n","s","w","e","nw","sw","se","ne","center"(默认) |
【示例】通过gid布局-实现计算器软件界面.
根据实际简易计算器的按键分布,设计一个相仿的计算器界面,相应的功能暂不需要实现。
如上界面,实际可以设计成一个7行4列的表格布局,然后将相应的按钮放置进去即可.
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
"""一个经典的GUI程序的类写法"""
def __init__(self, master=None):
super().__init__(master) # super() 代表的是父类的定义, 而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""通过grid布局实现计算器的界面"""
btnText = (("MC","M+","M-","MR"),("C","±","/","x"),(7,8,9,"-"),(4,5,6,"+"),(1,2,3,"="),(0,"."))
Entry(self).grid(row=0, column=0, columnspan=4,pady=10)
for rindex, r in enumerate(btnText):
for cindex, c in enumerate(r):
if c == "=":
Button(self, text=c,width=2).grid(row=rindex+1, column=cindex,rowspan=2, sticky=NSEW)
elif c == 0:
Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex, columnspan=2, sticky=NSEW)
elif c == ".":
Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex+1, sticky=NSEW)
else:
Button(self, text=c, width=2).grid(row=rindex+1, column=cindex, sticky=NSEW)
if __name__ == '__main__':
root = Tk()
root.geometry("200x250+200+300")
root.title("grid测试")
app = Application(master=root)
root.mainloop()
pack布局管理器
pack按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布,如果不指定任何选项,默认在父组件中自顶向下垂直添加组件。
pack是代码量最少,最简单的一种,可以用于快速生成界面。
pack()方法提供的选项
名称 | 描述 | 取值范国 |
expand | 当值为"yes“时,side选项无效.组件显示在父配件中心位置,若和yes,若fill选项为“both”,则填充父组件的剩余空间 | "yes"。自然数,"no",0(默认值"no"或0) |
fill | 填充x(y)方向上的空间.当属性side="top"或"bottom"时.填充×方向;当属性side="left"或"right"时,填充"y"方向;当expand选项为"yes"时,填充父组件的剩余空间。 | "x","y","both","none"(默认值为none) |
ipadx,padxy | 设置子组件之例的同隔.×方向或者y方向。默认单位为像素 | 非负浮点数.默认0.0 |
padx,pady | 与之并列的组件之间的间隔。x方向或者y方向,默认单位是像素 | 非负浮点数.默认0.0 |
side | 定义停意在父组件的哪一边上 | "top","bottom","left","right"(默认值"top") |
before | 得本组件于所选组建对象之前pack,类似于先创建本组件再创建选定组件 | 已经pack后的组件对象 |
after | 将本组件于所选组件对象之后pack,类似于先创建选定组件再本组件 | 已经pack后的组件对象 |
in_ | 将本组件作为所选组件的子组件,类似于指定本组件的master为选定组件 | 已经pack后的组件对象 |
anchor | 对齐方式,左对齐"w",右对齐"e",顶对其"n",底对齐"s" | "n","s","w,"e","nw","sw","se","ne","center'(默认) |
【老鸟建议】如上列出了pack布局所有的属性,但是不需要挨个熟悉,了解基本的即可。pack适用于简单的垂直平排布,如果需腰复杂的布局可以使用grid或place.
【示例】pack布局用法,制作钢琴按键布局
# coding=utf-8
# 测试pack布局管理
from tkinter import *
root = Tk();root.geometry("700x220")
#Frame是一个矩形区域,就是用来放置其他子组件
f1 = Frame(root)
f1.pack()
f2 = Frame(root);f2.pack()
btnText = ("流行风","中国风","日本风","重金属","轻音乐")
for txt in btnText:
Button(f1,text=txt).pack(side="left",padx="10")
for i in range(1,13):
Label(f2, width=5, height=10, borderwidth=1, relief="solid", bg="black" if i%2==0 else "white").pack(side="left",padx=2)
root.mainloop()
place布局管理器
place布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。
place0方法的选项
选项 | 说明 | 取值范围 |
x,y | 组件左上角的绝对坐标(相对于窗口) | 非负整数 ×和y选项用于设置偏移(像素),如果同时设置relx(rely)和x(y),那么place将优先计算relx和rely,然后再实现×和y指定的偏移值 |
relx,rely | 组件左上角的坐标(相对于父容器) | relx是相对父组件的位置.0是最左边.0.5是正中间,1是最右边:rely是相对父组件的位置.0是最上边.0.5是正中间.1是最下边: |
width.height | 组件的宽度和高度 | 非负整数 |
relwidth.relheight | 组件的宽度和高度(相对于父容器) | 与relx、rely取值类似,但是相对于父组件的尺寸 |
anchor | 对齐方式.左对齐"w",右对齐"e",顶对齐"n",底对齐"s" | "n"."s"."w".“e","nw"."sw"."se"."ne","center"(默认) |
# coding=utf-8
from tkinter import *
root = Tk();root.geometry("500x300")
root.title("布局管理olace");root["bg"]="white"
f1 = Frame(root, width=200, height=200, bg="green")
f1.place(x=30, y=30)
Button(root, text="尚学堂").place(relx=0.2, x=100, y=20, relwidth=0.2, relheight=0.5)
Button(f1, text="百战程序员").place(relx=0.6, rely=0.7)
Button(f1, text="高淇老师").place(relx=0.5, rely=0.2)
root.mainloop()
【示例】place布局管理-扑克牌游戏demo
"""扑克牌游观的界面设计"""
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) #super()代表的是父类的定义,而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""通过place布局管理器实现扑克牌位置控制"""
# self.photo = PhotoImage(file="picture/puke/puke1.gif")
# self.puke1 = Label(self.master,image=self.photo)
# self.puke1.place(x=10,y=50)
self.photos = [PhotoImage(file="picture/puke/puke"+str(i+1)+".gif") for i in range(5)]
self.pukes = [Label(self.master, image=self.photos [i]) for i in range(5)]
for i in range(5):
self.pukes[i].place(x=10+i*40,y=50)
#为所有的Label增加事件处理
self.pukes[0].bind_class("Label","<Button-1>", self.chupai)
def chupai(self,event):
print(event.widget.winfo_geometry())
print(event.widget.winfo_y())
if event.widget.winfo_y()==50:
event.widget.place(y=30)
else:
event.widget.place(y=50)
if __name__ == '__main__':
root = Tk()
root.geometry("600x270+200+300")
app = Application(master=root)
root.mainloop()
事件处理
一个GUI应用整个生命周期都处在一个消息循环(eventloop)中。它等待事件的发生,并作出相应的处理。
Tkinter提供了用以处理相关事件的机制.处理函数可被绑定给各个控件的各种事件。
widget.bind(event,handler)
如果相关事件发生,handler函数会被触发,事件对象event会传递给handler函数.
鼠标和键盘事件
代码 | 说明 |
<Button-1> <ButtonPress-1> <1> | 鼠标左键按下,2表示中键。3表示右键: |
<ButtonRelease-1> | 鼠标左键释放 |
<B1-Motion> | 按住鼠标左键移动 |
<Double-Button-1> | 双击左键 |
<Enter> | 鼠标指针进人某一组件区域 |
<Leave> | 鼠标指针离开某一组件区域 |
<MouseWheel> | 滚动滚轮: |
<KeyPress-a> | 按下a键,a可用其他键替代 |
<KeyRelease-a> | 释放a键. |
<KeyPress-A> | 按下A键(大写的A) |
<Alt-KeyPress-a> | 同时按下alt和a;alt可用ctl和shift替代 |
<Double-KeyPress-a> | 快速按两下a |
<Control-V> | CTRL和V键被同时按下,V可以换成其它键位 |
event对象常用属性
名称 | 说明 |
char | 按键字符,仅对键盘事件有效 |
keycode | 按键编码,仅对键盘事件有效 |
keysym | 按键名称。仅对键盘事件有效 比如按下空格键: 键的char: 键的keycode:32 键的keysym:space 比如按下a键: 键的char: a 键的keycode:65 键的keysym:a |
num | 鼠标按键,仅对鼠标事件有效 |
type | 所触发的事件类型 |
widget | 引起事件的组件 |
width,height | 组件改变后的大小,仅Configure有效 |
x.y | 鼠标当前位置,相对于父容器 |
x_root,y_root | 鼠标当前位置,相对于整个屏幕 |
# coding=utf-8
# 测试键盘和鼠标事件
from tkinter import *
root = Tk();root.geometry("530x300")
c1 = Canvas(root,width=200,height=200,bg="green")
c1.pack()
def mouseTest(event):
print("鼠标左键单击位置(相对于父容器):{0},{1}".format(event.x,event.y))
print("鼠标左键单击位置(相对于屏幕):{0},{1}".format(event.x_root,event.y_root))
print("事件绑定的组件:{0}".format(event.widget))
def testDrag(event):
c1.create_oval(event.x,event.y,event.x+1,event.y+1)
def keyboardTest(event):
print("键的keycode:{0},键的char:{1},键的keysym:{2}".format(event.keycode,event.char,event.keysym))
def press_a_test(event):
print("press a")
def release_a_test(event):
print("release a")
c1.bind("<Button-1>",mouseTest)
c1.bind("<B1-Motion>",testDrag)
root.bind("<KeyPress>",keyboardTest)
root.bind("<KeyPress-a>",press_a_test) #只针对小写的a,太写的A不管用
root.bind("<KeyRelease-a>",release_a_test)
root.mainloop()
lambda表达式详解
lambda表达式定义的是一个匿名函数,只适合简单输入参数,简单计算返回结果,不适合功能复杂情况.
lambda定义的匿名函数也有输入、也有输出,只是没有名字。语法格式如下:
lambda参数值列表:表达式
参数值列表即为输入。
表达式计算的结构即为输出。
我们写一个最简单的案例:
add3args = lambda x,y,z:x+y+z
#print(add3args(10.20,30))
上面的lambda表达式相当于如下函数定义:
def add3args(x,y,z):
return x+y+Z
lambda表达式的参数值列表可以为如下内容:
lambda格式 | 说明 |
lambda x,y:x*y | 函数输入是×和y,输出是它们的积x*y |
lambda:None | 函数没有输入参数,输出是None |
lambda:aaa(3,4) | 函数没有输入参数,输出是aaa(3,4)的结果 |
lambda *args:sum(args) | 输入是任意个数的参数,输出是它们的和 |
lambda **kwargs:1 | 输入是任意键值对参数,输出是1 |
我们在平时使用时,注意lambda只是一个匿名函数(没有名字的函数),功能不强,不要过度使用;
使用lambda表达式实现传参
【示例】使用lambda帮助command属性绑定时传参
# coding=utf-8
# 测试command属性绑定事件,测试lambda表达式帮助传参
from tkinter import *
root = Tk();root.geometry("270x50")
def mouseTest1():
print("command方式,简单情况:不涉及获取event对象,可以使用")
def mouseTest2(a,b):
print("a={0},b={1}".format(a,b))
Button(root,text="测试command1", command=mouseTest1).pack(side="left")
Button(root,text="测试command2", command=lambda:mouseTest2("gaoqi","xixi")).pack(side="left")
root.mainloop()
多种事件绑定方式汇总
- 组件对象的绑定
1.通过command属性绑定(适合简单不需获取event对象)
Button(root,text=”登录”,command=login)
2.通过bind0方法绑定(适合需要获取event对象)
c1 = Canvas();c1.bind("",drawLine)
- 组件类的绑定
调用对象的bind_class函数,将该组件类所有的组件绑定事件:
w.bind_class("Widget","event",eventhanler)
比如:btn01.bind_class("Button",””,func)
# coding=utf-8
# 多种事件绑定方式汇总
from tkinter import *
root = Tk();root.geometry("270x30")
def mouseTest1(event):
print("bind方式绑定,可以获取event对象")
print(event.widget)
def mouseTest2(a,b):
print("a={0},b={1}".format(a,b))
print("command方式绑定,不能直接获取event对象")
def mouseTest3(event):
print("右键单击事件,绑定给所有按钮啦!!")
print(event.widget)
b1 = Button(root,text="测试bind()绑定")
b1.pack(side="left")
#bind方式绑定事件
b1.bind("<Button-1>",mouseTest1)
#command属性直接绑定事件
b2 = Button(root, text="测试command2", command=lambda:mouseTest2("gaoqi","xixi"))
b2.pack(side="left")
#给所有Button按钮都绑定右键单击事件<Button-3>
b1.bind_class("Button", "<Button-3>", mouseTest3)
root.mainloop()
OptionMenu选择项
OptionMenu(选择项)用来做多选一,选中的项在顶部显示。
显示效果如下:
【示例】OptionMenu(选择项)的基本用法
"""optionmenu的使用测试"""
from tkinter import *
root = Tk(); root.geometry("200x100")
v = StringVar(root)
v.set("百战程序员")
om = OptionMenu(root,v,"尚学堂","百战程序员","卓越班[保底18万]")
om["width"]=10
om.pack()
def test1():
print("最喜爱的机构:",v.get())
# v.set("尚学堂") # 直接改成了optionmenu选中的值
Button(root, text="确定", command=test1).pack()
root.mainloop()
Scale移动滑块
Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值。
【示例】使用Scale(移动滑块)控制字体大小变化
"""scale滑块的使用测试"""
from tkinter import *
root = Tk();root.geometry("400x150")
def test1(value):
print("滑块的值:", value)
newFont=("末体", value)
a.config(font=newFont)
s1 = Scale(root,from_=10,to=50,length=200,tickinterval=5,orient=HORIZONTAL,command=test1)
s1.pack()
a = Label(root,text="百战程序员", width=10,height=1,bg="black",fg="white")
a.pack()
root.mainloop()
颜色选择框
颜色选择框可以帮助我们设置背景色、前景色、画笔颜色、字体颜色等等。
【示例】颜色选择框基本用法
from tkinter.colorchooser import *
from tkinter import *
root = Tk();root.geometry("400x150")
def test1():
s1 = askcolor(color="red",title="选择背景色")
print(s1)
# s1的值是:((0.0,0.0,255.99609375),'#0000ff')
root.config(bg=s1[1])
Button(root, text="选择背景色", command=test1).pack()
root.mainloop()
文件对话框
文件对话框帮助我们实现可视化的操作目录、操作文件。最后,将文件、目录的信息传入到程序中。文件对话框包含如
下一些常用函数:
函数名 | 对话框 | 说明 |
askopenfilename(**options) | 文件对话框 | 返回打开的文件名 |
askopenfilenames(**options) | 文件对话框 | 返回打开的多个文件名列表 |
askopenfile(**options) | 文件对话框 | 返回打开文件对象 |
askopenfiles(**options) | 文件对话框 | 返回打开的文件对象的列表 |
askdirectory(**options) | 目录对话框 | 返回目录名 |
asksaveasfile(**options) | 保存对话框 | 返回保存的文件对象 |
asksaveasfilename(**options) | 保存对话框 | 返回保存的文件名 |
命名参数options的常见值如下:
参数名 | 说明 |
defaultextension | 默认后缀:.xxx 用户没有输入则自动添加 |
filetypes=[(label1,pattern1),(label2,pattern2)] | 文件显示过滤器 |
initaldir | 初始目录 |
initalfile | 初始文件 |
parent | 父窗口,默认根窗口 |
title | 窗口标题 |
"""文件对活框获取文件"""
from tkinter import *
from tkinter.filedialog import *
root = Tk();root.geometry("400x100")
def testl():
f=askopenfilename(title="上传文件",initialdir="f:",filetypes=[("视频文件",".mp4"),("文件",".txt")])
#print (f)
show["text"]=f
Button(root,text="选择编辑的视须文件",command=testl).pack()
show = Label(root,width=40,height=3,bg="green")
show.pack()
root.mainloop()
# -*- coding: UTF-8 -*-
from tkinter import *
from tkinter.filedialog import *
root = Tk(); root.geometry("400x100")
def test1():
# with open(askopenfile(title="上传文件", initialdir="d:", filetypes=[("文本文件",".txt")]), encoding='utf-8') as f:
with askopenfile(title="上传文件", initialdir="d:", filetypes=[("文本文件",".txt")]) as f:
show["text"]=f.read()
Button(root, text="选择读取的文本文件",command=test1).pack()
show = Label(root,width=40,height=3,bg="green")
show.pack()
root.mainloop()
简单输入对话框
simpledialog(简单对话框)包含如下常用函数:
函数名 | 说明 |
askfloat(title,prompt,**kw) | 输入并返回浮点数 |
askinteger(title,prompt,**kw) | 输入并返回整数 |
askstring(title,prompt,**kw) | 输入并返回字符串 |
参数中,title表示窗口标题:prompt是提示信息:命名参数kw为各种选项:initialvalue(初始值)、minvalue(最小值)、maxvalue(最大值)。
【示例】简单对话框基本用法
"""简单对活框"""""
from tkinter.simpledialog import *
root = Tk();root.geometry ("400x100")
def test1():
a = askinteger(title="输入年龄", prompt="请输入年龄", initialvalue=18, minvalue=1, maxvalue=150)
# askstring、askfloat据使用方式一样
show['text'] = a
Button(root,text="老高你多大了?请输入", command=test1).pack()
show = Label(root,width=40,height=3,bg="green")
show.pack()
root.mainloop()
通用消息框
messagebox(通用消息框)用于和用户简单的交互,用户点击确定、取消。如下列出了messagebox的常见函数:
函数名 | 说明 | 例子 |
askokcancel(title,message,**options) | OK/Cancel为话框 | |
askquestion(title,message,**options) | Yes/No问题为话框 | |
askretrycancel(title,message,**options) | Retry/Cancel问题对话框 | |
showerror(title,message,**options) | 错误消息对话框 | |
showinfo(title,message,**options) | 消息框 | |
showwarning(title,message,**options) | 警告消息框 |
【示例】
from tkinter import *
from tkinter.messagebox import *
root = Tk();root.geometry("400x100")
a1=showinfo(title="尚学堂", message="Python400集从零开始,深入底层,\
深入算法,打好基础。还手写神经网络")
print (a1)
root.mainloop()
tk子模块控件
我们再前面学的组件是tkinter模块下的组件,整体风格较老较丑。为了弥补这点不足,推出了ttk组件。ttk组件更加美观、功能更加强大。新增了LabeledScale(带标签的Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(数)等组件。
使用ttk组件与使用普通的Tkinter组件并没有多大的区别,只要导入ttk模块即可。tk子模块的官方文档:
https://docs.python.org/3.7/library/tkinter.ttk.html
【注】此处我们不展开细讲ttk。如果你的项目确实需要用到复杂的界面,推荐大家使用wxpython或者pyQt.
菜单和记事本小项目
GU1程序通常都有菜单,方便用户的交互.。我们一般将菜单
分为两种:
1.主菜单
GU1程序通常都有菜单,方便用户的交互。我们一般将菜单
分为两种:
1.主菜单
主菜单通常位于GU1程序上方。例如:
上下文菜单
快捷菜单(上下文菜单)是通过鼠标右键单击组件而弹出的菜单,一般是和这个组件相关的操作,比如:剪切、复制、粘贴、属性等。创建快捷菜单步骤如下:
1.创建菜单
menubar = tk.Menu(root)
menubar.add_command(label="字体")
2.绑定鼠标右键单击事件
def test(event):
menubar..post(event.x_root,event.y_root)#在鼠标右键单击坐标处显示菜单
root.bind("",test)
【示例】为记事本程序增加上下文菜单
#coding=utf-8
"""开发记事本软件的菜单"""
from tkinter.filedialog import *
from tkinter.colorchooser import *
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) #super()代表的是父类的定义,而不是父类对象
self.master = master
self.textpad = None #textpad表示Text文本框对象
self.filename = None
self.pack()
self.createWidget()
def createWidget(self):
# 创建主菜单栏
menubar = Menu(root)
# 创建子菜单:虚线的意思是菜单是否可以独立出来为一个窗口显示 tearoff
# menuFile = Menu(menubar, tearoff=1)
menuFile = Menu(menubar, tearoff=0)
menuEdit = Menu(menubar,tearoff=1)
menuHelp = Menu(menubar)
# 将子菜单加入到主菜单相
menubar.add_cascade(label="文件(F)", menu=menuFile)
menubar.add_cascade(label="编辑(E)", menu=menuEdit)
menubar.add_cascade(label="帮助(H)", menu=menuHelp)
# 添加菜单项
menuFile.add_command(label="新建", accelerator="ctrl+n", command=self.newfile)
menuFile.add_command(label="打开", accelerator="ctrl+o", command=self.openfile)
menuFile.add_command(label="保存", accelerator="ctrl+s", command=self.savefile)
menuFile.add_separator() # 添加分制线
menuFile.add_command(label="退出", accelerator="ctrl+q", command=self.exit)
# 将主菜单栏加到根窗口
root["menu"] = menubar
# 增加快捷键
root.bind("<Control-n>", lambda event:self.newfile())
root.bind("<Control-o>", lambda event:self.openfile())
root.bind("<Control-s>", lambda event:self.savefile())
root.bind("<Control-q>", lambda event:self.exit())
# 文本编辑区
self.textpad = Text(root, width=62, height=25)
self.textpad.pack()
# 创建上下菜单
self.contextMenu = Menu(root, tearoff=0)
self.contextMenu.add_command(label="背景颜色", command=self.openAskColor)
# 为右键绑定事件
root.bind("<Button-3>",self.createContextMenu)
def newfile(self):
self.textpad.delete("1.0","end")
self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt", filetypes=[("文本文档", "*.txt")], defaultextension=".txt")
self.savefile()
def openfile(self):
self.textpad.delete("1.0", "end") # 把text控件中所有的内容清空
with askopenfile(title="打开文本文件") as f:
self.textpad.insert(INSERT, f.read())
self.filename = f.name
def savefile(self):
try:
with open(self.filename, "w") as f:
context = self.textpad.get(1.0, END)
f.write(context)
except TypeError:
self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt",
filetypes=[("文本文档", "*.txt")], defaultextension=".txt")
with open(self.filename, "w") as f:
context = self.textpad.get(1.0, END)
f.write(context)
def exit(self):
root.quit()
def createContextMenu(self,event): # 菜单在鼠标右健单击的坐标处显示
self.contextMenu.post(event.x_root, event.y_root)
def openAskColor(self):
s1 = askcolor(color="red", title="选择背景色")
self.textpad.config(bg=s1[1])
if __name__ == '__main__':
root = Tk()
root.geometry("450x300+200+300")
root.title("百战程序员的简易记事本")
app = Application(master=root)
root.mainloop()
将 python 程序打包成exe文件
我们可以使用pyinstaller模块实现将python项目打包成exe文件。操作步聚如下:
1.安装pyinstaller模块
在pycharm中操作:file->setting-->Project:xxx->Project interpretor,再点击+即可,
2.在pycharm的Terminal终端输入如下命令:
pyinstaller -F xxxpy
【注】相关参数如下:
--icon=图标路径(pyinstaller -F -icon=mw.ico XXXX.py)
-F打包成一个exe文件
-w 使用窗口,无控制台
-c 使用控制台,无窗口
-D 创建一个目录,里面包含exe以及其他一些依赖性文件
3.在项目的dist目录下可以看到生成了exe文件,直接在windows系统中使用即可