6.事件和绑定
正如前几章提到的,Tkinter应用程序大部分事件都在事件循环中(通过mainloop方法进入事件循环)
事件来自于多个来源,比如用户的键盘的输入和鼠标操作,和window manager的重绘事件(大多数情况下不是有用户直接调用的)
Tkinter提供强大的机制让您自己处理事件,每个组件你都可以为各种事件绑定python的函数和方法
widget.bind(event, handler)
如果组件中发生了与event描述匹配的事件,将调用handler指定的处理程序,
例子,在窗口中捕获点击次数
from Tkinter import *
root = Tk()
def callback(event):
print "clicked at", event.x, event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
在这里例子中,我们用bind方法把frame的鼠标左键点击事件绑定到callback函数
当控件拥有键盘焦点后,键盘事件将发送到对应的控件,你可以使用focus_set
方法来获得键盘焦点,获取键盘事件的代码:
from Tkinter import *
root = Tk()
def key(event):
print "pressed", repr(event.char)
def callback(event):
frame.focus_set()
print "clicked at", event.x, event.y
frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
事件
事件用格式化的字符串来表示
<modifier-type-detail>
type区域是事件说明符中最重要的部分,它说明我们想绑定哪一类的事件
例如Button\Key或者如进入、配置之类的窗口管理事件。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个事件都被触发
<Enter>当鼠标进入组件区域被触发
<Leave>当鼠标离开组件区域被触发
<FocusIn>当键盘焦点移到这个组件,或者移到这个组件的子组件
<FocusOut>失去焦点
<Return>用户按下回车键,你可以绑定键盘上几乎所有键,Shift_L(左边的shift键)
Delete\F1\F5\Num_Lock都可以
<Key>用户按下任意键
<Shift-Up>用户按下shift键同时按下向上键触发,也可以用Alt,shift,control
frame1.bind("<Shift M>",shiftm) 当同时按下shift 和m 后回调函数shiftm
<Configure>如果组件的尺寸变化那么被触发
Event对象
event对象是标准的Python对象实例,有许多属性
事件属性
widget:产生这个事件的组件,这个一个合法Tkinter组件实例而不是名字,所有的事件都有这个属性
x,y:当前鼠标的位置,像素为单位
x_root,y_root:当前鼠标相对于上层框架的位置
char:字符码 只有键盘事件才有,string类型
keysym:键盘符号只有键盘事件才有
keycode:键盘代码
num:鼠标的编号,左键1,中键2,右键3....
width,height:组件新尺寸,只有Configure 事件才有,像素
type:事件类型
实例和类的绑定
以上的例子中我们用的都是在实例上使用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个事件处理程序。
常见的混乱原因是当您尝试使用绑定来覆盖标准组件的默认行为。例如,假设你想在
文本框内禁止输入回车键,这样用户就无法输入多行数据,也行你会用下面的小伎俩
def ignore(event):
pass
text.bind("<Return>",ignore)
或者,你喜欢一行的简洁代码
text.bind("<Return>",lamdba e:None)
不幸的是,新的一行依然会插入,因为,以上的绑定仅仅应用在应用级别,
而标准的行为依然有类级别的绑定实现了。
你可以使用bind_class方法来改变类级别的绑定,但这将更改应用程序中所有文本组件的行为。下面是比较合理的解决办法
def ignore(event):
return "break"
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将不再自动的关闭程序,比如下面这个例子
Capturing destroy events
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)