1. Tkinter

tkinter (Tk interface)是python 的标准 GUI 库,支持跨平台的GUI 程序开发

Tkinter适合小型的GUI程序编写,也特别适合初学者学习GUI 编程。本书以 Tkinter为核心进行讲解。

2.wxpython

wxpython是比较流行的GUI 库,适合大型应用程序开发,功能强于 Tkinter

整体设计框架类似于MFC(Microsoft Foundation Classes 微软基础类库

3,PyQT是QT的工具包,QT是开源的GUI

基于 tkinter 模块创建 GUI 程序包含如下 4 个核心步骤:

1,创建应用程序主窗口对象(也称:根窗口)

(1)通过类 Tk 的无参构造函数

1
2
3
4
from tkinter import*


root = Tk()

2,在主窗口中,添加各种可视化组件,比如:按钮(Button),文本框(Label)等

1
2
3
btn01 = Button(root)

btn01["text"] ="点我就送花"

3,通过几何布局管理器,管理组件的大小和位置

1
btn01.pack()

4**,事件处理**

(1)通过绑定事件处理程序,响应用户操作所触发的事件(比如:单击,双击等)

1
2
3
4
5
6
def songhua(e):

messagebox.showinfo("Message","送你一朵玫瑰花")
print("送你99朵玫瑰花")

btn01.bind("<Button-1>",songhua)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tkinter import *
from tkinter import messagebox
root = Tk()

btn01 = Button(root)
btn01["text"] = "点我就送花"

btn01.pack()


def songhua(e): # e就是事件对象
messagebox.showinfo("Message", "送你一朵玫瑰花")
print("送你99朵玫瑰花")


btn01.bind("<Button-1>", songhua)

root.mainloop() #调用组件的mainloop()方法,进入事件循环

可变参数

指的是”可变数量的参数“,分两种情况

1,*param(一个星号),将多个参数收集到一个”元组“对象中

2,**param(两个星号),将多个参数收集到一个”字典“对象中

特殊方法和运算符重载

python 的 运算符实际上是通过调用对象的特殊方法实现的,比如:

1
2
3
4
5
6
a = 20
b = 30
c = a+b
d = a.__add__(b)
print("c=",c)
print("d=",d)

Entry 单行文本框

Entry 用来接收一行字符串的控件。如果用户输入的文字长度长于 Entry 控件的宽度时,文字会自动向后滚动。如果想输入多行文本,需要使用 Text 控件

Text 多行文本框

主要用于显示多行文本,还可以显示网页链接,图片,HTML 页面,甚至 CSS 样式表,添加组件等。因此,也常 被当做简单的文本处理器,文本编辑器或者网页浏览器来使用。比如 IDLE 就是 Text 组件构成的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from tkinter import *
import webbrowser

class Application(Frame):

def __init__(self, master=None):
super().__init__(master) #super()代表的是父类的定义,而不是父类对象
self.master = master
self.pack()
self.createWidget()


def createWidget(self):
self.w1 = Text(root, width=40, height=12, bg="gray")
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.addWidget).pack(side="left")
Button(self, text="通过tag精确控制文本", command=self.testTag).pack(side="left")

def insertText(self):
self.w1.insert(INSERT, 'Gaoqi')
self.w1.insert(END, '[sxt]')

def returnText(self):
print(self.w1.get(1.2, 1.6))
self.w1.insert(1.8, "gaoqi")
print("所有文本内容: \n"+self.w1.get(1.0, END))

def addWidget(self):
b1 = Button(self.w1, text='爱尚学堂')
self.w1.window_create(INSERT, window=b1)

def testTag(self):
self.w1.delete(1.0,END)
self.w1.insert(INSERT,"good good study,day day up!\n北京尚学堂\n百战程序员\n百度,搜一下就知道了")
self.w1.tag_add("good",1.0, 1.9)
self.w1.tag_config("good", background="yellow", foreground="red")

self.w1.tag_add("baidu", 4.0, 4.2)
self.w1.tag_config("baidu", underline=True)
self.w1.tag_bind("baidu", "<Button-1>", self.webshow)

def webshow(self,event):
webbrowser.open("http://www.baidu.com")

if __name__ == '__main__':
root = Tk()
root.geometry("450x300+200+300")
app = Application(master=root)
root.mainloop()

以上代码存在问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from tkinter import *
import webbrowser

class Application(Frame):

def __init__(self, master=None):
super().__init__(master) # super()代表的是父类的定义,而不是父类对象
self.master = master
self.pack()
self.createWidget()

def createWidget(self):
# 修改这里:将父容器从 root 改为 self
self.w1 = Text(self, width=40, height=12, bg="gray") # 改为 self
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.addWidget).pack(side="left")
Button(self, text="通过tag精确控制文本", command=self.testTag).pack(side="left")

def insertText(self):
self.w1.insert(INSERT, 'Gaoqi')
self.w1.insert(END, '[sxt]')

def returnText(self):
print(self.w1.get(1.2, 1.6))
self.w1.insert(1.8, "gaoqi")
print("所有文本内容: \n"+self.w1.get(1.0, END))

def addWidget(self):
b1 = Button(self.w1, 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百度,搜一下就知道了")

# 添加 good tag
self.w1.tag_add("good", 1.0, 1.9)
self.w1.tag_config("good", background="yellow", foreground="red")

# 添加 baidu tag - 这里索引要正确
self.w1.tag_add("baidu", 4.0, 4.2) # "百度"两个字
self.w1.tag_config("baidu", underline=True, foreground="blue")
self.w1.tag_bind("baidu", "<Button-1>", self.webshow)

def webshow(self, event):
webbrowser.open("http://www.baidu.com")

if __name__ == '__main__':
root = Tk()
root.geometry("450x300+200+300")
app = Application(master=root)
root.mainloop()

Radiobutton 单选按钮

Radiobutton 控件用于选择同一组单选按钮中的一个。

Radiobutton 可以显示文本,也可以显示图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
"""测试Radiobutton组件的基本用法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox

class Application(Frame):

def __init__(self, master=None):
super().__init__(master)

self.master = master
self.pack()
self.creareWidget()

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("400x50+200+300")
app = Application(master=root)
root.mainloop()

以上代码也存在一点问题,不能执行

父容器选择:

使用 self 作为父容器

因为 Application 是继承自 Frame 的,所有在类内部创建的组件应该放在这个 Frame 内部,而不是直接放在根窗口 root 上。

代码结构说明:

  • 使用 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">self</font> 作为父容器可以让所有组件都在 <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">Application</font> 的 Frame 内部,这样布局更清晰,也更符合面向对象的设计。
  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">self.v = StringVar()</font> 创建了一个 StringVar 变量,用于绑定到 RadioButton 组件。当选择不同的 RadioButton 时,<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">self.v</font> 的值会自动更新。
  • <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">self.v.set("F")</font> 设置了默认选中"女性"选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""测试Radiobutton组件的基本用法,使用面向对象的方式"""

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() # 这里拼写错误:creareWidget -> createWidget

def createWidget(self):
self.v = StringVar()
self.v.set("F")

# 修改这里:将父容器从 root 改为 self
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 可以显示文本,也可以显示图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""测试Radiobutton组件的基本用法,使用面向对象的方式"""

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.videHobby = IntVar()

print(self.codeHobby.get())
self.c1 = Checkbutton(self, text="敲代码",
variable=self.codeHobby, onvalue=1, offvalue=0)
self.c2 = Checkbutton(self, text="看视频",
variable=self.videHobby, 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.videHobby.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 是一个矩形区域,可以放置图形,图像,组件等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"""测试canvas组件的基本用法,使用面向对象的方式"""

from tkinter import *
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="lightgreen")
self.canvas.pack()

# 画线
line = self.canvas.create_line(10, 10, 30, 20, 40, 50)

# 画矩形
rect = self.canvas.create_rectangle(50, 50, 100, 100, fill="red")

# 画椭圆
oval = self.canvas.create_oval(150, 50, 200, 100, fill="blue")

# 尝试加载图片(如果存在)
try:
self.photo = PhotoImage(file="imgs/logo.gif")
self.canvas.create_image(150, 170, image=self.photo)
except Exception as e:
print(f"无法加载图片: {e}")
# 如果没有图片,画一个替代图形
self.canvas.create_rectangle(130, 150, 170, 190, fill="yellow")
self.canvas.create_text(150, 170, text="图片")

# 按钮
Button(self, text="画10个矩形", command=self.draw50Recg).pack(side="left")

def draw50Recg(self):
colors = ["red", "blue", "green", "yellow", "orange", "purple", "pink"]
for i in range(0, 10):
x1 = random.randrange(int(self.canvas["width"]))
y1 = random.randrange(int(self.canvas["height"]))
x2 = x1 + random.randrange(20, 50)
y2 = y1 + random.randrange(20, 50)
color = random.choice(colors)
self.canvas.create_rectangle(x1, y1, x2, y2,
fill=color,
outline="black",
width=2)


if __name__ == '__main__':
root = Tk()
root.title("Canvas测试")
root.geometry("400x300+200+300") # 修正窗口大小
app = Application(master=root)
root.mainloop()

布局管理器

一个 GUI 应用程序必然有大量的组件,需要使用 tkinter 提供的布局管理器帮助我们组织,管理在父组件中子组件的布局方式,tkinter 提供了三种管理器:pack,grid,place

grid 布局 管理器

grid 表格布局,采用表格结构组织组件,子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"""计算器软件界面的设计"""

from tkinter import *
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):
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 是代码量最少,最简单的一种,可以用于快速生成界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#coding=utf-8
#测试pack布局管理

from tkinter import *

root = Tk()
root.geometry("700x200")

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 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding=utf-8
from tkinter import *

root = Tk()
root.geometry("500x300")

f1 = Frame(root,width=200,height=200,bg="green")
f1.place(x=30,y=30)

Button(root,text="尚学堂").place(relx=0.2, x=100,y=20,relheight=0.5,relwidth=0.2)
Button(f1,text="百战程序员").place(relx=0.6,rely=0.7)
Button(f1,text="高琦老师").place(relx=0.5,rely=0.2)
root.mainloop()

lambda 表达式–事件传参应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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()