GUI编程和TKinter介绍
目录
Tkinter
主窗口位置和大小
GUI编程的整体描述
GUI应用程序类的经典写法
简单组件
Lable标签
Option选项详解
Button_anchor位置控制
Entry单行文本框
Text多行文本框
Canvas画布组件
布局管理器
1.pack
2.grid
应用
3.place
事件机制与消息循环原理
lambda表达式_事件传参应用
多种事件绑定方式的汇总
组件对象的绑定
组件类的绑定
其他组件
OptionMenu
Scale移动滑块
颜色选择框
文件对话框
简单输入对话框
通用消息框
ttk子模块控件
菜单
wxPython
PyQT
记事本项目
画图项目
从今天开始,小编要陆陆续续给大家介绍一些关于GUI,即图形用户界面编程。我们可以通过现在的主流语言python中提供的丰富组件来快速得实现使用图形界面与用户交互。
Tkinter
官方文档:Graphical User Interfaces with Tk — Python 3.7.17 documentation
基本步骤:
1.创建应用程序主窗口对象(也称:根窗口)
2.在主窗口中,添加各种可视化组件,比如说:按钮、文本框等
3.通过几何布局管理器,管理组件的大小和位置
4.事件处理
<1>通过绑定时间处理程序,响应用户操作所触发的事件(比如:单击、双击等)
# 第一步我们肯定是要导入我们的tkinter这个库
from tkinter import *
from tkinter import messagebox
# 1.创建一个窗口
root = Tk()
# 2.添加可视化组件
btn01 = Button(root) # 我建立一个按钮对象在这个窗口里
btn01["text"] = "点我你期末考试一定过!"
btn01.pack() # btn01.pack() 是用于布局管理的一部分[紧缩]
# 3.定义一个事件
def exam(e): # e就是事件对象
messagebox.showinfo("Message", "恭喜啊!期末绩点满级!快感谢我!")
print("奖励你1000块钱!")
# 4.事件的绑定
btn01.bind("<Button-1>", exam)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
主窗口位置和大小
在这里我们一般通过geometry(”wxh±x±y“)进行设置。其中w为宽度,h为高度,x为乘号;+x表示距离左边的屏幕;-x表示距离屏幕右边的屏幕;+y表示距离上边的屏幕;-y表示距离下边的屏幕;
代码展示:
# 1.创建一个窗口
root = Tk()
root.title("大学生期末不信邪")
root.geometry("500x300+300+200")
结果展示:
GUI编程的整体描述
图形用户界面是有一个个组件组成的,就像小孩子“搭积木”一样最终组成了整个界面,有的组件还能在里面再放置其他组件,我们称作为“容器”。Tkinter的GUI组件关系图如下:
注:上图来源于网络,如有作者介意请及时联系我
常见的组件汇总列表:
GUI应用程序类的经典写法
本小节是GUI应用程序编写的一个主要结构,采用了解面对对象的方式,更加合理的组织代码。通过Application组织整个GUI程序,类Application继承了Frame及通过继承有了父类的特性。通过构造函数_init_()初始化窗口中的对象,通过createWidgets()方法创建窗口中的对象。【Frame框架是一个tkinter组件,表示一个矩形的区域。Frame一般作为容器使用,可以放置其他组件,从而实现复杂的布局。】
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
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("送花", "送你一朵小红花!") # 点击按钮即可弹出一个窗口,窗口标题为“送花”,窗口内容为“送你一朵小红花”
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("400x200+200+300") # 该根窗口的大小以及在整个屏幕中的位置
root.title("这是一个经典的GUI程序类的测试") # 该窗口的标题
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
代码重难点解释:
self.btn01 = Button(self)
Button(self)
创建一个按钮,self
代表 Application
这个 Frame
容器,表示按钮属于 Application
组件。
self.btn01["command"] = self.songhua
command
属性用于指定按钮点击后执行的函数,这里绑定的是 self.songhua
方法,即点击按钮后会弹出消息框。
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
- 创建
Application
的实例app
,并以root
作为master
(父窗口)。 - 这一步会:
- 生成一个
Frame
组件(即Application
继承的Frame
)。 - 运行
createWidget()
方法,创建两个按钮。
- 生成一个
结果的可视化展示:
当运行该程序的时候,就会弹出这么一个窗口。这个窗口里面包含了两个按钮,一个按钮是“点击送花”,另外一个按钮是“退出”。“点击送花”和“退出”都是属于按钮的文本内容,这个文本内容是可以更改的。
当我们点击“点击送花”这个按钮的时候,就会出现另外一个窗口,该窗口的标题为“送花”,该窗口的内容为“送你一朵小红花”。其窗口跟标题是可以根据不同需要进行更改的。
简单组件
Lable标签
Lable(标签)主要用于显示文本信息,也可以显示图像。
Lable(标签)有以下常见的属性:
1.width,height
用于指定区域大小。如果显示是文本,则以单个英文字符大小为单位(一个汉字占2个字符位置,高度和英文字符一样);如果显示的是图像,则以像素为单位,默认值是根据具体显示的内容动态调整。
2.font
指定字体和字体的大小,如font = (font_name,size)
3.image
显示在Lable上的图像,目前tkinter只支持gif格式。
4.fg 和 bg
fg(foreground):前景色;bp(background):背景色
5.justify
针对多行文字的对齐,可设置justify属性,可选值“left”,“center”,“right”
属性总结:
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
"""创建组件"""
self.lable01 = Label(self, text="我要成为一名程序员",
width=16, height=2, bg="black", fg="white")
self.lable01.pack()
self.lable02 = Label(self, text="我要顺利找到工作",
width=16, height=2, bg="blue", fg="white",
font=("黑体", 30))
self.lable02.pack()
# 显示图像
global photo # 把photo对象定义为全局变量。如果只是局部变量,本方法执行完毕后,图像对象就会被销毁,窗口显示不出图像。
# 1.创造一个图像对象
image = Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\cat.gif")
photo = ImageTk.PhotoImage(image)
# 2.定义一个新的Lable
self.lable03 = Label(self, image=photo)
# 3.布局
self.lable03.pack()
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("800x700+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
注意:这里需要的一个点是【也是小编在写代码的过程遇到的问题】就是在导入图片的时候,并不是单纯得把图片得后缀改成GIF图片得属性就会发生改变的,本质上图片的属性还是原来的属性,所以在导入的时候必须要转换格式,否则就会报错;
结果展示:
Option选项详解
通过学习Lable组件,我们发现可以通过Options设置组件的属性,从而控制组件的各种状态,比如:宽度,高度、颜色、位置等等
我们可以通过三种方式设置Option选项,这在各种GUI组件中用法都一致
1.创建对象时,使用命名参数(也叫做关键字参数)
fred = Button(self,fg="red",bg="blue")
2.创建对象后,使用字典索引方式
fred["fg"] = "red"
fred["bg"] = "blue"
3.创建对象后,使用config()方法
fred.config(fg="red",bg="blue")
Option选项属性大合集
代码示例:
from tkinter import * # 导入 Tkinter 库,用于创建 GUI 界面
# 创建主窗口
root = Tk()
root.geometry("300x250") # 设置窗口大小:宽 300 像素,高 250 像素
# 绑定变量,用于存储用户选择的值
var = StringVar() # 创建一个字符串变量,绑定到 OptionMenu
var.set("请选择") # 设置 OptionMenu 的默认显示值
# 选项变更回调函数,每当用户选择一个选项时执行
def option_selected(value):
print(f"用户选择了:{value}") # 打印用户选择的选项
# 创建 OptionMenu(下拉菜单)
option_menu = OptionMenu(root, var, "Python", "Java", "C++", command=option_selected)
# 其中 root 为父窗口,var 绑定的变量,后续的 "Python", "Java", "C++" 是初始选项
# command=option_selected 绑定了选项变更时的回调函数
# 配置 OptionMenu 按钮的外观
option_menu.config(
font=("Arial", 12), # 设置字体为 Arial,字号 12
fg="white", # 文字颜色设为白色
bg="blue", # 按钮背景颜色设为蓝色
relief="ridge" # 按钮的边框样式(ridge = 突起的边框)
)
# 设置 OptionMenu 在界面中的布局
option_menu.pack(pady=20) # 使用 pack 布局,并在 Y 轴方向增加 20 像素的间距
# 获取 OptionMenu 的 `menu` 组件(即实际的下拉菜单)
menu = option_menu["menu"]
# 动态添加选项到下拉菜单
menu.add_command(label="Go", command=lambda: var.set("Go"))
# 添加选项 "Go",并绑定点击后将变量 var 设置为 "Go"
menu.add_command(label="JavaScript", command=lambda: var.set("JavaScript"))
# 添加选项 "JavaScript",并绑定点击后将变量 var 设置为 "JavaScript"
# 进入主事件循环,运行 Tkinter 窗口
root.mainloop()
结果展示:
Button_anchor位置控制
Button(按钮)用来执行用户的单机操作。Button可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定方法【与用户的交互】。
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
"""创建组件"""
self.btn01 = Button(root, text="登录", command=self.login)
self.btn01.pack() # 布局管理
global photo
image = Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\校徽.jpg")
photo = ImageTk.PhotoImage(image) # 进行图片类型上的转换
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("800x700+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
如上图所示,当我们点击登录这个文本按钮或者”南京邮电大学校徽“这个图片按钮的时候都会出现这个”私车公用报销系统“这个弹窗
如上图所示,当我们修改代码时,我们发现登录文本按钮依旧是可以使用的,但是发现这个图片按钮被打上了马赛克,其实是因为在代码里面,设置了这个按钮禁用。
Entry单行文本框
Entry是用来接受一行字符串的控件。如果用户输入的文字长度长于Entry控件时,文字就会自动向后滚动。如果想输入多行文本,需要Text控件。
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
"""创建登录界面的组件"""
self.lable01 = Label(self, text="用户名")
self.lable01.pack()
# StringVar变量绑定到指定的组件
# StringVar变量的值发生变化,组件内容也发生变化
# 组件内容发生变化,StringVar变量的值也发生变化
# StringVar和组件内容的值是双向关联的
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.pack()
self.lable02 = Label(self, text="密码")
self.lable02.pack()
v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2)
self.entry02.pack()
self.button01 = Button(self, text="登录", command=self.login)
self.button01.pack()
# # 测试以下是否是相互关联的关系
# v1.set("云倩怡")
# print(v1.get())
# print(self.entry01.get())
def login(self):
messagebox.showinfo("教务系统", "登录成功!")
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("200x150+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
如上述的代码所示,小编这里只是做了一个简单的示范,后续有很多功能可以进行获取,详细可以看前面发的那个网址的文档。在小编以往的文章中有关于MySQL数据库的内容,关于数据库中操作有详细的展示。所以在后续小编再用其与数据库的数据结合到一块,实现一个简单的登录和信息查询系统。
结果展示:
如上图所示,有标签”用户名“和”密码“,也有按钮”登录“,同时还有两个entry框,注意这两个entry框关联的变量是不用的,详细请看代码中的注释。
Text多行文本框
Text(多行文本框)的主要用于显示多行文本框,还可以显示网页链接,图片,HTML页面,设置CSS样式表,添加组件等。因此,也常被当做简单的文本处理器、文本编辑器或者网页浏览器来使用。比如IDLE就是Text组件构成的。
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
import webbrowser
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
# 宽度为20个字母(10个汉字),高度一个行高
self.w1 = Text(root, width=60, height=20, bg="gray")
self.w1.pack()
# 刚开始的这两个数字表示的是第几行第几列
self.w1.insert(1.0, "012345678\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, "[set]")
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
image = Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\yundong.jpg")
self.photo = ImageTk.PhotoImage(image) # 进行图片类型上的转换
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你需要找个人修一下了\n百度搜一下解决方案")
self.w1.tag_add("text", 1.0, 2.3) # 往哪个位置插入什么内容
# 这个标签的名字可以随便取【但是上下要一致】
self.w1.tag_config("text", background="yellow", foreground="red")
self.w1.tag_add("baidu", 5.0, 5.9)
self.w1.tag_config("baidu", underline=True, background="white")
self.w1.tag_bind("baidu", "<Button-1>", self.webshow)
def webshow(self, event):
print("webshow 被触发了!") # 调试信息
webbrowser.open("https://www.baidu.com/?tn=15007414_13_dg")
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("400x400+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
1.如图所示,当我点击第一个按钮的时候,就会自动在光标所在处插入”Gaoqi“,在文本的末尾处出现”[set]“
2.如图所示,在控制台上出现了test框中的所有文本内容
3.如图所示,在文本框中出现了我所加的图片,这里要注意的是图片的形式,必须是GIF形式的,如果不是记得要及时进行图片类型的转换。
4.如图所示,但我点击对应按钮的时候,在test框中就会按顺序在文本的末尾出现该按钮。其按钮的位置可以任意设置。在此小编不做过多的赘述,感兴趣的同学可以阅读文档或者是去相关社区看看。
5.如图所示,白色的底的文字是一个标签,我们使其变成一个按钮与一个事件绑定在一起,当点击其按钮的时候就会跳转到相关的百度页面。
Radiobutton单选按钮
Radiobutton控件用于选择同一组单选按钮中的一个。Radiobutton可以显示文本,也可以显示图像
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
self.v = StringVar()
self.v.set("F")
self.r1 = Radiobutton(root, text="男性", value="M", variable=self.v) # 进行组件的绑定
self.r2 = Radiobutton(root, text="女性", value="F", variable=self.v) # 进行组件的绑定
self.r1.pack(side="left")
self.r2.pack(side="left")
Button(root, text="确定", command=self.confirm).pack(side="left")
def confirm(self):
messagebox.showinfo("测试", "选择的性别:" + self.v.get())
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("200x200+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
如上图所示,在刚开始的时候设置v为”F“,所以在刚刚弹出来的时候默认选的是女性。
Checkbutton复选按钮
Checkbutton控件用于选择多个按钮的情况。Checkbutton可以显示文本,也可以显示图像。
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
self.v = StringVar()
self.v.set("F")
self.r1 = Radiobutton(root, text="男性", value="M", variable=self.v) # 进行组件的绑定
self.r2 = Radiobutton(root, text="女性", value="F", variable=self.v) # 进行组件的绑定
self.r1.pack(side="left")
self.r2.pack(side="left")
Button(root, text="确定", command=self.confirm).pack(side="left")
def confirm(self):
messagebox.showinfo("测试", "选择的性别:" + self.v.get())
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("200x200+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
当小编勾选”敲代码“时,再点击”确定“,即可出现如下图的内容
当小编勾选”刷抖音“时,再点击”确定“,即可出现如下图的内容
注意:Checkbutton复选按钮和Radiobutton单选按钮的区别就在于,Checkbutton复选按钮可以多选,即”敲代码“和”刷抖音“可以同时选上
Canvas画布组件
Canvas(画布)是一个举行区域,可以放置图形、图像、组件等。本节我们简单介绍Canvas的使用,更加详细和深入的内容将在后面的”图形绘制“章节讲解。
Canvas方法
修改图形对象
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
import random
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
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
# image = Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\校徽.jpg")
# photo = ImageTk.PhotoImage(image) # 进行图片类型上的转换
# self.canvas.create_image(200, 200, image=photo)
Button(self, text="画十个矩形", command=self.draw50Rceg).pack(side="left")
def draw50Rceg(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, width=1)
if __name__ == '__main__':
root = Tk() # 这里依旧是需要创建一个根窗口的
root.geometry("600x600+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
布局管理器
一个GUI应用程序必然有大量的组件,这些组件如何排布?这时候,就需要使用tkinter提供布局管理器帮助我们组织、管理在父组件中子组件的布局方式。tkinter提供了三种管理器:
1.pack
pack按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平方向自然排布。如果不指定任何选项,默认在父组件中自顶垂直添加组件。pack是代码量最少,最简单的一种,可以用于快速生成界面。
pack()方法提供的选项
2.grid
grid表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。
grid()方法提供的选项
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
import random
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
"""通过grid布局实现登录页面"""
self.lable01 = Label(self, text="用户名")
self.lable01.grid(row=0, column=0) # 把”用户名“这个标签通过grid管理器放在第一行第一列的位置
self.entry01 = Entry(self)
self.entry01.grid(row=0, column=1)
Label(self, text="(用户名为手机密码)").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("400x140+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
应用
通过grid布局-实现计算器软件页面:根据实际简易计算器的按键分布,设计一个相仿的计算器界面,相应的功能暂且不需要实现。
代码展示:
# 测试一个经典的GUI程序的写法,使用面对对象的方式
import random
from tkinter import *
from tkinter import messagebox
from PIL import Image, ImageTk
class Application(Frame):
"""一个经典的GUI程序的类的写法"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
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) # 这个文本框在第0行第0列且跨越4列
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("300x230+200+300") # 该根窗口的大小以及在整个屏幕中的位置
app = Application(master=root) # Application 类的实例 app 以 root 作为 master(父窗口)
root.mainloop() # 调用组件的mainloop()方法,进步事件循环
结果展示:
3.place
place布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。
place()方法提供的选项
注:如果x和relx同时出现,先是确定relx再确定x,就是先根据relx把这个框先固定下来,再根据x进行平移。【y和rely也是一样的】
代码展示:
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.4, rely=0.4, 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, rely=0.2)
root.mainloop()
结果展示:
应用:place布局管理-扑克牌游戏
代码展示:
"""扑克牌游戏的界面设计"""
from tkinter import * # 导入 tkinter 库,该库是 Python 的标准 GUI 开发工具
from PIL import Image, ImageTk
class Application(Frame):
def __init__(self, master=None): # self 代表 实例本身(即当前对象)
super().__init__(master) # 让 Application 继承 Frame 的所有功能
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""通过place布局管理器实现扑克牌位置控制"""
# 主要是做测试用的
# image = Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\puke\puke1.png")
# self.photo = ImageTk.PhotoImage(image) # 进行图片类型上的转换
# # self.puke1 = Label(self, image=self.photo) # # 图片对象被销毁,Label 无法显示[如果这样子写的话会导致图片无法显示出来]
# self.puke1 = Label(root, image=self.photo)
# # self.puke1.pack() # 这个是pack来进行测试一下
# self.puke1.place(x=10, y=10)
# 下面我们要连续加入10张牌
self.photos = [
ImageTk.PhotoImage(Image.open(r"C:\Users\云倩怡\PycharmProjects\GUI\imgs\puke\puke" + str(i + 1) + ".png"))
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 * 70, y=50)
# 为所有的Label增加事件处理
for puke in self.pukes:
puke.bind("<Button-1>", self.chupai) # 进行事件的绑定
def chupai(self, event):
print(event.widget.winfo_geometry())
print(event.widget.winfo_x)
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("870x330+100+200")
app = Application(master=root)
root.mainloop()
结果展示:
结果如上图所示,当我们点击对应的Label的时候,对应的标签的y值就会发生变化,从而实现”上升”的效果。【这个图片是小编从网上截图的,可能有些许的不清晰,请大家见谅】
事件机制与消息循环原理
一个GUI应用整个生命周期都处在一个消息循环(event loop)中。它等待事件的发生,并作出相应的处理。Tkinter提供了用于处理相关事件的机制。处理函数可被绑定给各个控件的各种事件。【widget.bind(event,handle):不同的事件对应着不同的方法(方法由我们自己来定义);widget:哪个区域要干什么事】
鼠标事件
键盘事件
event对象常用属性
代码展示:
from tkinter import *
root = Tk()
root.geometry("530x300")
c1 = Canvas(root, width=200, height=200, bg="green") # 这是一个画布,宽200,高200,背景是绿色的
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("键的keyboard:{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键
root.bind("<KeyRelease-a>", release_a_test) # 松开键盘上的a键
root.mainloop()
结果展示:
如上图所示就是本次一个简单的示例,小编这边建议大家还是把上述代码复制然后到自己的电脑上进行测试一下,多加几个示例进行多方面的测试。
lambda表达式_事件传参应用
lambda表达式定义的是一个匿名函数,只适合简单输入参数,简单计算返回结果,不适合功能复杂情况。lambda定义的匿名函数也有输入,也有输出,只是没有名字,语法格式如下:【lambda 参数值列表:表达式】;其中参数值列表即为输入,表达式计算的结构即为输出。
例如:
add3args = lambda x,y,z:x+y+z
print(add3args (10,20,30))
上述代码等效于下述这个函数表达式
def add3args(x,y,z):
return x+y+z
lambda表达式的参数值列表
或者可以参考一下这个
应用:
使用lambda表达式实现传参
1.使用lambda帮助command属性绑定时传参:
代码展示:
from tkinter import *
root = Tk()
root.geometry("270x100") # 这里需要注意的一个操作就是”x“不要写成”*“
def mouseTest1():
print("command方式,简单情况:不涉及获取event对象,可以使用")
def mouseTest2(a, b):
print("a={0},b={1}".format(a, b)) # 这里的{0}位置就是a值取代的位置,{1}位置就是b值取代的位置
# 设置两个按钮
Button(root, text="测试command1",
command=mouseTest1).pack(side="left") # 当点击这个按钮时就会跳转到mouseTest1
Button(root, text="测试command2",
command=lambda: mouseTest2("xiaoyun", "good")).pack(side="left") # 当点击这个按钮时就会跳转到mouseTest1
root.mainloop() # 进入事件循环
结果展示:
如上图所示,当我点击”测试command1“的时候就会在控制台出现我们在代码里面预设打印的那一句话。然而当我们点击”测试command2“的时候就会通过lambda表达式对对应的函数进行一个简单的传参,也是会出现预设结果。
多种事件绑定方式的汇总
组件对象的绑定
1.通过command属性进行绑定(适合简单不需要获取event对象)
Button(root, text="登录", command=login)
如上述代码所示,我们设置了一个按钮组件,父类是root,名字叫做”登录“,绑定的对象是login
2.通过bind()方法绑定 (适合需要获取event对象)
c1=Canvas()
c1.bind("<Button-1>", drawLine)
如上述代码所示,先画一个画布,在这个画布中用”鼠标左键“绑定事件”drawLine“。即当用户点击”鼠标左键“时,事件”drawLine“就会随即发生。
组件类的绑定
调用对象的bind_class函数,将该组件类所有的组件绑定事件
公式:w.bind_class("Widget","event",eventhanler)
例如:
btn01.bind_class("Button","Button-1",func)
汇总表
代码示例:
from tkinter import *
from functools import partial
root = Tk() # 先生成一个窗口
root.geometry("300x200") # 设置窗口的大小
# 1️⃣ command 绑定按钮点击事件
def button_click():
print("按钮被点击!")
btn = Button(root, text="点击我", command=button_click)
btn.pack(pady=10) # pady=10:这个是设置边内距
# 2️⃣ bind() 绑定键盘事件
def key_press(event):
print(f"按下键: {event.keysym}")
root.bind("<Key>", key_press) # 绑定整个窗口的键盘事件【只要按下键盘的任何一个都会触发key_press事件的生成】
# 3️⃣ bind_class() 绑定所有 Entry 组件的焦点事件
def on_focus(event):
print("一个 Entry 获得焦点!")
entry1 = Entry(root)
entry2 = Entry(root)
entry1.pack(pady=5)
entry2.pack(pady=5)
root.bind_class("Entry", "<FocusIn>", on_focus) # 所有 Entry 组件共享同一个事件
root.mainloop()
结果示例:
其他组件
OptionMenu
OptionMenu(选择项)用来做多选一,选中的项在顶部显示。
OptionMenu方法总结
应用:OptionMenu的基本用法
代码展示:
from tkinter import *
root = Tk()
root.geometry("300x200")
label = Label(text="商场厕所", font=20)
label.pack()
# 1️⃣ 定义变量并设置默认值
selected_value = StringVar() # 像这种默认选项基本上都是这种用法【在前面的单选多选里面有涉及到】
selected_value.set("女厕所") # 默认选项
# 2️⃣ 创建下拉菜单
options = ["女厕所", "男厕所", "家庭厕所", "小朋友厕所"]
dropdown = OptionMenu(root, selected_value, *options)
dropdown.pack(pady=20)
# 3️⃣ 绑定选项变化的事件
def on_option_change(value):
print(f"当前选择: {value}")
selected_value.trace_add("write", lambda *args: on_option_change(selected_value.get()))
root.mainloop()
结果展示:
如图所示,在这个窗口中,显示一个大的label”商场厕所‘,接着就是一个提供可选择的下拉选项【其默认值是“女厕所”(这个默认值的设置是比较单一的,在前面我们讲单选按钮和多选按钮的时候已经讲过一遍了)】
重点代码讲解:
dropdown = OptionMenu(root, selected_value, *options)
如上述代码所讲述,括号里的第一个是父类【就是你要在哪里部署这个选择项】,第二个就是默认值,第三个就是可提供选项的列表。
※后续我们也可以通过selected_value.get()来获得所选中的选项
Scale移动滑块
Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值
Scale移动滑块基本方法总结
例如:用Scale移动滑块控制字体大小的变化
代码示例:
from tkinter import *
root = Tk()
root.geometry("300x200")
# 1️⃣ 创建一个变量来存储滑块的值
scale_value = IntVar()
# 2️⃣ 创建 Scale 滑块
# from_=0, to=100:滑块的最小值和最大值
# orient=HORIZONTAL:滑块方向(HORIZONTAL 水平 / VERTICAL 垂直)
# length=200:滑块长度
# variable=scale_value:绑定 IntVar() 变量存储滑块的值
scale = Scale(root, from_=0, to=100, orient=HORIZONTAL, length=200, variable=scale_value)
scale.pack(pady=20)
# 3️⃣ 显示当前滑块值
# 设置一个标签
label = Label(root, text="当前值: 0")
label.pack()
# 4️⃣ 绑定滑块变化的事件
def update_label(value):
label.config(text=f"当前值: {value}")
scale.config(command=update_label) # 滑块移动时触发 update_label(value),实时更新 Label 显示的值
root.mainloop()
注:重要的代码,小编已经在代码的注释中展现了
结果展示:
如上图所示
颜色选择框
颜色选择可以帮助我们设置背景色、前景色、画笔颜色、字体颜色等等
应用:颜色选择框基本用法
代码展示:
"""ackcolor颜色选择框的测试,改变背景色"""
from tkinter import *
from tkinter.colorchooser import *
root = Tk() # 创建根窗口
root.geometry("400x150") # 设置这个根窗口的大小
def test1():
s1 = askcolor(color="red", title="请选择一个你想要的颜色")
print(s1)
# s1的值是:((0.0,0.0,255.99609375),’#0000ff‘)
if s1: # 确保用户选择了颜色
root.config(bg=s1[1]) # 设置背景颜色[s1[1] 是 十六进制颜色('#0000ff')]
Button(root, text="选择背景色", command=test1).pack() # 点击按钮就是触发事件test1
root.mainloop() # 循环事件
结果展示:
如上图所示,当我点击”选择背景色“按钮的时候,就会跳出一个选择颜色的窗口,刚开始默认的颜色是”红色“,而且这个窗口的标题是”请选择一个你自己喜欢的颜色“。这几个信息都与我们代码中所设置的信息一致。
如图所示,但我们选择一个”颜色“,并点击确定,颜色窗口随之关闭且根窗口也变成对应的颜色。
如图中运行窗口中显示,255是代表红色的占比,第一个0是代表绿色的占比,第二个0代表的是红色的占比。【因为这个颜色是红色,所以是(255,0,0)这个组合,不同颜色组合不同】
文件对话框
文件对话框帮助我们实现可视化的操作目录,操作文件。最后,将文件和目录的信息传入到程序中。
文件对话框常用函数
命名参数options的常见值
代码展示:
"""文件对话框获取文件"""
from tkinter import *
from tkinter.filedialog import *
root = Tk() # 创建根窗口
root.geometry("400x100") # 设置窗口的大小
def test1():
f = askopenfilename(title="上传文件", initialdir="f:",
filetypes=[("视频文件", ".mp4")])
# print(f)
show["text"] = f
Button(root, text="请选择要编辑的文件", command=test1).pack()
show = Label(root, width=40, height=3, bg="green")
show.pack()
root.mainloop() # 循环事件
结果展示:
结果是上图所示,当我们点击”请选择要编辑的文件“这个按钮的时候就会触发选择文件事件,跳转到选文件页面,当我们选择好一个mp4文件后点击确认,这个标签里面就会显示视频的地址。
简单输入对话框
简单对话框(simpledialog)常用函数
应用:简单对话框基本用法
代码展示:
"""简单输入对话框"""
from tkinter import *
from tkinter.simpledialog import *
root = Tk() # 创建一个根窗口
root.geometry("400x200") # 设置根窗口的大小以及相对与屏幕的指定位置
# 设置全局字体,使 askinteger() 对话框更大
root.option_add("*Font", "Arial 16")
def test1():
a = askinteger(title="输入年龄", prompt="请输入年龄", initialvalue=18, minvalue=1, maxvalue=130)
show["text"] = a
Button(root, text="请点击这里输入你的年龄", command=test1).pack()
show = Label(root, width=40, height=5, bg="green")
show.pack()
root.mainloop() # 循环事件
结果展示:
如上图所示,当我们点击按钮的时候就会跳转到对话框事件,且这个对话框事件的标题,对话框提示的文本,都是与我们代码预设的一致,当我们输入一个1到130的数字,并点击确认时就会出现上述的第二张图。
通用消息框
通用消息框(messagebox)用于和用户 简单的交互,用户点击确定、取消。
messagebox常见函数
应用:通用消息框基本用法
代码展示:
from tkinter import *
from tkinter.messagebox import *
root = Tk() # 创建根窗口
root.geometry("400x100") # 设置根窗口的大小
def show_message():
showinfo(title="小云GUI小课堂", message="跟紧小云,小云带你勇闯人生十字路口\n小云保证带你飞黄腾达")
# 按钮点击后才会弹出消息框
Button(root, text="点击查看小云致辞", width=20, height=2, command=show_message).pack()
root.mainloop() # 事件循环
结果展示:
ttk子模块控件
我们前面学的组件是tkinter模块下的组件,整体风格较老较丑。为了弥补这点不足,推出了ttk组件。ttk组件更加的美观,功能更加得强大。新增了LabeledScale(带标签的Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(树)等组件。
使用ttk组件与使用普通的Tkinter组件并没有多大区别,只要导入ttk模块即可。
ttk子模块的官方文档:
tkinter.ttk — Tk themed widgets — Python 3.7.17 documentation
注:此处我们不展开细讲ttk。如果你的项目确实需要用到复杂界面,推荐大家使用wxpython或者pyQt
菜单
GUI程序通常都会有菜单,方便用户的交互。我们一般将菜单分为两种:
1.主菜单:主菜单一般位于GUI程序的上方
2.上下文菜单:(快捷菜单)是通过鼠标右键单击组件而弹出的菜单,一般是和这个组件相关的操作,比如:剪切,复制,粘贴,属性等
代码展示:
from tkinter import *
from tkinter.colorchooser import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
def createWidget(self):
# 创建主菜单栏
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)
# 添加菜单项
menuFile.add_command(label="新建", accelerator="ctrl+n", command=self.test) # 这个只是一个文本的提示,还没有涉及到事件的绑定
menuFile.add_command(label="打开", accelerator="ctrl+o", command=self.test)
menuFile.add_command(label="保存", accelerator="ctrl+s", command=self.test)
menuFile.add_separator() # 添加一个分割线
menuFile.add_command(label="退出", accelerator="ctrl+q", command=self.test)
# 将主菜单栏加到根窗口
root["menu"] = menubar
# 文本编辑区
self.textpad = Text(root, width=50, height=30)
self.textpad.pack()
# 创建上下文菜单
self.contextMenu = Menu(root)
self.contextMenu.add_command(label="背景颜色", command=self.test)
# 右键绑定事件
root.bind("<Button-3>", self.createContextMenu)
def test(self):
pass
def createContextMenu(self, event):
# 菜单在鼠标右键单击的坐标处显示
self.contextMenu.post(event.x_root, event.y_root) # self.contextMenu.post(x, y) 在鼠标位置显示右键菜单
if __name__ == '__main__':
root = Tk()
root.geometry("450x300")
root.title("小云的笔记本")
app = Application(master=root)
root.mainloop() # 循环事件
结果展示:
wxPython
PyQT
对于上面所有得内容,我们来一个知识点的大杂烩【做一个项目来巩固知识】
记事本项目
代码展示:
from tkinter import *
from tkinter.colorchooser import *
from tkinter.filedialog import askopenfile, asksaveasfilename
class Application(Frame):
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 存储 master 引用,方便后续在 Application 类中访问父窗口
self.pack() # pack() 是 Tkinter 的 布局管理方法,作用是自动调整窗口大小以适应子组件
self.createWidget() # 调用自定义的 createWidget() 方法,该方法用于添加 UI 组件
self.bind_shortcuts() # 绑定快捷键
def createWidget(self):
# 创建主菜单栏
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)
# 添加菜单项
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
# 文本编辑区
self.textpad = Text(root, width=50, height=30) # 这是一个文本编辑区
self.textpad.pack()
# 创建上下文菜单
self.contextMenu = Menu(root)
self.contextMenu.add_command(label="背景颜色", command=self.openAskColor)
# 右键绑定事件
root.bind("<Button-3>", self.createContextMenu)
# 打开文件
def openfile(self, event=None):
""" 打开文件,并在文本框中显示内容 """
file = askopenfile(title="打开文本文件", filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])
if file: # 确保文件有效
file_path = file.name # 获取文件路径
file.close() # 关闭 askopenfile() 返回的文件对象
with open(file_path, "r", encoding="utf-8") as f: # 这里指定 encoding
content = f.read()
self.filename = f.name
self.textpad.delete("1.0", "end") # 清空文本框
self.textpad.insert("1.0", content) # 插入文件内容[1.0代表从第一行第0列插入]
# 保存文件
def savefile(self, event=None):
""" 保存文件内容到当前文件 """
if not self.filename: # 如果文件名为空
self.filename = asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
if not self.filename: # 如果用户取消保存对话框
return
with open(self.filename, "w", encoding="utf-8") as f:
f.write(self.textpad.get("1.0", "end-1c")) # 获取文本内容并保存
self.textpad.delete("1.0", "end") # 清空文本框
# 推出对话框
def exit(self, event=None):
root.quit()
# 新建文件
def newfile(self, event=None):
self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt",
filetypes=[("文本文档", "*txt")], defaultextension=".txt")
self.savefile()
def openAskColor(self):
s1 = askcolor(color="red", title="请你选择一个你想要的颜色")
self.textpad.config(bg=s1[1])
def createContextMenu(self, event):
# 菜单在鼠标右键单击的坐标处显示
self.contextMenu.post(event.x_root, event.y_root) # self.contextMenu.post(x, y) 在鼠标位置显示右键菜单
# 快捷键
def bind_shortcuts(self):
"""绑定快捷键"""
root.bind("<Control-n>", self.newfile)
root.bind("<Control-o>", self.openfile)
root.bind("<Control-s>", self.savefile)
root.bind("<Control-q>", self.exit)
if __name__ == '__main__':
root = Tk()
root.geometry("450x300")
root.title("小云的笔记本")
app = Application(master=root)
root.mainloop() # 循环事件
结果展示:
这里只展示部分结果,具体的结果大家可以复制粘贴我的代码到自己的电脑上运行自己去摸索,或者是自己去完善。【这里小编提醒一下大家,代码复制运行不起来的原因有可能是环境的不匹配或者是库未导入】
python程序打包成exe可执行的文件:
我们可以使用pyinstall模块实现将python项目打包成exe文件,操作步骤如下:
1.安装pyinstall模块
在pycharm中操作:file→setting→Project:xxx→Project interpretor,再点击+即可
【但是小编是在终端通过pip下载安装的【小编用上述官方操作一直下载不了那个包】】
如上图,小编已经通过小编的方法安装好了
2.在pycharm的终端输入以下命令:
pyinstaller -F xxxx.py
如上图所示,小编已经生成好一个exe文件.
画图项目
开发一款简单画图软件,包含以下功能:
1.画笔
2.矩形/椭圆绘制
3.清屏
4.橡皮擦
5.直线/带箭头的执行
6.修改画笔的颜色
"""一款画图软件"""
from tkinter import *
from tkinter.colorchooser import askcolor # 导入颜色选择器
# 创建窗口
root = Tk()
root.geometry("700x500")
root.title("小云画图版")
# 创建画布
canvas = Canvas(root, bg="white", width=600, height=400)
canvas.pack(expand=True, fill="both") # 让画布自动填充窗口
# 默认参数
current_color = "black"
drawing_mode = "brush" # 记录当前模式,默认是画笔
start_x, start_y = None, None # 记录鼠标起点
rect_id = None # 记录当前绘制的矩形 ID
rect_id1 = None # 记录当前绘制的椭圆 ID
line_id = None # 记录当前绘制的直线 ID
line_id1 = None # 记录当前绘制的带箭头直线 ID
# 功能1:画笔颜色选择
def ChooseColor():
global current_color
color = askcolor(color=current_color, title="选一个你想要的画笔颜色")
if color[1]:
current_color = color[1]
# **功能1:画笔模式**
def Brush():
""" 进入画笔模式 """
global drawing_mode
drawing_mode = "Brush"
# 绑定画笔事件
canvas.bind("<Button-1>", start_paint)
canvas.bind("<B1-Motion>", paint)
def start_paint(event):
""" 记录鼠标起始位置 """
global last_x, last_y
last_x, last_y = event.x, event.y
def paint(event):
""" 画笔 or 橡皮擦 """
global last_x, last_y
if drawing_mode == "Brush":
canvas.create_line(last_x, last_y, event.x, event.y, fill=current_color, width=3)
elif drawing_mode == "Eraser":
canvas.create_line(last_x, last_y, event.x, event.y, fill="white", width=eraser_size) # 用白色覆盖
last_x, last_y = event.x, event.y # 更新起点
# **功能2:矩形模式**
def Rectangle():
""" 进入矩形模式 """
global drawing_mode
drawing_mode = "rectangle"
# 绑定矩形绘制事件
canvas.bind("<Button-1>", start_draw)
canvas.bind("<B1-Motion>", draw)
def start_draw(event):
""" 记录鼠标起始位置 """
global start_x, start_y, rect_id
start_x, start_y = event.x, event.y
rect_id = canvas.create_rectangle(start_x, start_y, start_x, start_y, outline=current_color, width=3)
def draw(event):
""" 动态调整矩形大小 """
global rect_id
canvas.coords(rect_id, start_x, start_y, event.x, event.y) # 更新矩形坐标
# 功能3:椭圆绘制
def Elliptic():
""" 进入椭圆模式 """
global drawing_mode
drawing_mode = "Elliptic"
# 绑定椭圆绘制事件
canvas.bind("<Button-1>", start_draw1)
canvas.bind("<B1-Motion>", draw1)
def start_draw1(event):
""" 记录鼠标起始位置 """
global start_x, start_y, rect_id1 # 这里声明 rect_id1 为全局变量
start_x, start_y = event.x, event.y
rect_id1 = canvas.create_oval(start_x, start_y, start_x, start_y, outline=current_color, width=3) # 创建椭圆
def draw1(event):
""" 动态调整椭圆大小 """
global rect_id1 # 这里确保访问的是全局变量
canvas.coords(rect_id1, start_x, start_y, event.x, event.y) # 更新椭圆坐标
# 功能4:清屏
def Clear():
canvas.delete("all") # 清除画布内容
# 功能5:橡皮擦
def Eraser():
""" 进入橡皮擦模式 """
global drawing_mode
drawing_mode = "Eraser"
# 绑定鼠标事件,使橡皮擦可以生效
canvas.bind("<Button-1>", start_paint)
canvas.bind("<B1-Motion>", paint) # 关键:绑定拖动事件
# 创建一个新弹窗
eraser_window = Toplevel(root)
eraser_window.title("调整橡皮擦大小")
eraser_window.geometry("300x150") # 设置窗口大小
# 创建滑块(初始值为20)
scale_value.set(20)
scale = Scale(eraser_window, from_=5, to=50, orient=HORIZONTAL, length=200, variable=scale_value,
label="橡皮擦大小")
scale.pack(pady=10)
# 确定按钮,点击后关闭窗口
def confirm_size():
global eraser_size
eraser_size = scale_value.get() # 更新橡皮擦大小
eraser_window.destroy() # 关闭窗口
Button(eraser_window, text="确定", command=confirm_size).pack(pady=10)
# 创建一个变量来存储滑块的值
scale_value = IntVar()
# 先创建滑块(但不立即显示)
scale = Scale(root, from_=5, to=50, orient=HORIZONTAL, length=200, variable=scale_value, label="橡皮擦大小")
scale_value.set(20) # 设置默认大小
# 标签显示滑块值
label = Label(root, text=f"当前大小: {scale_value.get()}")
# 功能6:绘制直线
def Straight():
""" 进入直线模式 """
global drawing_mode
drawing_mode = "Straight"
# 绑定直线绘制事件
canvas.bind("<Button-1>", start_draw3)
canvas.bind("<B1-Motion>", draw3)
def start_draw3(event):
""" 记录鼠标起始位置,并创建初始直线 """
global start_x, start_y, line_id # 需要声明全局变量
start_x, start_y = event.x, event.y
line_id = canvas.create_line(start_x, start_y, start_x, start_y, fill=current_color, width=3) # 先创建直线
def draw3(event):
""" 动态调整直线的终点 """
global line_id
canvas.coords(line_id, start_x, start_y, event.x, event.y) # 更新直线坐标
# 功能7:绘制带箭头的直线
def Arrow():
""" 进入箭头直线直线模式 """
global drawing_mode
drawing_mode = "Arrow"
# 绑定直线绘制事件
canvas.bind("<Button-1>", start_draw4)
canvas.bind("<B1-Motion>", draw4)
def start_draw4(event):
""" 记录鼠标起始位置,并创建初始直线 """
global start_x, start_y, line_id1 # 需要声明全局变量
start_x, start_y = event.x, event.y
line_id1 = canvas.create_line(start_x, start_y, start_x, start_y,
fill=current_color, width=3, arrow="last") # 先创建直线
def draw4(event):
""" 动态调整带箭头直线的终点 """
global line_id1
canvas.coords(line_id1, start_x, start_y, event.x, event.y) # 更新直线坐标
# 创建按钮栏
button_frame = Frame(root)
button_frame.pack(side="top", fill="x")
# 设定按钮字体
btn_font = ("Arial", 14, "bold") # 设置字体为 Arial,大小 14,加粗
# 按钮绑定对应事件
Button(button_frame, text="颜色", font=btn_font, command=ChooseColor).pack(side="left", padx=5, pady=5)
Button(button_frame, text="画笔", font=btn_font, command=Brush).pack(side="left", padx=5, pady=5)
Button(button_frame, text="矩形绘制", font=btn_font, command=Rectangle).pack(side="left", padx=5, pady=5)
Button(button_frame, text="椭圆绘制", font=btn_font, command=Elliptic).pack(side="left", padx=5, pady=5)
Button(button_frame, text="清屏", font=btn_font, command=Clear).pack(side="left", padx=5, pady=5)
Button(button_frame, text="橡皮擦", font=btn_font, command=Eraser).pack(side="left", padx=5, pady=5)
Button(button_frame, text="直线", font=btn_font, command=Straight).pack(side="left", padx=5, pady=5)
Button(button_frame, text="带箭头直线", font=btn_font, command=Arrow).pack(side="left", padx=5, pady=5)
root.mainloop()
对于上述项目,小编能力有限,只能把画图版做成这个简单的样子了。当然小编也是一边写,一边查资料,还要借助GPT解决问题,这么多组块,这么多方法,靠脑子是记不下来的,我们只需要知道有,需要的时候去查即可。
关于GUI小编就讲到这个了,谢谢大家!