python——GUI图形用户界面编程
GUI简介
我们前面实现的都是基于控制台的程序,程序和用户的交互通过控制台来完成
本章,我们来学习GUI图形用户界面编程,我们可以通过python提供的丰富的组件,快速的视线使用图形界面和用户交互
GUI变成类似于“搭积木”,将一个个组件(widget)放到窗口中。windows中的画图软件,就是一个典型的GUI程序:
上面的各种按钮、菜单、编辑区域等都是一个个组件,它们都防止到窗口中,并通过增加“对时间的处理”称为一个完整的程序。
常用的GUI库
- Tkinter
tkinter是Python的标准GUI库,支持跨平台的GUI程序开发,主要一tkinter为核心进行讲解 - wxPython
wxPyhton是比较流行的GUI库,适合大型应用程序开发,功能强于tkinter,整体设计框架类似于MFC - PyQT
Qt是一种开源的GUI库,适合大型GUI程序开发,PyQT是Qt工具包标准的Python实现。我们也可以使用Qt Desginer界面设计器快速开发GUI应用程序
tkinter模块
本章设计大量的API讲解。学习API最好的来源就是官方提供的文档:tkinter官方网站:
Graphical User Interfaces with Tk — Python 3.7.17 documentation或者:https://effbot.org/tkinternook/
基于tkinter模块创建GUI程序包含如下4个核心步骤:
1.创建应用程序主窗口对象
(1)通过类Tk的无参数构造函数
from tkinter import *
root=Tk()
2.在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等
btn01=Button(root)
btn01["text"]="点我就送花"
3.通过几何布局管理器,管理组件的大小和位置
btn01.pack()
4.事件处理
(1)通过绑定事件处理程序,响应用户操作所触发的时间(比如:单击、双击等)
def songhua(e):
messagebox.showinfo("Message", "送你一朵玫瑰花,请你爱上我")
print("送你99多玫瑰花")
btn01.bind("<Button-1>", songhua())
总代码
from tkinter import *
from tkinter import messagebox
root = Tk()
btn01 = Button(root)
btn01["text"] = "点我就送花"
btn01.pack()
def songhua(e): # e就是事件对象
messagebox.showinfo("Message", "送你一朵花")
print("送你999多玫瑰花")
btn01.bind("<Button-1>", songhua)
root.mainloop() # 调用组件的mainloop()方法,进入时间循环
主窗口大小和位置
通过geometry('w x h+-x+-y')进行设置。w为宽度,h为高度。+x表示距屏幕左边的距离;-x表示距屏幕右边的距离;+y表示距屏幕上边的距离;-y表示距屏幕下边的距离。
【测试】测试tkinter主窗口位置和大小的设置
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
root = Tk()
root.title("这是我的第一个GUI程序")
root.geometry("500x300+100+200")
btn01 = Button(root)
btn01["text"] = "点我就送花"
btn01.pack()
def songhua(e): # e就是事件对象
messagebox.showinfo("Message", "送你一朵花")
print("送你999多玫瑰花")
btn01.bind("<Button-1>", songhua)
root.mainloop() # 调用组件的mainloop()方法,进入时间循环
GUI编程整体描述
图形用户界面是由一个个组件组成,就像小孩“搭积木”一样最终组成整个界面。有的组件还能再里面再放置其他组件,我们称为“容器”。Tkinter的GUI组件关系图如下:
GUI应用程序类的经典写法
本节程序也是GUI应用程序编写的恶一个主要结构,采用了面向对象的方式,更加合理的组织代码。
通过类Application组织整个GUI程序,类Application继承了Frame及通过继承拥有了父类的特性。通过构造函数__init__()初始化窗口中的对象,通过createWidfets()方法创建窗口中的对象。
Frame框架式一个tkinter组件,表示一个矩形的区域。Frame一般作为容器使用,可以放置其他组件,从而实现复杂的布局。
# -*- coding: utf-8 -*-
"""测试一个经典的GUI程序写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""
一个经典的GUI程序的类的写法
"""
def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master # 传入root作为继承
self.pack() # 是用来将一个组件(如按钮、标签、框架等)添加到其父容器中的一种布局管理方法。
self.createWiget() # 调用创建组件方法
def createWiget(self):
"""创建组件"""
self.btn01 = Button(self.master)
self.btn01["text"] = "点击送花"
self.btn01.pack()
self.btn01["command"] = self.songhua
# 创建一个退出按钮
self.btnQuit = Button(self.master, text="退出", command=root.destroy)
self.btnQuit.pack()
def songhua(self):
messagebox.showinfo("送花", "送你99多玫瑰花")
if __name__ == '__main__':
"""Application 类实例化时,将 master 参数设为 root,这意味着 Application 的所有组件(在 Frame 中创建)都将在 root 主窗口中显示。"""
root = Tk() # 创建一个Tk对象
root.geometry("400x100+200+300") # 设置Tk的窗口大小和范围
root.title("一个经典的GUI程序类的测试") # 设置Tk的的名字
app = Application(master=root) # 实例化一个Application对象
root.mainloop() # 它的作用是启动 Tkinter 事件循环,让你的应用程序开始运行并等待用户的操作
-
root
:- 代表了 Tkinter 主窗口的实例,是
Tk
类的对象。 - 这是 GUI 应用程序的顶层窗口,对整个应用进行管理。
- 代表了 Tkinter 主窗口的实例,是
-
Frame
:- 是一个容器控件,用于在应用程序窗口中组织其他控件(比如按钮、标签等)。
- 在这里,
Application
类是Frame
的子类,它通过继承Frame
来创建一个新的组件。
1. Button(self.master)
如果你的 master
是一个 Tk 实例(也就是主窗口),那么使用 Button(self.master)
是合适的。这种情况下,按钮会被添加到主窗口中。
2. Button(self)
如果你在一个自定义类中,且希望按钮添加到该类的实例(比如 Frame
或者 Toplevel
),则使用 Button(self)
是合适的。按钮将被添加到这个特定的实例中。
简单组件
Label标签
Label(标签)主要用于显示文本信息,也可以显示图像。
Label(标签)有一些常见的属性:
- width,height:
用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字占2个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是根据具体显示的内容动态调用。 - font
指定字体和字体大小 - image
显示在Label上的图像,目前tkinter只支持gif格式 - fg和bg
fg(foreground):前景色、bg(background):背景色 - justify
针对多行文字的对齐,可设置justify属性,可选值"left"、"center" or "right
# -*- coding: utf-8 -*-
"""测试一个经典的GUI程序写法,使用面向对象的方式"""
from tkinter import *
class Application(Frame):
"""
测试labels标签
"""
def __init__(self, master=None):
super().__init__(master)
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.pack()
self.label02 = Label(self, text="老杨", width=10, height=2, bg="blue", fg="white", font=("黑体", 30))
self.label02.pack()
# 显示图像
# global photo # 把photo声明成全局变量,如果是局部变量,不然本方法执行后,图像对象销毁
self.photo = PhotoImage(file="img/3.gif")
self.label03 = Label(self, image=self.photo)
self.label03.pack()
self.label04 = Label(self, text="你好!\n我是一名初学者\n正在学习python编程",
borderwidth=1, relief="solid", justify="right")
self.label04.pack()
if __name__ == '__main__':
root = Tk()
root.geometry("600x500+200+200")
app = Application(master=root)
root.mainloop()
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可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。
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.butn1 = Button(self.master, text="begin",
width=6, height=3, anchor=E, command=self.songhua) # anchor的作用就是决定你的text文本唯一按钮的那个位置
self.butn1.pack()
# 插入图片
self.photo = PhotoImage(file="img/1.gif")
self.butn2 = Button(self.master, image=self.photo, command=self.login)
self.butn2.pack()
self.butn2.config(state=DISABLED) # 加入state会让你不能点击,就让你取消点击状态
def songhua(self):
"""点击开始送花"""
messagebox.showinfo("恭喜你", "得到两朵玫瑰花")
def login(self):
messagebox.showinfo("系统", "登录成功!欢迎开始学习!")
if __name__ == '__main__':
root = Tk()
root.title("button练习")
root.geometry("400x300+200+100")
app = Application(root)
root.mainloop()
Entry单行文本框
Entry用来接收一行字符串的控件。如果用户输入的文字长度长于Entry控件的宽度时,文字会自动向后滚动。如果想输入多行文本,需要使用Text控件。
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.pack()
self.createWidgt()
def createWidgt(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()
# 登录按钮
self.btn01 = Button(self, text="登录", command=self.login)
self.btn01.pack()
def login(self):
username = self.entry01.get()
pwd = self.entry02.get()
print("用户名:" + username)
print("密 码 :" + pwd)
messagebox.showinfo("老杨系统", "恭喜登录成功")
if __name__ == '__main__':
root = Tk()
root.geometry("400x300+200+100")
root.title("entry文本")
app = Application(root)
root.mainloop()
Text多行文本框
Text(多行文本框)的主要用于显示多行文本,还可以显示网页链接,图片,HTML页面,甚至CSS样式表,添加组件等。因此,也常被当做简单的文本处理器、文本编辑器或者网页浏览器来使用。比如IDLE就是Text组件构成的。
# -*- coding: utf-8 -*-
import webbrowser
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.pack()
self.createWidget()
def createWidget(self):
"""创建框架"""
self.w1 = Text(self.master, width=40, height=12, bg="gray") # 注意这里的self.master可以是root,但是不能是self
# 宽度20个字母(10个汉字),高度一个行高
self.w1.pack()
self.w1.insert(1.0, "0123456789\nabcdefg") # 这里的1.0的意思是第一行第0列
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, "老杨")
# END索引号表示在最后插入
self.w1.insert(END, "你好,我是你大哥")
def returnText(self):
# indexes(索引)是用来纸箱Text组件中文本的位置,
# Text的组件索引也是对应实际字符之间的位置
# 核心:行号以1开始,列好以0开始
print(self.w1.get(1.2, 1.6))
self.w1.insert(1.8, "老杨")
print("所有文本内容为:\n" + self.w1.get(1.0, END))
def addImage(self):
"""在光标位置插入图片"""
global photo
photo = PhotoImage(file="img/3.gif")
self.w1.image_create(END, image=photo) # 在文章的末尾插入
self.w1.image_create(INSERT, image=photo) # 在鼠标的光标出插入图片
def addWidget(self):
"""增加新的控件方法"""
b1 = Button(self.w1, text="爱你老杨", command=self.songhua)
# 在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("good", 1.0, 1.9) # 第一个参数为标记名称,第二个参数和第三个参数是索引的位置
self.w1.tag_config("good", background="yellow", foreground="red") # 第一个参数是需要添加配置的标记,第二和第三个参数的含义是添加前景色和背景色
self.w1.tag_add("baidu", 5.0, 5.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")
def songhua(self):
"""送花"""
messagebox.showinfo("老杨系统", "送你9999朵玫瑰花")
if __name__ == '__main__':
root = Tk()
root.geometry("400x300+200+100")
root.title("老杨系统")
app = Application(root)
root.mainloop()
Radiobutton单选按钮
Radiobutton控件用于选择同一组单选按钮中的一个
Radiobutton可以显示文本,也可以显示图像
# -*- coding: utf-8 -*-
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.v = StringVar()
self.v.set("女")
self.election1 = Radiobutton(self.master, text="男", value="男", variable=self.v)
self.election2 = Radiobutton(self.master, text="女", value="女", variable=self.v)
self.election1.pack(side="left")
self.election2.pack(side="left")
Button(self.master, text="确定", command=self.confirm).pack(side="left")
def confirm(self):
messagebox.showinfo("测试", "选择的性别:" + self.v.get())
if __name__ == '__main__':
root = Tk()
root.title("老杨的系统")
root.geometry("400x300+200+100")
app = Application(root)
root.mainloop()
Checkbutton复选按钮
Checkbutton控件用于选择多个按钮的情况。Checkbutton可以显示文本,也可以显示图像
# -*- coding: utf-8 -*-
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.title("老杨的系统")
root.geometry("400x300+200+100")
app = Application(root)
root.mainloop()
canvas画布
canvas(画布)是一个矩形区域,可以放置图形、图像、组件等。本节我们简单介绍canvas的使用。
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
import random
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.createWidget()
def createWidget(self):
"""创建组件"""
self.canvas = Canvas(self, width=400, height=500, 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="img/3.gif")
self.canvas.create_image(200, 300, 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) # 生成一个从0到这个数中的其中一个数
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.title("老杨的系统")
root.geometry("400x600+200+100")
app = Application(root)
root.mainloop()
布局管理器
一个GUI应用程序必然有大量的组件,这些组件如何排布?z这时候,就需要使用tkinter提供的布局管理器帮租我们组织、管理在父组件中子组件的布局方式,,tkinter提供了三种管理器:pack、grid、place。
grid布局管理器
grid表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。
grid()方法提供的选项
选项 | 说明 | 取值范围 |
---|---|---|
column | 单元格的列号 | 从0开始的正整数 |
columnspan | 跨列,跨越的列数 | 正整数 |
row | 单元格的行号 | 从0开始的正整数 |
rowspan | kuahang,跨越的行数 | 正整数 |
ipadx,ipady | 设置子组件之间的间隔,x方向或者y方向,默认单位为像素 | 非负浮点数,默认为0.0 |
padx,pady | 与之并列的组件之间的间隔,x方向或者y方向,默认单位是像素 | 非负浮点数,默认0.0 |
sticky | 组件紧贴所在单元格的某一角,对应于东南西北中以及4个角 | “n”,“s”,“w”,“e”,“nw”,“sw”,“se”,“ne”,“center(默认)” |
# -*- coding: utf-8 -*-
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.grid(row=0, column=0)
v1 = StringVar()
self.entry01 = Entry(self, textvariable=v1)
self.entry01.grid(row=0, column=1)
v1.set("12346578")
print(self.entry01.get())
self.label02 = Label(self, text="用户名为手机号")
self.label02.grid(row=0, column=2)
# 密码
self.label03 = Label(self, text="密码")
self.label03.grid(row=1, column=0)
v2 = StringVar()
self.entry02 = Entry(self, textvariable=v2, show="*")
self.entry02.grid(row=1, column=1)
# 登录
Button(self, text="登录", command=lambda: self.login(self.entry01.get(), self.entry02.get())).grid(row=2,
column=1,
sticky="EW")
Button(self, text="取消", command=self.quxiao).grid(row=2, column=2, sticky="EW")
def login(self, admin, key):
"""
用于登录
:param admin:用户名
:param key: 密码
:return:
"""
if admin == "17729686779" and key == "123456":
messagebox.showinfo("老杨的系统", "密码正确")
else:
messagebox.showinfo("老杨的系统", "密码错误")
def quxiao(self):
"""取消登录"""
self.entry01.delete(0, END)
self.entry01.insert(0, "123456")
self.entry02.delete(0, END)
if __name__ == '__main__':
root = Tk()
root.title("老杨的系统")
root.geometry("400x300+200+100")
app = Application(root)
root.mainloop()
【示例】通过grid布局-实现计算器软件界面
根据实际建议计算器的按键分布,设计一个相仿的计算器界面,
如上界面,实际可以设计成一个7行4列的表格布局,然后将相应的按钮放置进去即可
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
class Application(Frame): # 制作一个简易的计算器
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.createWidget()
def createWidget(self):
"""通过grid布局实现计算器的界面"""
# 按钮
binText = (("MC", "M+", "M-", "MR"),
("C", "±", "÷", "x"),
(7, 8, 9, "-"),
(4, 5, 6, "+"),
(1, 2, 3, "="),
(0, "."))
# 输入框
self.v1 = StringVar()
self.entry01 = Entry(self, textvariable=self.v1)
self.entry01.grid(row=0, column=0, columnspan=4, pady=10) # pady=10 意味着在 entry01 的顶部和底部各添加 10 像素的空间。
for rindex, rText in enumerate(binText):
for cindex, cvalue in enumerate(rText):
if cvalue == "=":
Button(self, text=cvalue, width=2, command=self.mainCaculate()).grid(row=rindex + 1, column=cindex,
rowspan=2, sticky=NSEW)
elif cvalue == 0:
Button(self, text=cvalue, width=2).grid(row=rindex + 1, column=cindex, columnspan=2, sticky=NSEW)
elif cvalue == ".":
Button(self, text=cvalue, width=2).grid(row=rindex + 1, column=cindex + 1, sticky=NSEW)
else:
Button(self, text=cvalue, width=4).grid(row=rindex + 1, column=cindex, sticky=NSEW)
# sticky=NSEW,意味着按钮会填充整个单元格
def mainCaculate(self):
pass
if __name__ == '__main__':
root = Tk()
root.title("简易计算器")
root.geometry("250x250+200+100")
app = Application(root)
root.mainloop()
pack布局管理器
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布局用法,制作钢琴按键布局
# -*- coding: utf-8 -*-
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, 20):
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布局管理器可以通过坐标精准控制组件的位置,使用于一些布局更加灵活的场景
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”(默认) |
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
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=20)
Button(root, text="老杨").place(relx=0.5, x=100, y=20, relwidth=0.2, relheight=0.5)
# 如果relx和x同时存在的话,就是先定位relx的位置,然后再定位x的位置,就是定位了relx的位置后,向两边移x
Button(f1, text="老杨爱睡觉").place(relx=0.6, rely=0.7)
Button(f1, text="老杨大哥").place(relx=0.5, rely=0.2)
root.mainloop()
【示例】place布局管理-扑克牌游戏demo
!!!扑克图片需要自己在网上去找,然后做成gif图片的形式
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
class Application(Frame):
"""通过place布局管理器实现扑克牌位置控制"""
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.yabs = 100
self.createWidget()
def createWidget(self):
"""创建控制组件"""
# self.photo = PhotoImage(file="img/puke/1.gif")
# self.puke1 = Label(self.master, image=self.photo)
# self.puke1.place(x=10, y=50)
self.photos = [PhotoImage(file="img/puke/" + str(i + 1) + ".gif") for i in range(6)]
self.pukes = [Label(self.master, image=self.photos[i]) for i in range(6)]
for i in range(6):
self.pukes[i].place(x=10 + i * 120, y=self.yabs)
# 为所有的Label增加事件处理
for i in range(6):
self.pukes[i].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() == self.yabs:
event.widget.place(y=self.yabs - 50)
else:
event.widget.place(y=self.yabs)
if __name__ == '__main__':
root = Tk()
root.geometry("800x400+300+200")
root.title("扑克牌玩具")
app = Application(root)
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
print(add3args(10, 20, 30))
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属性绑定时传参
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("laoyang", "xixi")).pack(side="left")
root.mainloop()
多种事件绑定方式汇总
组件对象的绑定
- 通过command属性绑定(适合简单不需要获取event对象)
Button(root,text=“登录”,command=login) - 通过bind()方法绑定(适合需要获取event对象)
c1=Canvas();c1.bind("<Button-1>",drawLine)
组件类的绑定
调用对象的bind_class函数,将该组件类所有的组件绑定事件:
w.bind_class("Widget","event",eventhanler)
# -*- 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={},b={}".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("laoyang", "xixi"))
b2.pack(side="left")
# 给所有Button按钮都绑定右键单击事件<Button-2>
b1.bind_class("Button", "<Button-3>", mouseTest3) # Button-3是单击右键的意思
root.mainloop()
其他组件
OptionMenu选择项
OptionMenu(选择项)用来做多选一,选中的项在顶部显示。
【示例】OptioMenu(选择项)的基本用法
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.createWidget()
def createWidget(self):
"""创建组件"""
Label(self, text="最爱的人是谁:").grid(row=0, column=0)
v = StringVar(self)
v.set("彭于晏")
om = OptionMenu(self, v, "彭于晏", "刘亦菲", "yyx")
om["width"] = 10
om.grid(row=0, column=1)
Btn1 = Button(self, text="确定", command=lambda: self.test1(v))
Btn1.grid(row=1, column=1)
def test1(self, v):
print("最爱的人:", v.get())
if __name__ == '__main__':
root = Tk()
root.geometry("300x100")
root.title("最爱的明星")
app = Application(root)
root.mainloop()
Scale滑块
Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值
【示例】使用Scale(移动滑块)控制字体大小变化
# -*- coding: utf-8 -*-
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()
self.a
def createWidget(self):
"""创建框架"""
s1 = Scale(self, from_=10, to=50, length=200, tickinterval=5, orient=HORIZONTAL,
command=self.test1) # 默认是竖直的,HORIZONTAL是水平的,这里调用test的时候,会默认将value的值传入函数中
s1.pack()
self.a = Label(self, text="yyx", width=10, height=1, bg="white", fg="red")
self.a.pack()
def test1(self, value):
print("滑块的值:", value)
newFont = ("宋体", value)
self.a.config(font=newFont)
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的系统")
app = Application(root)
root.mainloop()
颜色选择框
颜色选择框可以帮组我们设置背景色、前景色、画笔颜色、字体颜色等等
【示例】颜色选择框的用法
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
from tkinter import colorchooser
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.createWidget()
def createWidget(self):
"""创建组件"""
Button(self, text="选择背景色", command=self.test1).pack()
def test1(self):
s1 = colorchooser.askcolor(color="white", title="选择背景色")
print(s1)
self.master.config(bg=s1[1])
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的系统")
app = Application(root)
root.mainloop()
文件对话框
文件对话框帮助我们实现可视化的操作目录、操作文件。最后,将文件、目录的信息传入到程序中。文件对话框包含如下一些常用函数:
函数名 | 对话框 | 说明 |
askopenfilename(**options) | 文件对话框 | 返回打开的文件名 |
askopenfilenames(**options) | 返回打开的多个文件名列表 | |
askopenfile(**options) | 返回打开文件对象 | |
askopenfiles(**options) | 返回打开的文件对象的列表 | |
askdirectory(**options) | 目录对话框 | 返回目录名 |
asksavefile(**options) | 保存对话框 | 返回保存的文件对象 |
asksaveasfilename(**options) | 返回保存的文件名 |
命名参数options的常见值如下:
参数名 | 说明 |
defaultextension | 默认后缀:.xxx 用户没有输入则自动添加 |
filetypes=[(label1,pattern1),(label2,pattern2)] | 文件显示过滤器 |
initialldir | 初始目录 |
initialfile | 初始文件 |
parent | 父窗口,默认根窗口 |
title | 窗口标题 |
【示例】文件对话框基本用法
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.createWidget()
def createWidget(self):
"""用于创建组件"""
Button(self, text="选择编辑的视频文件", command=self.test1).pack()
self.show = Label(self, width=40, height=3, bg="green")
self.show.pack()
def test1(self):
f = filedialog.askopenfilename(title="上传文件", initialdir="d:", filetypes=[("视频文件", "mp4")])
self.show["text"] = f
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的程序")
app = Application(root)
root.mainloop()
简单输入对话框
simpledialog(简单对话框)包含如下常用函数:
函数名 | 说明 |
askfloat(title,prompt,**kw) | 输入并返回浮点数 |
askinteger(title,prompt,**kw) | 输入并返回整数 |
askstring(title,prompt,**kw) | 输入并返回字符串 |
参数中,title表示窗口标题;prompt是提示信息;命令参数kw为各种选项:initialvalue(初始值)、minvalue(最小值)、maxvalue(最大值)
【示例】简单对话框基本用法
# -*- coding: utf-8 -*-
from tkinter.simpledialog import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.createWidget()
def createWidget(self):
"""用于创建组件"""
Button(self, text="老杨你多大了?请输入", command=self.test1).pack()
self.show = Label(self, width=40, height=3, bg="green")
self.show.pack()
def test1(self):
a = askinteger(title="输入年龄", prompt="请输入年龄", initialvalue=18, minvalue=1, maxvalue=150)
self.show["text"] = a
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的系统")
app = Application(root)
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) | 警告消息框 |
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter.messagebox import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.createWidget()
def createWidget(self):
"""创建组件"""
# a1 = showinfo(title="yyx系统", message="yyx大牛")
# a2 = askokcancel(title="yyx系统", message="你是不是gay")
# a3 = askquestion(title="yyx系统", message="你是不是傻逼")
# a4 = showerror(title="yyx系统", message="报错了")
# a5 = showwarning(title="yyx系统", message="无法进行这样的操作")
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的系统")
app = Application(root)
root.mainloop()
菜单
GUI程序通常都有菜单,方便用户的交互。我们一般将菜单分为两种:
主菜单
主菜单通常位于GUI程序上方
上下文菜单
快捷菜单(上下文菜单)是通过鼠标右键单击组件而弹出的菜单,一般是和这个组件相关的操作,比如:剪切、复制、粘贴、属性等。
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
self.textpad = None
self.createWidget()
def createWidget(self):
"""创建组件"""
# 创建主菜单栏
menubar = Menu(self)
# 创建子菜单
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)
# 将主菜单栏加到根窗口
self.master["menu"] = menubar
# 文本编辑区
self.textpad = Text(self, width=50, height=30)
self.textpad.pack()
# 创建上下菜单
self.contentMenu = Menu(self.master)
self.contentMenu.add_command(label="背景颜色", command=self.test)
# 为右键绑定事件
self.master.bind("<Button-3>", self.createContextMenu)
def test(self):
"""用于测试"""
pass
def createContextMenu(self, event):
"""菜单在鼠标右键单击的坐标处显示"""
self.contentMenu.post(event.x_root, event.y_root)
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的记事本系统")
app = Application(root)
root.mainloop()
记事本项目
【示例】创建一个记事本项目
# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox
from tkinter.filedialog import *
from tkinter.colorchooser import *
class Application(Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.master = master
# 对象参数
self.textpad = None # 初始化文本编辑区对象
self.filename = None
self.createWidget()
def createWidget(self):
"""用于创建模块"""
# 创建主菜单栏
menubar = Menu(root) # 创建一个主菜单对象
# 创建子菜单
menuFile = Menu(menubar) # 将menuFile对象放在Menu中
menuEdit = Menu(menubar)
menuHelp = Menu(menubar)
# 将子菜单加入到主菜单栏中
menubar.add_cascade(label="文件(F)", menu=menuFile) # 文件放到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.test)
# 将主菜单栏加到根窗口
self.master["menu"] = menubar
# 文本编辑区
self.textpad = Text(root)
self.textpad.pack()
# 创建上下菜单
self.contentMenu = Menu(root)
self.contentMenu.add_command(label="背景颜色", command=self.openAskColor)
# 为右键绑定事件
self.master.bind("<Button-3>", self.createContextMenu)
# 绑定窗口变化事件
root.bind('<Configure>', self.on_resize)
def test(self):
"""用于测试专用"""
pass
def createContextMenu(self, event):
"""菜单在鼠标右键单击的坐标处显示"""
self.contentMenu.post(event.x_root, event.y_root)
def openFile(self):
if self.textpad.get("1.0", "end") != "\n":
if messagebox.askquestion("yyx的记事本", "请问是否要保存当前文本数据") == "yes":
self.saveFile()
else:
self.textpad.delete("1.0", "end")
try:
with askopenfile(title="打开文本文件") as f:
self.textpad.insert(INSERT, f.read())
self.filename = f.name
# print(self.filename)
except:
messagebox.showinfo("yyx的记事本", "未打开文件,请重试")
def saveFile(self):
if self.filename == None:
# 说明未选择要存取的的文件
messagebox.showinfo("yyx的记事本", "请选择你要存储的文件")
try:
with askopenfile(title="打开你要存储的文本文件") as f:
self.filename = f.name
except:
messagebox.showinfo("yyx的记事本", "未保存成功,请重试")
else:
# 如果当前文件板中存在数据,但是想存入新的文件
if messagebox.askquestion("yyx的记事本", "请问要把当前的文本数据存入其他文件中吗") == "yes":
try:
with askopenfile(title="打开你要存储的文本文件") as f:
self.filename = f.name
self.filename = None
except:
messagebox.showinfo("yyx的记事本", "未保存成功,请重试")
else:
with open(self.filename, "w") as f:
c = self.textpad.get(1.0, END)
f.write(c)
self.filename = None # 再进行写入过后要将filename令为None值
def newFile(self):
"""创建一个新的文件"""
self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt", filetype=[("文本文档", "*.txt")],
defaultextension=".txt")
self.saveFile()
def openAskColor(self):
"""打开背景颜色框"""
s1 = askcolor(color="red", title="选择背景色")
self.textpad.config(bg=s1[1])
def on_resize(self, event):
"""textpad的窗口跟随root的窗口变化"""
self.textpad.config(width=event.width, height=event.height)
if __name__ == '__main__':
root = Tk()
root.geometry("400x300")
root.title("yyx的记事本")
app = Application(root)
root.mainloop()
python项目打包成exe可执行文件
在pycharm的Terminal终端输入如下命令:
pyinstaller -F xxxx.py
【注】相关参数如下:
--icon=图标路径(pyinstaller -F --icon=my.ico XXXX.py)
-F 打包成一个exe文件
-W 使用窗口,无控制台
-c 使用控制台,无窗口
-D 创建一个目录,里面包含exe以及其他一些依赖性文件
在打包的过程中遇到一些问题:
1.如果提示AttributeError: 'str' object has no attribute 'decode'. Did you mean: 'encode'?
可以尝试将pyinstaller更新一下
pip install --upgrade pyinstaller
2.如果提示Please remove this package (located in D:\python3.10\lib\site-packages ) using "D:\python3.10\python.exe" -m pip uninstall pathlib then try again.
就将这个pathlib库删除即可
画图软件开发
开发一款简单的画图软件,包含如下功能:
- 画笔
- 矩形/椭圆绘制
- 清屏
- 橡皮擦
- 直线/带箭头的执行
- 修改画笔颜色、背景颜色
# -*- coding: utf-8 -*-
import tkinter
from tkinter import *
from tkinter import messagebox
class Application(Frame):
def __init__(self, master=None, win_width=900, win_height=450):
super().__init__(master)
self.pack()
self.master = master
self.x = 0
self.y = 0
self.bgcolor = "#000000"
self.fgcolor = "#ff0000"
self.lastDraw = 0 # 表示最后绘制的图形
self.startDrawFlag = False
# 创建元素参数区域
self.canvasPad = None
self.win_width = win_width
self.win_height = win_height
self.createWidget()
def createWidget(self):
"""创建控件"""
# 创建绘图区
self.canvasPad = Canvas(self.master, width=self.win_width, height=self.win_height * 0.9, bg=self.bgcolor)
self.canvasPad.pack(fill=tkinter.BOTH, expand=True)
# 创建功能按钮
btn_start = Button(self.master, text="开始", name="start")
btn_start.pack(side="left", padx="10")
btn_pen = Button(self.master, text="画笔", name="pen")
btn_pen.pack(side="left", padx="10")
btn_rect = Button(self.master, text="矩形", name="rect")
btn_rect.pack(side="left", padx="10")
btn_clear = Button(self.master, text="清屏", name="clear")
btn_clear.pack(side="left", padx="10")
btn_eraser = Button(self.master, text="橡皮擦", name="eraser")
btn_eraser.pack(side="left", padx="10")
btn_line = Button(self.master, text="直线", name="line")
btn_line.pack(side="left", padx="10")
btn_lineArrow = Button(self.master, text="箭头直线", name="lineArrow")
btn_lineArrow.pack(side="left", padx="10")
btn_color = Button(self.master, text="颜色", name="color")
btn_color.pack(side="left", padx="10")
# 时间处理
self.canvasPad.bind_class("Button", "<1>", self.eventManger)
self.canvasPad.bind("<ButtonRelease-1>", self.stopDraw)
"""绑定快捷键区域"""
# 绑定窗口变化
self.master.bind("<Configure>", self.resize_canvas)
def test(self):
"""用于测试专用"""
pass
def eventManger(self, event):
"""整个事件管理"""
name = event.widget.winfo_name()
print(name)
if name == "line":
self.canvasPad.bind("<B1-Motion>", self.myline)
elif name == "lineArrow":
self.canvasPad.bind("<B1-Motion>", self.mylineArrow)
elif name == "rect":
self.canvasPad.bind("<B1-Motion>", self.myRect)
elif name == "pen":
self.canvasPad.bind("<B1-Motion>", self.myPen)
elif name == "eraser":
self.canvasPad.bind("<B1-Motion>", self.myEraser)
def startDraw(self, event):
self.canvasPad.delete(self.lastDraw)
if not self.startDrawFlag:
self.startDrawFlag = True
self.x = event.x
self.y = event.y
def stopDraw(self, evnet):
self.startDrawFlag = False
self.lastDraw = 0
def myline(self, event):
"""创建直线"""
self.startDraw(event)
self.lastDraw = self.canvasPad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)
def mylineArrow(self, event):
"""绘制箭头直线"""
self.startDraw(event)
self.lastDraw = self.canvasPad.create_line(self.x, self.y, event.x, event.y, arrow=LAST, fill=self.fgcolor)
def myRect(self, event):
self.startDraw(event)
self.lastDraw = self.canvasPad.create_rectangle(self.x, self.y, event.x, event.y, outline=self.fgcolor)
def myPen(self, event):
self.startDraw(event)
self.canvasPad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)
self.x = event.x
self.y = event.y
def myEraser(self, event):
self.startDraw(event)
self.canvasPad.create_rectangle(event.x - 6, event.y - 6, event.x + 6, event.y + 6,
fill=self.bgcolor)
self.x = event.x
self.y = event.y
def resize_canvas(self, event):
"""canvas的窗口跟随root的窗口变化"""
self.canvasPad.config(width=event.width, height=event.height * 0.9)
if __name__ == '__main__':
win_width = 900
win_height = 450
root = Tk()
root.geometry("600x500")
root.title("yyx的画图软件")
app = Application(root, win_width=win_width, win_height=win_height)
root.mainloop()