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)