最常见的绑定形式是针对构件实例的:

    其语义是:若针对<构件实例>发生了与<事件描述符>相匹配的事件,则调用<事件处理程序>。 调用事件处理程序时,系统会传递一个 Event 类的对象作为实际参数,该对象描述了所发生 事件的详细信息。

    事件处理程序一般都是用户自定义的函数。这种函数在应用程序中定义,但不由应用程 序调用,而是由系统调用,所以一般称为回调(callback)函数。

    GUI 应用程序经常封装为类,在这种情况下,事件处理程序常常定义为应用程序类的方 法。我们将在 8.4.1 中通过例子详细介绍这种做法。

    先看一个处理鼠标点击事件的例子:

    【程序 8.6】eg8_6.py

    1. def callback(event):
    2. print "clicked at", event.x, event.y
    3. root = Tk()
    4. f = Frame(root, width=100, height=100)
    5. f.bind("<Button-1>", callback)
    6. f.pack()
    7. root.mainloop()

    本程序在根窗口中添加了一个框架构件,然后把框架构件与事件进行了绑定, 对应事件的回调函数是 callback,意思是每当在框架中点击鼠标左键时,都将触发 callback 执行。系统执行 callback 时,将一个描述事件的 Event 类对象作为参数传递给该函数, 该函数从事件对象参数中提取点击位置信息并在控制台输出类似“clicked at 44 63”的信息。

    键盘事件与焦点

    当图形界面中存在许多构件时,如果是用鼠标直接点击某个窗口或构件,程序自然就知

    道要操作哪个构件。但如果是按一下键盘,应该由哪个构件做出响应呢?GUI 引入了“焦点” 概念:图形界面中有唯一焦点,任何时刻只能有一个构件占有焦点,键盘事件总是发送到当 前占有焦点的构件。焦点的位置可以通过构件的 focus_set()方法来设置,也可以用键盘上的 Tab 键来轮转。因此,键盘事件处理比鼠标事件处理多了一个设置焦点的步骤,如下例所示:

    【程序 8.7】eg8_7.py

    1. from Tkinter import *
    2. def printInfo(event):
    3. print "pressed", event.char
    4. root = Tk()
    5. b = Button(root,text = 'Press any key')
    6. b.bind('<Key>',printInfo)
    7. b.focus_set()
    8. b.pack()
    9. root.mainloop()

    本程序创建了一个按钮构件,该按钮与按任意键事件进行绑定,事件处理程序是 回调函数 printInfo。此程序的 b.focus_set()语句将按钮设为键盘焦点,从而按下任何键都会由 按钮响应,并触发 printInfo 函数来处理事件,处理过程是显示按下的键的字符。读者可以思 考一下:本例中绑定的是事件,运行时如果输入上档键(如@#$%^&之类)会出现什 么结果呢?

    【程序 8.8】eg8_8.py

    1. from Tkinter import *
    2. def callback1(event):
    3. print "clicked at", event.x, event.y
    4. root = Tk()
    5. f = Frame(root, width=100, height=100)
    6. f.bind("<Key>", callback1)
    7. f.bind("<Button-1>", callback2)
    8. f.pack()
    9. root.mainloop()

    此程序在根窗口中创建一个框架构件,并为框架构件同时绑定了任意键事件和鼠 标左键事件。运行此程序,先在框架中点击鼠标,从而触发 callback2 函数的执行, 该函数又将框架设置为键盘焦点。此后,按下任何键都将触发 callback1 函数的执行,其功能 是显示所按的字符。运行此程序后如果没有在框架中先点击鼠标,则框架未获得焦点,也就 不会对键盘事件进行处理。

    当构件绑定的多个事件之间具有“特殊与一般”的关系,总是调用最“近”的事件处理 程序。例如,如果将某构件与任意键事件绑定,相应事件处理程序是 h1,又与回车键 事件绑定,相应事件处理程序是 h2,那么当按下回车键时,处理此事件的将是 h2。

    绑定层次

    前面三个例子中都是针对某个构件实例进行事件绑定,称为“实例绑定”。实例绑定只对 该构件实例有效,对其他实例——即使是同类型的构件——是无效的。除了实例绑定,Tkinter 还提供了其他事件绑定方式。实际上,Tkinter 中共有不同层次的四种绑定方法:

    • 实例绑定:绑定只对特定构件实例有效,用构件实例的 bind 方法实现。

    • 类绑定:绑定针对构件类,故对该类的所有实例有效,可用任何构件实例的 bind_class 方法实现。例如,为使 Button 类的所有实例都以同样方式响应回车键事件,可执行:

    • 窗口绑定:绑定对窗口(根窗口或顶层窗口)中的所有构件有效。用窗口的 bind 方 法实现,例如为使窗口中所有构件都以同样方式响应鼠标右键点击事件,可执行:

      1. root.bind('<Button-3>',callback)
    • 应用程序绑定:绑定对应用程序中的所有构件都有效。用任一构件实例的 bind_all 方法实现。例如,很多应用程序在运行时可以随时按下 F1 键以使用户得到帮助信 息,这可以通过建立 F1 键的应用程序绑定来实现:

      1. root.bind_all('<F1>',printHelp)

    下面这个例子演示了事件传递与绑定层次结合所带来的后果:

    【程序 8.9】eg8_9.py

    1. from Tkinter import *
    2. def printInstance(event):
    3. print 'Instance:',event.keycode
    4. def printToplevel(event):
    5. print 'Toplevel:',event.keycode
    6. def printClass(event):
    7. print 'Class:',event.keycode
    8. root = Tk()
    9. b = Button(root,text = 'Press Return')
    10. b.bind('&lt;Return&gt;',printInstance)
    11. b.winfo_toplevel().bind('&lt;Return&gt;',printToplevel)
    12. root.bind_class('Button','&lt;Return&gt;',printClass)
    13. root.bind_all('&lt;Return&gt;',printApp)
    14. b.pack()
    15. b.focus_set()
    16. root.mainloop()

    本程序中定义了四个层次的事件绑定,运行此程序并按下回车键,将得到如图 8.24 所示 的输出。这是因为事件首先被拥有焦点的按钮实例 b 捕获,并执行 printInstance 函 数。此后,事件还将向 b 的各级上层传递,从而依次被 b 所属的 Button 类、b 所属 的顶层窗口 root、b 所属的应用程序这三个层次捕获,分别导致 printClass、printTopleve 和 printApp 三个函数的执行。

    图 8.24 多层绑定

    关于程序 8.9 还有几点要说明:(1)程序中的 b.winfo_toplevel()方法返回 b 所属的顶层构 件,本例中即根窗口 root;(2)对程序代码与输出结果进行比较后可看出,事件的传递层次 与程序中绑定语句的次序没有关系;(3)类绑定与应用程序绑定可以通过任何构件来设置, 因此将上面程序中的 root.bind_class 和 root.bind_all 改成 b.bind_class 和 b.bind_all,结果也是 一样的。

    协议处理

    用过 Word 的读者都知道,如果编辑了文档还没有保存就去关闭程序窗口,Word 会弹出 一个对话框,询问用户是否要保存当前文档。如果我们希望利用事件绑定到事件处理程序来 实现这种功能,就面临一个问题:“关闭窗口”并不属于前面介绍过的事件类型,因此无法用 事件绑定来处理。

    为此,Tkinter 提供了一种称为“协议处理”的机制,用于应用程序处理来自操作系统窗 口管理器的协议消息。处理过程是这样的:当用户企图关闭窗口,操作系统的窗口管理器就 会生成一条 WM_DELETE_WINDOW 的协议消息并发送给应用程序,应用程序再调用相应的 处理程序来处理这条消息。

    窗口构件有一个称为 protocol 的方法,用于定义对协议消息的处理程序:

    其中窗口构件可以是根窗口或顶层窗口,处理程序是函数或方法。如此定义之后,当用户试 图关闭窗口时,我们自己的处理程序就会接管控制。处理程序可以弹出一个消息框询问用户 是否要保存当前数据,或者干脆忽略关闭窗口的请求。处理完毕之后,可以在处理程序中完 成关闭窗口的操作,方法是调用窗口的 destroy 方法。例如:

    【程序 8.10】eg8_10.py

    1. from Tkinter import *
    2. from tkMessageBox import *
    3. def callback():
    4. if askokcancel("Quit","Do you really wish to quit?"):
    5. root.destroy()
    6. root = Tk()
    7. root.protocol("WM_DELETE_WINDOW", callback)
    8. root.mainloop()

    虚拟事件

    我们也可以自定义新的事件类型,称为虚拟事件。虚拟事件的形式是<<事件名称>>,可 利用构件的 event_add 方法来创建。例如,如果想为构件 w 创建一个新事件<>, 该事件由鼠标右键或键盘上的 Pause 键触发,则执行下列语句:

    1. w.event_add("<<MyEvent>>","<Button-3>","<KeyPress-Pause>")

    此后就可以像系统定义的事件一样使用了。例如:

    在构件 w 上点击右键或按下 Pause 键都会触发函数 myHandler。