今天我们将深入学习GUI图形界面编程tkinter, 了解布局管理和事件管理。
今日冒险片段1
随着冒险的深入, 了不起来到了天帷巨兽的颈部地区. 听奥菲利亚讲, 这里位于天帷巨兽的颈部区域, 名为炼狱. 死后的信徒们都会被送到这里. 久而久之这里便树木林立, 人烟罕至, 暗无天日. 因此诞生了诸如僵尸, 夜叉一样的怪物. 好在现在的了不起现在也是有两把刷子的, 在其护送下, 路上的小怪虽然不少, 但是没有影响他们的赶路计划. 但是在走至一半时, 一个黑袍祭祀阻挡到了他们前进的去处. 奥菲利亚震惊的张开红唇说到: 这个难道是传说中的暗之传道士吗?
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”(默认) |
实操代码:
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
super(Application, self).__init__(master)
self.pack()
self.createWidget()
def createWidget(self):
self.label1 = Label(self, text="用户名")
self.label1.grid(row=0, column=0)
self.entry1 = Entry(self)
self.entry1.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=SW)
Button(self, text="取消").grid(row=2, column=2, sticky=S)
if __name__ == '__main__':
root = Tk()
root.geometry("400x90+200+300")
app = Application(master=root)
root.mainloop()
pack 布局管理器
pack 按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布 如果不指定任何选项,默认在父组件中自顶向下垂直添加组件. pack 是代码量最少,最简单, 最常用的一种,可以用于快速生成界面。
pack()方法提供的方法参数选项
如上列出了 pack 布局所有的属性,但是不需要挨个熟悉,了解基本的即可 pack 适用于简单的垂直或水平排布,如果需要复杂的布局可以使用 grid 或 place。
选项 | 说明 | 取值范围 |
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”(默认) |
实操代码:
from tkinter import *
root = Tk()
root.geometry("700x220")
# Frame是个矩形区域, 用来存放其他组件
f1 = Frame(root)
f1.pack()
f2 = Frame(root)
f2.pack()
# 声明一个元组
btnText = ("流行风", "中国风", "日本风", "重金属", "轻音乐")
# 遍历元组, 为Button命令
for txt in btnText:
Button(f1, text=txt).pack(side="left", padx="10")
# 遍历一个range序列
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 布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。
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”(默认) |
事件管理
一个 GUI 应用整个生命周期都处在一个消息循环 (event loop) 中。它等待事件的发生,并作出相应的处理. Tkinter 提供了用以处理相关事件的机制. 处理函数可被绑定给各个控件的各种事件. widget.bind(event, handler) 如果相关事件发生, handler 函数会被触发, 事件对象 event 会传递给 handler 函数.
今日冒险片段2
原来, 在大主教, 大祭司和教主之间. 还有四个特殊的存在, 那就是黎明, 黄昏, 暗, 光之传道师. 由于他们四个比较特殊, 因此直接受教主管辖, 但自从天帷巨兽上面的人被控制之后, 他们四位便杳无音信. 因此. 黎明和黄昏已在之前的冒险中被了不起解救. 但是相较于之前两位, 安之传道师明显要更强一些. 于是了不起赶忙把武器切换成光属性. 只见自己的长剑在黑暗中散发着点点光芒. 正在了不起握紧武器准备进攻的时候. 对方先开口说话了: "尊敬的教主大人, 暗之传道师-扎西特参上!". 原来, 暗之传道师由于本人对黑暗环境的契合度极高, 因此侥幸躲过了精神控制. 但是由于他是唯一一位在此地摆脱控制的人, 因此每天都会同被控制的怪物做斗争. 好在遇到了我们, 他才能将紧绷的心态方式下来. 就这样了不起的小队便又多了一人.
鼠标和键盘事件
代码 | 说明 |
| 鼠标左键按下。2 表示右键,3 表示中键; |
| 鼠标左键释放 |
| 按住鼠标左键移动 |
| 双击左键 |
| 鼠标指针进入某一组件区域 |
| 鼠标指针离开某一组件区域 |
| 滚动滚轮; |
| 按下 a 键,a 可用其他键替代 |
| 释放 a 键。 |
| 按下 A 键(大写的 A) |
| 同时按下 alt 和 a;alt 可用 ctrl 和 shift 替代 |
| 快速按两下 a |
| CTRL 和 V 键被同时按下,V 可以换成其它键位 |
event 对象常用属性:
名称 | 说明 |
char | 按键字符,仅对键盘事件有效 |
keycode | 按键编码,仅对键盘事件有效 |
keysym | 按键名称,仅对键盘事件有效 比如按下空格键:键的 char:键的 keycode, 32 键的 keysym:space ; 再比如按下 a 键:键的 char:a 键的 keycode:65 键的 keysym:a |
num | 鼠标按键,仅对鼠标事件有效 |
type | 所触发的事件类型 |
widget | 引起事件的组件 |
width,height | 组件改变后的大小,仅 Configure 有效 |
x,y | 鼠标当前位置,相对于父容器 |
x_root,y_root | 鼠标当前位置,相对于整个屏幕 |
实操代码:
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):
"""
char 按键字符,仅对键盘事件有效
keycode 按键编码,仅对键盘事件有效
keysym 按键名称,仅对键盘事件有效 比如按下空格键: 键的 char: 键的 keycode:32 键的 keysym:space
num 鼠标按键,仅对鼠标事件有效
type 所触发的事件类型
widget 引起事件的组件
width,height 组件改变后的大小,仅 Configure 有效
x,y 鼠标当前位置,相对于父容器
x_root,y_root 鼠标当前位置,相对于整个屏幕
"""
print("键的 keycode:{0},键的 char:{1},键的 keysym:{2}".format(event.keycode, event.char, event.keysym))
def press_a_test(event):
print("press a")
def release_a_test(event):
print("release a")
c1.bind("<Button-1>", mouseTest) # 鼠标左键按下
c1.bind("<B1-Motion>", testDrag) # 按住鼠标左键移动
root.bind("<KeyPress>", keyboardTest) # 键盘按压事件
root.bind("<KeyPress-a>", press_a_test) # 按下 a 键,a 可用其他键替代
root.bind("<KeyRelease-a>", release_a_test) # # 释放 a 键
root.mainloop()
多种事件绑定方式汇总
(1) 组件对象的绑定:
- 通过 command 属性绑定(适合简单不需获取 event 对象)Button(root,text=”登录”,command=login)
- 通过 bind()方法绑定(适合需要获取 event 对象)c1 = Canvas(); c1.bind(“<Button-1>”,drawLine)
(2) 组件类的绑定调用对象的 bind_class 函数,将该组件类所有的组件绑定事件:w.bind_class(“Widget”,”event”,eventhanler) 比如:btn01.bind_class(“Button”,”<Button-1>”,func)
实操代码:
from tkinter import Tk, Button
root = Tk()
root.geometry("270x30")
def mouseTest1(event):
print("bind()方式绑定,可以获取 event 对象")
print(event.widget)
def mouseTest2(a, b):
print("a={0},b={1}".format(a, b))
print("command 方式绑定,不能直接获取 event 对象")
def mouseTest3(event):
print("右键单击事件,绑定给所有按钮啦!!")
print(event.widget)
b1 = Button(root, text="测试 bind()绑定")
b1.pack(side="left") # bind 方式绑定事件
b1.bind("<Button-1>", mouseTest1)
# command 属性直接绑定事件
b2 = Button(root, text="测试 command2", command=lambda: mouseTest2("实参1传入", "实参2传入"))
b2.pack(side="left")
# 给所有 Button 按钮都绑定右键单击事件<Button-2>
b1.bind_class("Button", "<Button-2>", mouseTest3)
root.mainloop()
今日冒险片段3
在扎西特的指引下, 了不起很快的通过了炼狱的大部分区域, 直到来到最核心的地方. 这里被领主夜叉王守着, 他们无法从侧面突破, 只能选择从正面突破. 在三位传道师和一个教主的增幅下, 了不起开始与夜叉王开始战斗. 虽然享受着几人的增幅与武器的属性克制, 但是双方仍不相上下, 由此可见夜叉王的强大. 不一会, 了不起身上也有多出挂彩. 好在有奥菲利亚的治愈魔法, 这才让他不那么快败下阵来. 在战斗僵持不下时, 扎西特开始联合其他两位传道师释放他们教的特有技能, 联合契约召唤-精灵王伊莎贝拉的残念(因为少了一个人). 在精灵王和了不起的强烈攻势下, 夜叉王终于败下阵来. 在感慨传道师力量的强大之时, 了不起也顺利的晋升到了lv22.