GUI的最终选择 Tkinter(九):事件
Tkinter事件处理
Tkinter应用会花费大部分的时间在处理事件循环中(通过mainloop()方法进入),事件可以是触发的鼠标,键盘的操作,管理窗口触发的重绘事件(在多数情况下都是有用户间接引起的)。
Tkinter提供了一个强大的机制,可以让你自由的去处理事件,对于每个组件来说。可以通过bond()方法将函数或者方法绑定到具体的事件上。当触发器的事件满足该组件的事件时,Tkinter()就会带着事件取调用handler()方法。
1 from tkinter import * 2 3 root = Tk() 4 def callbach(event): 5 print("点击位置:",event.x,event.y) 6 frame = Frame(root,width=200,height=200) 7 frame.bind("<Button-1>",callbach) 8 frame.pack() 9 mainloop()
执行结果:
在例子中,使用Frame组件的bind()方法将鼠标的单击事件和自定义的callbach()方法绑定起来,那么运行的结果就是:当鼠标点击的时候,IDLE会相应的将鼠标的位置显示出来。
只有当组件获得焦点的时候才能接收键盘事件(Key),下面的例子中将用focus_set()获得焦点,可以设置Frame的takefocus选项为True,然后将焦点转移上来。
1 from tkinter import * 2 3 root = Tk() 4 def callbach(event): 5 print("点击位置:",event.x,event.y) 6 frame = Frame(root,width=200,height=200) 7 frame.bind("<Key>",callbach) 8 frame.focus_set() 9 frame.pack() 10 mainloop()
执行结果:
在来实现一个展示捕获鼠标轨迹的组件,需要关注的是事件。
1 from tkinter import * 2 3 root = Tk() 4 def callbach(event): 5 print("点击位置:",event.x,event.y) 6 frame = Frame(root,width=200,height=200) 7 frame.bind("<Motion>",callbach) 8 frame.pack() 9 mainloop()
执行结果:
事件序列
Tkinter使用一种称之为事件序列的机制运行用户定义事件,用户需要使用bind()方法将具体的事件序列与自定义的方法做绑定。事件序列以字符串的形式表示的,可以是一个或多个相关联的事件(如果多个事件,需要对应的方法只有在满足所有事件的前提下才会满足)。
事件序列使用一下语法描述:
<modifier-type-detail>
修饰 部分为可选内容,一般在组合事件中用于多功能按键组合的描述。如:Ctrl + Shift + V;
类型 部分用于描述一般的事件对象类型,如键盘按键或鼠标动作等;
type区域是事件说明符中最重要的部分,它说明我们想绑定哪一类的事件
例如Button\Key或者如进入、配置之类的窗口管理事件。
详细描述 部分为可选内容,一般用于描述事件具体单元对象,如KeyPress-A,表示按键盘A键
Modifier和detail区域用于额外的信息
在许多情况下可以省略,还有各种方法来简化事件字符串,例如为了匹配键盘键,您可以省略
尖括号,只需使用键,当然要包含在尖括号内让我们来看看最常见的事件格式
事件格式
<Button-1>鼠标左键按下事件,1表示左键;2-鼠标中键;3-鼠标右键
当您在组件上按下鼠标左键,随后的鼠标事件(移动、释放)就会发送到当前组件,即使
鼠标移动到当前组件之外,鼠标指针的的当前位置在传递给回调函数的event对象的x,y中
<Button-1>、<ButtonPress-1>、<1>都是同义词
<B1-Motion>按住鼠标左键移动鼠标,鼠标指针的当前位置在传递给回调函数的event中的x\y提供
<ButtonRelease-1>鼠标左键被释放
<Double-Button-1>鼠标左键被双击,你可以用Double表示双击,还可以用Triple表示三击
注意,如果你绑定了<Button-1>和<Double-Button-1>,那么2个事件都被触发
键入部分常用的关键字及含义
事件类型 |
意义描述 |
Activate |
组件状态变化事件,未激活变为激活时触发; 与 Deactivate 相对 |
Button |
鼠标按键点击事件; 由描述部分指定具体按键: <Button-1> 鼠标左键 <Button-2> 鼠标中键 <Button-3> 鼠标右键 <Button-4> 滚轮上(linux) <Button-5> 滚轮下(linux) |
ButtonRelease |
释放鼠标按键事件。强调释放动作 |
Configure |
组件尺寸改变事件 |
Deactivate |
组件状态改变事件。激活变为未激活; 与 Activate 相对 |
Destroy |
组件被销毁触发事件 |
Enter |
鼠标指针进入组件区域事件; 与 Leave 相对 |
Expose |
窗口或组件的部分被显示时触发事件,比如某部分被置顶 |
FocusIn |
获得焦点事件。如果组件的takefocus属性为True,则用户可通过Tab按键切换组件焦点。也可用focus_set()方法设置组件焦点 |
FocusOut |
失去焦点事件 |
KeyPress |
键盘按键事件。强调按下动作; 由描述部分指定具体按键,如 <KeyPress-A>、<KeyPress-B>; 可简写为Key |
KeyRelease |
释放键盘按键事件。强调释放动作 |
Leave |
鼠标指针离开组件区域事件; 与 Enter 相对 |
Map |
组件被映射时触发事件。指在程序中,该组件被调用(组件显示、隐藏等)。如调用grid()方法 |
Motion |
鼠标在组件区域内移动事件 |
MouseWheel |
鼠标滚轮事件。该事件序列支持Windows和Mac 等同于Linux下的Button-4/5 |
Unmap |
组件被取消映射事件; 与Map相对 |
Visibility |
应用程序至少有一部分被置顶(即被显示出来)事件 |
<Shift-Up> |
用户按下shift键同时按下向上键触发,也可以用Alt,shift,control frame1.bind("<Shift M>",shiftm) 当同时按下shift 和m 后回调函数shiftm |
<Return> |
用户按下回车键,你可以绑定键盘上几乎所有键,Shift_L(左边的shift键) Delete\F1\F5\Num_Lock都可以 |
修饰语
修饰符 |
意义 |
Alt |
按下Alt键 |
Any |
按下任意键。如<Any-KeyPress>,表示按下任意键事件 |
Control |
按下Ctrl键 |
Double |
被修饰的事件类型连续两次触发。如<Double-Button-1>,表示双击鼠标左键 |
Lock |
大写字母锁定键 |
Shift |
按下Shift |
Triple |
被修饰的事件类型连续三次触发 |
事件对象
当Tkinter回调预定义的函数时,将带着事件对象(作为参数)去掉用。
事件对象的属性及含义
属性 |
意义描述 |
widget |
产生事件的组件 |
x,y |
以窗口左上角为原点,鼠标指针的坐标。以像素为单位 |
x_root,y_root |
以屏幕左上角为原点,鼠标指针的坐标,以像素为单位 |
char |
键盘按键字符 |
keysym |
键盘按键名 |
keycode |
键盘按键码 |
num |
鼠标按钮数字 |
width,height |
组件尺寸改变后的宽和高 |
type |
事件类型 |
当事件为,,的时候,细节可以通过设定具体的按键名来筛选,例如表示按下键盘的大写字母H的时候触发事件,表示按下键盘上的Tab键的时候触发事件。
列举了键盘的所有特殊按键的keysym和keycode(其中的按键码是对应美国标准101键盘的latin-1字符集,键盘标准不同对应的按键码不同,但按键名是一样的。)
键盘所有特殊按键的键符和键码
按键名(keysym) |
按键码(keycode) |
按键描述 |
Alt_L |
64 |
左Alt |
Alt_R |
113 |
右Alt |
BackSpace |
22 |
退格 |
Cancel |
110 |
break键 |
Caps_Lock |
66 |
大写字母锁定 |
Control_L |
37 |
左Ctrl |
Control_R |
109 |
右Ctrl |
Shift_L |
50 |
左Shift |
Shift_R |
62 |
右Shift |
Insert |
106 |
插入键 |
Delete |
107 |
删除键 |
Left |
100 |
左方向键 |
Right |
102 |
右方向键 |
Up |
98 |
上方向键 |
Down |
104 |
下方向键 |
Home |
97 |
Home键 |
End |
103 |
End键 |
Escape |
9 |
Esc键 |
Execute |
111 |
Sysreq键 |
F1~F11 |
67~77 |
F1键到F11键(一一对应) |
F12 |
96 |
F12键 |
Linefeed |
54 |
Linefeed键(Ctrl + J) |
KP_0 |
90 |
数字键盘0 |
KP_1 |
87 |
数字键盘1 |
KP_2 |
88 |
数字键盘2 |
KP_3 |
89 |
数字键盘3 |
KP_4 |
83 |
数字键盘4 |
KP_5 |
84 |
数字键盘5 |
KP_6 |
85 |
数字键盘6 |
KP_7 |
79 |
数字键盘7 |
KP_8 |
80 |
数字键盘8 |
KP_9 |
81 |
数字键盘9 |
KP_Subtract |
82 |
数字键盘 - |
KP_Add |
86 |
数字键盘 + |
KP_Begin |
84 |
数字键盘中间键(5) |
KP_Decimal |
91 |
数字键盘点键 |
KP_Delete |
91 |
数字键盘删除 |
KP_Divide |
112 |
数字键盘反斜杠 / |
KP_Up |
80 |
数字键盘上方向 |
KP_Left |
83 |
数字键盘左方向 |
KP_Right |
85 |
数字键盘右方向 |
KP_Down |
88 |
数字键盘下方向键 |
KP_End |
87 |
数字键盘End键 |
KP_Enter |
108 |
数字键盘回车 |
KP_Home |
79 |
数字键盘Home |
KP_Insert |
90 |
数字键盘插入 |
KP_Multiply |
63 |
数字键盘星号 * |
KP_Next |
89 |
数字键盘PageDown |
KP_Prior |
81 |
数字键盘PageUp |
Next |
105 |
PageDown键 |
Num_Lock |
77 |
数字键盘锁定 |
Pause |
110 |
Pause暂停 |
|
111 |
屏幕打印 |
Prior |
99 |
PageUp键 |
Return |
36 |
回车 |
Scroll_Lock |
78 |
ScrollLock键 |
Tab |
23 |
制表按键 |
按键名(keysym) |
按键码(keycode) |
按键描述 |
Alt_L |
64 |
左Alt |
Alt_R |
113 |
右Alt |
BackSpace |
22 |
退格 |
Cancel |
110 |
break键 |
Caps_Lock |
66 |
大写字母锁定 |
Control_L |
37 |
左Ctrl |
Control_R |
109 |
右Ctrl |
Shift_L |
50 |
左Shift |
Shift_R |
62 |
右Shift |
实例和类的绑定
以上的例子中我们用的都是在实例上使用bind方法,这意味着这样只能bind在一个组件上,
如果我们创建一个新的组件,他们不会继承这些绑定关系。
不过Tkinter也提供了类级别和应用级别的bind,实际上,你可以使用以下级别的binding
1.组件实例
2.组件的顶层窗体(Toplevel 或者 root)
3.组件类,用bind_class方法
4.整个应用,用bind_all方法
比如,你可以用bind_all来绑定F1按钮的点击,这样你能在这个应用的如何地方点击都可以弹出帮助框
但如果同一个键你在多处绑定了怎么办?
首先,在以上4个层次之内,Tkinter选择最接近匹配的方式。比如为<Key>
和<Return>事件创建实例绑定,那么只有按下Enter键之后才会调用<Return>的回调函数
但是,如果你如果在以上4个层次间,比如你同时向toplevel组件添加<Return>绑定,那么将调用2个绑定
Tkinter首先调用实例级别的最佳绑定,最后在应用程序级别调用最佳可用绑定,因此,
在极端情况下,单个事件可以调用4个事件处理程序。
常见的混乱原因是当您尝试使用绑定来覆盖标准组件的默认行为。例如,假设你想在
文本框内禁止输入回车键,这样用户就无法输入多行数据,也行你会用下面的小伎俩
1 def ignore(event): 2 pass 3 4 text.bind("<Return>",ignore)
或者,你喜欢一行的简洁代码
text.bind("<Return>",lamdba e:None)
不幸的是,新的一行依然会插入,因为,以上的绑定仅仅应用在应用级别,
而标准的行为依然有类级别的绑定实现了。
你可以使用bind_class方法来改变类级别的绑定,但这将更改应用程序中所有文本组件的行为。下面是比较合理的解决办法
1 def ignore(event): 2 return "break" 3 text.bind("<Return>", ignore)
顺便说一句,如果你真的想改变所有文本组件的默认行为,你可以用以下bind_class方法
top.bind_class("Text", "<Return>", lambda e: None)
真的不建议这么做,不要改变组件的默认行为。
协议
除了事件绑定,Tkinter还提供了协议处理的机制,这里的协议指的是应用程序和windows manager之间的互动
最常见的是WM_DELETE_WINDOW,用于定义当用户使用窗口管理器显式关闭窗口是的事件。
你可以用protocol方法来安装这个协议的回调函数(这个组件必须是root或者Toplevel组件)
widget.protocol("WM_DELETE_WINDOW", handler)
一旦你注册了自己的处理函数,Tkinter将不再自动的关闭程序,比如下面这个例子
from Tkinter import * import tkMessageBox def callback(): if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"): root.destroy() root = Tk() root.protocol("WM_DELETE_WINDOW", callback) root.mainloop()
注意,即使你没有在顶层窗口注册WM_DELETE_WINDOW的处理程序,窗口还是会被销毁的。最好还是自己注册一个处理程序
top = Toplevel(...)#确保窗口小部件实例被删除
top.protocol(“WM_DELETE_WINDOW”,top.destroy)