如果对您有一丁点帮助,劳烦动动手指点个赞,支持和鼓励是搬砖人不断创作的动力!
我们前面实现的都是基于控制台的程序,程序和用户的交 互通过控制台来完成。
本章,我们将学习 GUI(Graphics User Interface), 即图形用户界面编程,我们可以通过 python 提供的丰富的 组件,快速的实现使用图形界面和用户交互。
GUI编程类似于“搭积木”,将一个个组件(Widget)放到 窗口中。如下是 windows 中的画图软件,就是一个典型的 GUI程序:
上面的各种按钮、菜单、编辑区域等都是一个个组件,它 们都放置到窗口中,并通过增加“对事件的处理”成为一个 完整的程序。
常用的 GUI 库
tkinter(Tk interface)是 Python 的标准 GUI库,支持跨 平台的 GUI程序开发。tkinter 适合小型的 GUI程序编写, 也特别适合初学者学习 GUI编程。本书以 tkinter 为核心进 行讲解。
wxPython 是比较流行的 GUI库,适合大型应用程序开发, 功 能 强 于 tkinter, 整 体 设 计 框 架 类 似 于 MFC(Microsoft Foundation Classes 微软基础类库)。
Qt 是一种开源的 GUI库,适合大型 GUI程序开发,PyQT 是 Qt 工 具 包 标 准 的 Python 实 现 。 我 们 也 可 以 使 用 Qt Desginer 界面设计器快速开发 GUI应用程序。
本章中,涉及大量的 API讲解。学习 API最好的来源就是 官方提供的文档:tkinter 官方网址:
https://docs.python.org/3.7/library/tk.html
或者:http://effbot.org/tkinterbook/ 初学者查找)
(相对规整,适合
btn01 = Button(root)
基于 tkinter 模块创建 GUI程序包含如下 4 个核心步骤:
fromtkinter import *
root = Tk()
btn01 = Button(root)
btn01["text"]= "点我就送花"
def songhua(e):
messagebox.showinfo("Message","送你一朵玫瑰花,请 你爱上我")
print("送你 99 朵玫瑰花")
btn01.bind("",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("",songhua)
root.mainloop() #调用组件的 mainloop 方法,进入事 件循环
通过 geometry(‘wxh±x±y’)进行设置。w 为宽度,h 为高度。 +x 表示距屏幕左边的距离;-x 表示距屏幕右边的距离;+y 表示距屏幕上边的距离;-y 表示距屏幕下边的距离。
【示例】测试 tkinter 主窗口位置和大小的设置 fromtkinter import *
root = Tk()
root.title(“测试主窗口的位置和大小”) root.geometry(“500x400+100+200”) #宽度 500,高度 400;距 屏幕左边 100,距屏幕上边 200
root.mainloop() 执行结果:
图形用户界面是由一个个组件组成,就像小孩“搭积木”一样最终组成了整个界面。有 的组件还能在里面再放置其他组件,我们称为“容器”。Tkinter 的 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应用程序编写的一个主要结构,采用 了面向对象的方式,更加合理的组织代码。
通 过 类 Application 组 织 整 个 GUI 程 序 , 类 Application 继承了 Frame 及通过继承拥有了父类的特性。通过构造函数 __init__()初始化窗口中的对象,通过 createWidgets()方法创 建窗口中的对象。
Frame 框架是一个 tkinter 组件,表示一个矩形的区域。 Frame 一般作为容器使用,可以放置其他组件,从而实现复 杂的布局。
【示例】标准的 GUI程序类的写法
"""测试一个经典的_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(self)
self.btn01["text"] = "点击送花"
self.btn01.pack()
self.btn01["command"]= self.songhua
# 创建一个退出按钮
self.btnQuit = Button(self, text ="退出", command=root.destroy)
self.btnQuit.pack()
def songhua(self):
messagebox.showinfo("送花", "送你99朵玫瑰花")
if name == 'main':
root = Tk()
root.geometry("400x100+200+300")
root.title("一个经典的GUI程序类的测试")
app = Application(master=root)
root.mainloop()
Label(标签)主要用于显示文本信息,也可以显示图像。
Label(标签)有这样一些常见属性:
指定字体和字体大小,如:font = (font_name,size)
显示在 Label上的图像,目前 tkinter 只支持 gif格式。
fg 和 bg
fg(foreground):前景色、bg(background):背景色
justify
针对多行文字的对齐,可设置 justify 属性,可选值"left", “center” or “right”
【示例】Label(标签)的用法
"""测试_Label_组件的基本用法,使用面向对象的方式"""
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):
"""创建组件"""
self.label01 = Label(self, text="百战程序员", width=10, height=2,
bg="black", fg="white")
self.label01["text"] ="ccc"
self.label01.config(fg="red", bg="green")
self.label01.pack()
self.label02 = Label(self, text="高淇老师",
width=10, height=2,
bg="blue", fg="white", font=("黑体", 30) )
self.label02.pack()
# 显示图片
global photo # 把photo声明成全局变量。如果是局部变量,本方法执行完毕后,图像对象销毁,窗口显示不出图片。
photo = PhotoImage(file="imgs/logo.gif")
self.label03 = Label(self, image=photo)
self.label03.pack()
self.label04 = Label(self, text="北京尚学堂\n百战程序员\n老高好帅,就是做饭不行",
borderwidth=5, relief="groove", justify="right")
self.label04.pack()
if name == 'main':
root = Tk()
root.geometry("400x260+200+300")
app = Application(master=root)
root.mainloop()
运行结果如下:
通过学习 Label组件,我们发现可以通过 Options 设置组件的属性,从而控制组件的各 种状态。比如:宽度、高度、颜色、位置等等。
我们可以通过三种方式设置 Options 选项,这在各种 GUI组件中用法都一致。
fred = Button(self, fg=“red”, bg=“blue”)
fred[“fg”]= “red” fred[“bg”]= “blue”
如何查看组件的 Options 选项:
我们可以看到如下的代码:
class Button(Widget):
"""Button widget."""
def__init__(self, master=None, cnf={}, \*\*kw):
"""Construct a button widget with the parent MASTER.
STANDARDOPTIONS
activebackground, activeforeground, anchor, background, bitmap, borderwidth, cursor, disabledforeground, font, foreground highlightbackground, highlightcolor, highlightthickness, image, justify,
padx, pady, relief, repeatdelay, repeatinterval, takefocus, text,
textvariable, underline, wraplength
WIDGET- SPECIFICOPTIONS
command, compound, default, height, overrelief, state, width
"""
Widget.__init__(self, master, 'button', cnf, kw)
上面代码中有:“standard options 标准选项”和“widget-specific options 组件特定选项”。 我们将常见的选项汇总如下:
选项名(别名) | 含义 |
---|---|
activebackground | 指定组件处于激活状态时的背景色 |
activeforeground | 指定组件处于激活状态时的前景色 |
anchor | 指定组件内的信息(比如文本或图片)在组件中如何显示(当所在组件比信 息大时,可以看出效果)。必须为下面的值之一:N、NE、E、SE、S、SW W、NW或 CENTER。比如 NW(NorthWest)指定将信息显示在组件的左 上角 |
background(bg) | 指定组件正常显示时的背景色 |
bitmap | 指定在组件上显示该选项指定的位图,该选项值可以是 Tk_GetBitmap 接收的任何形式的位图。位图的显示方式受 anchor、justify选项的影响。如果 同时指定了 bitmap 和 text,那么 bitmap 覆盖文本;如果同时指定了 bitmap 和 image,那么 image 覆盖 bitmap |
borderwidth | 指定组件正常显示时的 3D边框的宽度,该值可以是 Tk_GetPixels 接收的任 何格式 |
cursor | 指定光标在组件上的样式。该值可以是 Tk_GetCursors 接受的任何格式 |
command | 指定按组件关联的命令方法,该方法通常在鼠标离开组件时被触发调用 |
disabledforeground | 指定组件处于禁用状态时的前景色 |
font | 指定组件上显示的文本字体 |
foreground(fg) | 指定组件正常显示时的前景色 |
highlightbackground | 指定组件在高亮状态下的背景色 |
highlightcolor | 指定组件在高亮状态下的前景色 |
highlightthickness | 指定组件在高亮状态下的周围方形区域的宽度,该值可以是 Tk_GetPixels 接收的任何格式 |
height | 指定组件的高度,以 font 选项指定的字体的字符高度为单位,至少为 1 |
image | 指定组件中显示的图像,如果设置了 image 选项,它将会覆盖 text、bitmap 选项 |
justify | 指定组件内部内容的对齐方式,该选项支持 LEFT(左对齐)、CENTER(居 中对齐)或 RIGHT(右对齐)这三个值 |
padx | 指定组件内部在水平方向上两边的空白,该值可以是 Tk_GctPixels 接收的 任何格式 |
pady | 指定组件内部在垂直方向上两地的空白,该值可以是 Tk_GctPixels 接收的 任何格式 |
relief | 指定组件的 3D 效果,该选项支持的值包括 RAISED、SUNKEN、FLAT、 RIDGE、SOLID、GROOVE。该值指出组件内部相对于外部的外观样式, 比如 RAISED表示组件内部相对于外部凸起 |
selectbackground | 指定组件在选中状态下的背景色 |
selectborderwidth | 指定组件在选中状态下的 3D边框的宽度,该值可以是 Tk_GetPixels 接收的 任何格式 |
selectforeground | 指定组在选中状态下的前景色 |
state | 指定组件的当前状态。该选项支持 NORMAL(正常)、DISABLE(禁用) 这两个值 |
takefocus | 指定组件在键盘遍历(Tab 或 Shift+Tab)时是否接收焦点,将该选项设为 1 表示接收焦点;设为 0 表示不接收焦点 |
text | 指定组件上显示的文本,文本显示格式由组件本身、anchor 及 justify 选 项决定 |
textvariable | 指定一个变量名,GUI 组件负责显示该变量值转换得到的字符串,文本显 示格式由组件本身、anchor 及 justify 选项决定 |
underline | 指定为组件文本的第几个字符添加下画线,该选项就相当于为组件绑定了 快捷键 |
width | 指定组件的宽度,以 font 选项指定的字体的字符高度为单位,至少为 1 |
wraplength | 对于能支持字符换行的组件,该选项指定每行显示的最大字符数,超过该 |
数量的字符将会转到下行显示 | |
xscrollcommand | 通常用于将组件的水平滚动改变(包括内容滚动或宽度发生改变)与水平 滚动条的 set 方法关联,从而让组件的水平滚动改变传递到水平滚动条 |
yscrollcommand | 通常用于将组件的垂直滚动改变(包括内容滚动或高度发生改变)与垂直 滚动条的 set 方法关联,从而让组件的垂直滚动改变传递到垂直滚动条 |
Button(按钮)用来执行用户的单击操作。Button 可以包含 文本,也可以包含图像。按钮被单击后会自动调用对应事件 绑定的方法。
【示例】Button 按钮用法(文字、图片、事件)
"""*测试_Button_组件的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""*创建组件*"""
self.btn01 = Button(root, text="登录",
width=6, height=3, anchor=NE, command=self.login)
self.btn01.pack()
global photo
photo = PhotoImage(file="imgs/start.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("400x130+200+300")
app = Application(master=root)
root.mainloop()
运行结果:
Entry 用来接收一行字符串的控件。如果用户输入的文字 长度长于 Entry 控件的宽度时, 文字会自动向后滚动。如果 想输入多行文本, 需要使用 Text 控件。
【示例】Entry 单行文本框实现简单登录界面
"""*测试_Entry_组件的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
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")
app = Application(master=root)
root.mainloop()
界面效果:
Text(多行文本框)的主要用于显示多行文本,还可以显示 网页链接, 图片, HTML 页面, 甚至 CSS 样式表,添加组件 等。因此,也常被当做简单的文本处理器、文本编辑器或者 网页浏览器来使用。比如 IDLE 就是 Text 组件构成的。
【示例】Text 多行文本框基本用法(文本输入、组件和图像 显示)
"""*测试_Text_多行文本框组件的基本用法,使用面向对象的方式*"""
from tkinter import *
import webbrowser
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
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 ')
# END索引号表示在最后插入
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="imgs/logo.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", "" , self.webshow)
def webshow(self, event):
webbrowser.open("http://www.baidu.com")
if __name__ == '__main__':
root = Tk()
root.geometry("450x300+200+300")
app = Application(master=root)
root.mainloop()
运行结果:
利用 Tags 实现更加强大的文本显示和控制
Tags 通常用于改变 Text 组件中内容的样式和功能。你 可以修改文本的字体、尺寸和颜色。另外,Tags 还允许你 将文本、嵌入的组件和图片与鼠标和键盘等事件相关联。 【示例】利用 Tag 属性实现更复杂文本控制
#coding=utf- 8
fromtkinter import \*
import webbrowser
root = Tk();root.geometry("300x300+400+400")
w1 = Text(root,width=40,height=20) #宽度 20 个字母(10 个汉 字),高度一个行高![ref7]
w1.pack()
w1.insert(INSERT,"good good study,daydayup!\n 百度,搜一下就知道") w1.tag\_add("good",1.0,1.9) w1.tag\_config("good",background="yellow",foreground="red")
w1.tag\_add("baidu",4.0,4.2) w1.tag\_config("baidu",underline=True)
defwebshow(event):
webbrowser.open("http://www.baidu.com")
w1.tag\_bind("baidu","",webshow) root.mainloop()
运行结果:
点击“百度”后,系统默认浏览器打开百度页面。
Radiobutton 控 件 用 于 选 择 同 一 组 单 选 按 钮 中 的 一 个 。 Radiobutton 可以显示文本,也可以显示图像。
【示例】Radiobutton 基础用法
"""*测试_Radiobutton_组件的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
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("400x50+200+300")
app = Application(master=root)
root.mainloop()
运行结果:
Checkbutton 控件用于选择多个按钮的情况。Checkbutton 可以显示文本,也可以显示图像。
【示例】Checkbutton 复选按钮用法
"""*测试_Checkbutton_组件的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
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("测试", "看视频,都是正常人有的爱好!你喜欢看什么类型?")
if self.codeHobby.get() == 1:
messagebox.showinfo("测试", "抓获野生程序猿一只,赶紧送给他尚学堂的视频充饥")
if __name__ == '__main__':
root = Tk()
root.geometry("400x50+200+300")
app = Application(master=root)
root.mainloop()
运行结果:
canvas(画布)是一个矩形区域,可以放置图形、图像、 组件等。本节我们简单介绍 canvas 的使用,更加详细和深 入的内容将在后面的“图形绘制”章节讲解。
【示例】canvas 画布使用基础示例
"""*测试_Canvas_组件的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
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="imgs/logo.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")
app = Application(master=root)
root.mainloop()
运行结果:
一个 GUI应用程序必然有大量的组件,这些组件如何排 布?这时候,就需要使用 tkinter 提供的布局管理器帮助我们 组织、管理在父组件中子组件的布局方式。tkinter 提供了三 种管理器:pack、grid、place。
grid 布 局 管 理 器
grid 表格布局,采用表格结构组织组件。子组件的位置由 行和列的单元格来确定,并且可以跨行和跨列,从而实现复 杂的布局。
grid()方法提供的选项
选项 | 说明 | 取值范围 |
---|---|---|
column | 单元格的列号 | 从 0 开始的正整数 |
columnspan | 跨列,跨越的列数 | 正整数 |
row | 单元格的行号 | 从 0 开始的正整数 |
rowspan | 跨行,跨越的行数 | 正整数 |
ipadx, ipady | 设置子组件之间的间隔,x 方向或者 y 方向, 默认单位为像素 | 非负浮点数,默认 0.0 |
padx, pady | 与之并列的组件之间的间隔,x 方向或者 y 方向,默认单位是像素 | 非负浮点数,默认 0.0 |
sticky | 组件紧贴所在单元格的某一角,对应于东南 西北中以及 4 个角 | “n”, “s”, “w”, “e”, “nw”, “sw”, “se”, “ne”, “center”(默认) |
【示例】grid 布局用法-登录界面设计
"""*测试_Grid_布局管理器的基本用法,使用面向对象的方式*"""
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""*通过grid布局实现登录界面*"""
self.label01 = Label(self, text="用户名")
self.label01.grid(row=0, column=0)
self.entry01 = Entry(self)
self.entry01.grid(row=0, column=1)
Label(self, text="用户名为手机号\n").grid(row=0, column=2)
Label(self, text="密码").grid(row=1, column=0)
Entry(self, show="*").grid(row=1, column=1)
Button(self, text="登录").grid(row=2, column=1, sticky=EW)
Button(self, text="取消").grid(row=2, column=2, sticky=E)
if __name__ == '__main__':
root = Tk()
root.geometry("400x90+200+300")
app = Application(master=root)
root.mainloop()
运行结果:
【示例】通过 grid 布局-实现计算器软件界面。 根据实际简易计算器的按键分布,设计一个相仿的计算器界 面,相应的功能暂不需要实现。
如上界面,实际可以设计成一个 7 行 4 列的表格布局,然后 将相应的按钮放置进去即可。
"""*计算器软件界面的设计*"""
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""*通过grid布局实现计算器的界面*"""
btnText = (("MC", "M+", "M-", "MR"),
("C", "±", "/", "✖"),
(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("200x200+200+300")
app = Application(master=root)
root.mainloop()
pack按照组件的创建顺序将子组件添加到父组件中,按 照垂直或者水平的方向自然排布。如果不指定任何选项,默 认在父组件中自顶向下垂直添加组件。
pack 是代码量最少,最简单的一种,可以用于快速生成
界面。
pack()方法提供的选项
名称 | 描述 | 取值范围 |
---|---|---|
expand | 当值为“yes”时,side 选项无效。组件显示在父配件中心位置;若 fill 选项为”both”,则填充父组件的剩余空间 | “yes”, 自然数,”no”, 0(默认值”no” 或 0) |
fill | 填充 x(y)方向上的空间,当属性 side=”top”或”bottom”时,填充 x 方向;当属性 side=”left”或”right”时,填充”y”方向;当 expand 选 项为”yes”时,填充父组件的剩余空间 | “x”, “y”, “both”,“none” (默认值为 none) |
ipadx, ipady | 设置子组件之间的间隔,x 方向或者 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 是一个矩形区域,就是用来防止其他子组件 overflow
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,20):
Button(f2, width=5, height=10, bg="black" if i%2==0 else "white").pack(side="left")
root.mainloop()
place 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。
place() 方法的选项:
选项 | 说明 | 取值范围 |
---|---|---|
x,y | 组件左上角的绝对坐标 (相对于窗口) | 非负整数 x 和 y 选项用于设置偏移(像素),如果同时设置 relx(rely) 和 x(y),那么 place 将优先计算 relx 和 rely,然后再实现 x 和 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”(默认) |
【示例】place 布局管理-基本用法测试 #coding=utf- 8
from tkinter import *
root = Tk()
root.geometry("500x300")
root.title("布局管理 place")
root["bg"] = "white"
f1 = Frame(root, width=200, height=200, bg="green")
f1.place(x=30, y=30)
Button(root, text="尚学堂").place(relx=0.5, rely=0, x=100, y=200, relwidth=0.2, relheight=0.2)
Button(f1, text="百战程序员").place(relx=0.6, rely=0.7)
Button(f1, text="高淇老师").place(relx=0.2, rely=0.2)
root.mainloop()
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="imgs/puke/puke1.gif")
# self.puke1 = Label(self.master, image=self.photo)
# self.puke1.place(x=10, y=50)
self.photos = [PhotoImage(file="imgs/puke/puke"+str(i+1)+".gif") for i in range(10)]
self.pukes = [Label(self.master, image=self.photos[i]) for i in range(10)]
for i in range(10):
self.pukes[i].place(x=10+i*40, y=50)
# 为所有的Label增加事件处理
self.pukes[0].bind_class("Label", "" , 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 应 用 整 个 生 命 周 期 都 处 在 一 个 消 息 循 环 (event loop) 中。它等待事件的发生,并作出相应的处理。
Tkinter 提供了 用以处理相 关事件的机 制. 处理函 数可被绑 定给各个控件的各种事件。
widget.bind(event, handler)
如 果 相 关 事 件 发 生 , handler 函 数 会 被 触 发 , 事 件 对 象 event 会传递给 handler 函数.
代码 | 说明 |
---|---|
<1> | 鼠标左键按下。 2 表示右键,3 表示中键; |
鼠标左键释放 | |
按住鼠标左键移动 | |
双击左键 | |
鼠标指针进入某一组件区域 | |
鼠标指针离开某一组件区域 | |
滚动滚轮; | |
按下 a 键,a 可用其他键替代 | |
释放 a 键。 | |
按下 A键(大写的 A) | |
同时按下 alt 和 a;alt 可用 ctrl和 shift 替代 | |
快速按两下 a | |
CTRL 和 V键被同时按下,V可以换成其它键位 |
event 对象常用属性
名称 | 说明 |
---|---|
char | 按键字符,仅对键盘事件有效 |
keycode | 按键编码,仅对键盘事件有效 |
keysym | 按键名称,仅对键盘事件有效 比如按下空格键: 键的 char: 键的 keycode:32 比如按下 a 键: 键的 keysym:space 键的 char:a 键的 keycode:65 键的 keysym:a |
num | 鼠标按键,仅对鼠标事件有效 |
type | 所触发的事件类型 |
widget | 引起事件的组件 |
width,height | 组件改变后的大小,仅 Configure 有效 |
x,y | 鼠标当前位置,相对于父容器 |
x_root,y_root | 鼠标当前位置,相对于整个屏幕 |
【示例】鼠标事件和键盘事件用法测试
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("" , mouseTest)
c1.bind("" , testDrag)
root.bind("" , keyboardTest)
root.bind("" , press_a_test) # 只针对小写的a,大写的A不管用
root.bind("" , release_a_test)
root.mainloop()
lambda 表达式定义的是一个匿名函数,只适合简单输入参 数,简单计算返回结果,不适合功能复杂情况。
lambda 定义的匿名函数也有输入、也有输出,只是没有名 字。语法格式如下:
lambda 参数值列表:表达式
参数值列表即为输入。 表达式计算的结构即为输出。
我们写一个最简单的案例:
add3args = lambda x,y,z:x+y+z #print(add3args(10,20,30))
上面的 lambda 表达式相当于如下函数定义:
defadd3args(x,y,z):
return x+y+z
lambda 表达式的参数值列表可以为如下内容:
lambda 格式 | 说明 |
---|---|
lambda x, y: x*y | 函数输入是 x 和 y,输出是它们的积 x*y |
lambda:None | 函数没有输入参数,输出是 None |
lambda:aaa(3,4) | 函数没有输入参数,输出是 aaa(3,4)的结 果 |
lambda *args: sum(args) | 输入是任意个数的参数,输出是它们的和 |
lambda **kwargs: 1 | 输入是任意键值对参数,输出是 1 |
我们在平时使用时,注意 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()
·组件对象的绑定
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("" , mouseTest1)
# command属性直接绑定事件
b2 = Button(root, text="测试command2",
command=lambda: mouseTest2("gaoqi", "xixi"))
b2.pack(side="left")
# 给所有Button按钮都绑定右键单击事件
b1.bind_class("Button", "" , mouseTest3)
root.mainloop()
我们在前面介绍了最常用的几个组件,接下来我们介绍其他 一些组件。
OptionMenu(选 择 项 )用 来 做 多 选 一 , 选 中 的 项 会 在 顶 部 显 示。
【示例】OptionMenu(选择项)的基本用法
# coding=utf-8
"""
optionmenu 的使用测试
"""
from tkinter import *
root = Tk()
root.geometry("200x100")
v = StringVar(root)
v.set("百战程序员")
om = OptionMenu(root, v, "尚学堂", "百战程序员", "卓越班[保底18万]")
om["width"] = 10
om.pack(pady=20)
def test1():
print("最喜爱的机构:", v.get())
# 直接修改了optionmenu中选中的值
v.set("尚学堂")
Button(root, text="确定", command=test1).pack()
root.mainloop()
运行结果:
Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来
选择值。
【示例】使用 Scale(移动滑块)控制字体大小变化 #coding=utf- 8
"""
optionmenu 的使用测试
"""
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, orient=HORIZONTAL, command=test1)
s1.pack()
a = Label(root, text="百战程序员", width=10, height=1, bg="black", fg="white")
a.pack()
root.mainloop()
运行结果:
颜色选择框可以帮助我们设置背景色、前景色、画笔颜色、 字体颜色等等。
【示例】颜色选择框基本用法
# coding=utf-8
"""
askcolor 颜色选择框的测试,改变背景色
"""
from tkinter import *
from tkinter.colorchooser import *
root = Tk()
root.geometry("400x150")
def test1():
s1 = askcolor(color="red", title="选择背景色")
root.config(bg=s1[1])
Button(root,text="选择背景色",command=test1).pack()
root.mainloop()
运行结果:
文件对话框帮助我们实现可视化的操作目录、操作文件。最 后,将文件、目录的信息传入到程序中。文件对话框包含如 下一些常用函数:
函数名 | 对话框 | 说明 |
---|---|---|
askopenfilename(**op tions) | 文件对话框 | 返回打开的文件名 |
askopenfilenames(**o ptions) | 返 回 打 开 的 多 个 文 件名列表 | |
askopenfile(**options) | 返 回 打 开 的 文 件 对 象 | |
askopenfiles(**option s) | 返 回 打 开 的 文 件 对 象的列表 | |
askdirectory(**options ) | 目录对话框 | 返回目录名 |
asksaveasfile(**option s) | 保存对话框 | 返 回 保 存 的 文 件 对 象 |
asksaveasfilename(** options) | 返回保存的文件名 |
命名参数 options 的常见值如下:
参数名 | 说明 |
---|---|
defaultextension | 默认后缀:.xxx 用户没 有输入则自 动添加 |
filetypes=[(label1, pattern1),(labe2,p attern2)] | 文件显示过滤器 |
initaldir | 初始目录 |
initalfile | 初始文件 |
parent | 父窗口 ,默认根窗 口 |
title | 窗口标题 |
【示例】文件对话框基本用法
运行结果:
【示例】打开指定 txt 文件,并读出文件内容到窗口
# coding=utf-8
"""
askopenfile 测试选择文件并读取内容
"""
from tkinter import *
from tkinter.filedialog import *
root = Tk()
root.geometry("400x100")
def test1():
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,promp t,**kw) | 输入并返回浮点数 |
askinteger(title,pro mpt,**kw) | 输入并返回整数 |
askstring(title,prom pt,**kw) | 输入并返回字符串 |
参数中,title 表示窗口标题;prompt 是提示信息;命名参数 kw 为各种选项:initialvalue(初始值)、minvalue(最小值)、 maxvalue(最大值)。
【示例】简单对话框基本用法 #coding=utf- 8 #简单对话框
from tkinter import *
from tkinter.simpledialog import *
root = Tk()
root.geometry("400x100")
show = Label(root, width=40, height=3, bg="green")
show.pack()
a = askinteger(title="输入年龄",
prompt="请输入年龄",
initialvalue=18,
minvalue=1,
maxvalue=150)
show["text"] = "年龄:" + str(a)
# askfloat, askstring 自行测试
root.mainloop()
运行结果:
messagebox(通用消息框)用于和用户简单的交互,用户 点击确定、取消。如下列出了 messagebox 的常见函数:
【示例】通用消息框的基本用法 #coding=utf- 8
#简单对话框
from tkinter import *
from tkinter.messagebox import *
root = Tk()
root.geometry("400x100")
a1 = showinfo(title="尚学堂",
message="Python400集从零开始, 深入底层,\
深入算法,打好基础。还手写神经网络")
print(a1)
root.mainloop()
我们再前面学的组件是 tkinter 模块下的组件,整体风格 较老较丑。为了弥补这点不足,推出了 ttk 组件。ttk 组件更 加 美 观 、 功 能 更 加 强 大 。 使 用 Combobox 替 代 了 原 来 的 Listbox 、 新 增 了 LabeledScale( 带 标 签 的 Scale) 、 Notebook(多文档窗口)、Progressbar(进度条)、Treeview(数) 等组件。
使用 ttk 组件与使用普通的 Tkinter 组件并没有多大的 区别,只要导入 ttk 模块即可。
ttk 子模块的官方文档:
https://docs.python.org/3.7/library/tkinter.ttk.html
GUI程序通常都有菜单,方便用户的交互。我们一般将菜单 分为两种:
主菜单通常位于 GUI程序上方。例如:
主菜单一般包含:文件、编辑、帮助等,位于 GUI窗口的上 面。创建主菜单一般有如下 4 步:
menubar = tk.Menu(root)
file_menu = tk.Menu(menubar) menubar.add_cascade(label=”文件”,menu=file_menu)
file_menu.add_separator() file_menu.add_command(label=”退出”)
【示例】记事本软件主菜单设计
# coding=utf-8
"""
记事本软件,练习主菜单的设计
"""
from tkinter import *
from tkinter.messagebox import *
from tkinter.filedialog import *
root = Tk()
root.geometry("400x400")
# 创建主菜单栏
menubar = Menu(root)
# 创建子菜单
menuFile = Menu(menubar)
menuEdit = Menu(menubar)
menuHelp = Menu(menubar)
# 将子菜单加入到主菜单栏
menubar.add_cascade(label="文件(F)", menu=menuFile)
menubar.add_cascade(label="编辑(E)", menu=menuEdit)
menubar.add_cascade(label="帮助(H)", menu=menuHelp)
filename = ""
def openFile():
global filename
with askopenfile(title="打开文件") as f:
content = f.read()
w1.insert(INSERT, content)
filename = f.name
print(f.name)
def saveFile():
with open(filename, "w") as f:
content = w1.get(1.0, END)
f.write(content)
def exit():
root.quit()
# 添加菜单项
menuFile.add_command(label="打开", accelerator="^O", command=openFile)
menuFile.add_command(label="保存", command=saveFile)
menuFile.add_separator() # 添加分割线
menuFile.add_command(label="退出", command=exit)
# 将主菜单栏加到根窗口
root["menu"] = menubar
w1 = Text(root, width=50, height=30)
w1.pack()
root.mainloop()
运行结果:
快捷菜单(上下文菜单)是通过鼠标右键单击组件而弹出的
菜单,一般是和这个组件相关的操作,比如:剪切、复制、 粘贴、属性等。创建快捷菜单步骤如下:
menubar = tk.Menu(root) menubar.add_command(label=”字体”)
def test(event):
menubar.post(event.x_root,event.y_root) #在 鼠 标 右 键单击坐标处显示菜单
root.bind(“”,test)
【示例】为记事本程序增加快捷菜单
# coding=utf-8
from tkinter import *
from tkinter.colorchooser import *
from tkinter.filedialog import *
root = Tk()
root.geometry("400x400")
def openAskColor():
s1 = askcolor(color="red", title="选择背景色")
root.config(bg=s1[1])
# 创建快捷菜单
menubar2 = Menu(root)
menubar2.add_command(label="颜色", command=openAskColor)
menuedit = Menu(menubar2, tearoff=0)
menuedit.add_command(label="剪切")
menuedit.add_command(label="复制")
menuedit.add_command(label="粘贴")
menubar2.add_cascade(label="编辑", menu=menuedit)
def test(event):
# 菜单在鼠标右键单击的坐标处显示
menubar2.post(event.x_root, event.y_root)
# 编辑区
w1 = Text(root, width=50, height=30)
w1.pack()
w1.bind("" , test)
root.mainloop()
ID: Txtechcom