### 8.3.2 事件处理
GUI 应用程序的核心是对各种交互事件的处理程序。应用程序一般在完成建立图形界面 等初始化工作后都会进入一个事件循环,等待事件发生并触发相应的事件处理程序。Tkinter 程序通过 mainloop 方法进入事件循环,而事件与相应事件处理程序之间是通过绑定建立关联 的。
最常见的绑定形式是针对构件实例的:
```
<构件实例>.bind(<事件描述符>,<事件处理程序>)
```
其语义是:若针对<构件实例>发生了与<事件描述符>相匹配的事件,则调用<事件处理程序>。 调用事件处理程序时,系统会传递一个 Event 类的对象作为实际参数,该对象描述了所发生 事件的详细信息。
事件处理程序一般都是用户自定义的函数。这种函数在应用程序中定义,但不由应用程 序调用,而是由系统调用,所以一般称为回调(callback)函数。
GUI 应用程序经常封装为类,在这种情况下,事件处理程序常常定义为应用程序类的方 法。我们将在 8.4.1 中通过例子详细介绍这种做法。
先看一个处理鼠标点击事件的例子:
【程序 8.6】eg8_6.py
```
from Tkinter import *
def callback(event):
print "clicked at", event.x, event.y
root = Tk()
f = Frame(root, width=100, height=100)
f.bind("<Button-1>", callback)
f.pack()
root.mainloop()
```
本程序在根窗口中添加了一个框架构件,然后把框架构件与<Button-1>事件进行了绑定, 对应<Button-1>事件的回调函数是 callback,意思是每当在框架中点击鼠标左键时,都将触发 callback 执行。系统执行 callback 时,将一个描述事件的 Event 类对象作为参数传递给该函数, 该函数从事件对象参数中提取点击位置信息并在控制台输出类似“clicked at 44 63”的信息。
键盘事件与焦点
当图形界面中存在许多构件时,如果是用鼠标直接点击某个窗口或构件,程序自然就知
道要操作哪个构件。但如果是按一下键盘,应该由哪个构件做出响应呢?GUI 引入了“焦点” 概念:图形界面中有唯一焦点,任何时刻只能有一个构件占有焦点,键盘事件总是发送到当 前占有焦点的构件。焦点的位置可以通过构件的 focus_set()方法来设置,也可以用键盘上的 Tab 键来轮转。因此,键盘事件处理比鼠标事件处理多了一个设置焦点的步骤,如下例所示:
【程序 8.7】eg8_7.py
```
from Tkinter import *
def printInfo(event):
print "pressed", event.char
root = Tk()
b = Button(root,text = 'Press any key')
b.bind('<Key>',printInfo)
b.focus_set()
b.pack()
root.mainloop()
```
本程序创建了一个按钮构件,该按钮与按任意键事件<Key>进行绑定,事件处理程序是 回调函数 printInfo。此程序的 b.focus_set()语句将按钮设为键盘焦点,从而按下任何键都会由 按钮响应,并触发 printInfo 函数来处理事件,处理过程是显示按下的键的字符。读者可以思 考一下:本例中绑定的是<Key>事件,运行时如果输入上档键(如@#$%^&之类)会出现什 么结果呢?
绑定到多个事件 一个构件可以响应多种事件,例如下面这个程序同时响应鼠标和键盘事件:
【程序 8.8】eg8_8.py
```
from Tkinter import *
def callback1(event):
print "pressed", event.char
def callback2(event): f.focus_set()
print "clicked at", event.x, event.y
root = Tk()
f = Frame(root, width=100, height=100)
f.bind("<Key>", callback1)
f.bind("<Button-1>", callback2)
f.pack()
root.mainloop()
```
此程序在根窗口中创建一个框架构件,并为框架构件同时绑定了任意键事件<Key>和鼠 标左键事件<Button-1>。运行此程序,先在框架中点击鼠标,从而触发 callback2 函数的执行, 该函数又将框架设置为键盘焦点。此后,按下任何键都将触发 callback1 函数的执行,其功能 是显示所按的字符。运行此程序后如果没有在框架中先点击鼠标,则框架未获得焦点,也就 不会对键盘事件进行处理。
当构件绑定的多个事件之间具有“特殊与一般”的关系,总是调用最“近”的事件处理 程序。例如,如果将某构件与任意键事件<Key>绑定,相应事件处理程序是 h1,又与回车键 事件<Return>绑定,相应事件处理程序是 h2,那么当按下回车键时,处理此事件的将是 h2。
绑定层次
前面三个例子中都是针对某个构件实例进行事件绑定,称为“实例绑定”。实例绑定只对 该构件实例有效,对其他实例——即使是同类型的构件——是无效的。除了实例绑定,Tkinter 还提供了其他事件绑定方式。实际上,Tkinter 中共有不同层次的四种绑定方法:
+ 实例绑定:绑定只对特定构件实例有效,用构件实例的 bind 方法实现。
+ 类绑定:绑定针对构件类,故对该类的所有实例有效,可用任何构件实例的 bind_class 方法实现。例如,为使 Button 类的所有实例都以同样方式响应回车键事件,可执行:
```
root.bind_class("Button","<Return>",callback)
```
+ 窗口绑定:绑定对窗口(根窗口或顶层窗口)中的所有构件有效。用窗口的 bind 方 法实现,例如为使窗口中所有构件都以同样方式响应鼠标右键点击事件,可执行:
```
root.bind('<Button-3>',callback)
```
+ 应用程序绑定:绑定对应用程序中的所有构件都有效。用任一构件实例的 bind_all 方法实现。例如,很多应用程序在运行时可以随时按下 F1 键以使用户得到帮助信 息,这可以通过建立 F1 键的应用程序绑定来实现:
```
root.bind_all('<F1>',printHelp)
```
下面这个例子演示了事件传递与绑定层次结合所带来的后果:
【程序 8.9】eg8_9.py
```
from Tkinter import *
def printInstance(event):
print 'Instance:',event.keycode
def printToplevel(event):
print 'Toplevel:',event.keycode
def printClass(event):
print 'Class:',event.keycode
def printApp(event):
print 'Application:',event.keycode
root = Tk()
b = Button(root,text = 'Press Return')
b.bind('<Return>',printInstance)
b.winfo_toplevel().bind('<Return>',printToplevel)
root.bind_class('Button','<Return>',printClass)
root.bind_all('<Return>',printApp)
b.pack()
b.focus_set()
root.mainloop()
```
本程序中定义了四个层次的事件绑定,运行此程序并按下回车键,将得到如图 8.24 所示 的输出。这是因为<Return>事件首先被拥有焦点的按钮实例 b 捕获,并执行 printInstance 函 数。此后,<Return>事件还将向 b 的各级上层传递,从而依次被 b 所属的 Button 类、b 所属 的顶层窗口 root、b 所属的应用程序这三个层次捕获,分别导致 printClass、printTopleve 和 printApp 三个函数的执行。
![](https://box.kancloud.cn/2016-02-22_56cafce65ccd8.png)
图 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 的方法,用于定义对协议消息的处理程序:
```
<窗口构件>.protocol("WM_DELETE_WINDOW",<处理程序>)
```
其中窗口构件可以是根窗口或顶层窗口,处理程序是函数或方法。如此定义之后,当用户试 图关闭窗口时,我们自己的处理程序就会接管控制。处理程序可以弹出一个消息框询问用户 是否要保存当前数据,或者干脆忽略关闭窗口的请求。处理完毕之后,可以在处理程序中完 成关闭窗口的操作,方法是调用窗口的 destroy 方法。例如:
【程序 8.10】eg8_10.py
```
from Tkinter import *
from tkMessageBox import *
def callback():
if askokcancel("Quit","Do you really wish to quit?"):
root.destroy()
root = Tk()
root.protocol("WM_DELETE_WINDOW", callback)
root.mainloop()
```
虚拟事件
我们也可以自定义新的事件类型,称为虚拟事件。虚拟事件的形式是<<事件名称>>,可 利用构件的 event_add 方法来创建。例如,如果想为构件 w 创建一个新事件<<MyEvent>>, 该事件由鼠标右键或键盘上的 Pause 键触发,则执行下列语句:
```
w.event_add("<<MyEvent>>","<Button-3>","<KeyPress-Pause>")
```
此后就可以像系统定义的事件一样使用了。例如:
```
w.bind("<<MyEvent>>",myHandler)
```
在构件 w 上点击右键或按下 Pause 键都会触发函数 myHandler。
- 前言
- 第 1 章 计算与计算思维
- 1.1 什么是计算?
- 1.1.1 计算机与计算
- 1.1.2 计算机语言
- 1.1.3 算法
- 1.1.4 实现
- 1.2 什么是计算思维?
- 1.2.1 计算思维的基本原则
- 1.2.2 计算思维的具体例子
- 1.2.3 日常生活中的计算思维
- 1.2.4 计算思维对其他学科的影响
- 1.3 初识 Python
- 1.3.1 Python 简介
- 1.3.2 第一个程序
- 1.3.3 程序的执行方式
- 1.3.4 Python 语言的基本成分
- 1.4 程序排错
- 1.5 练习
- 第 2 章 用数据表示现实世界
- 2.1 数据和数据类型
- 2.1.1 数据是对现实的抽象
- 2.1.1 常量与变量
- 2.1.2 数据类型
- 2.1.3 Python 的动态类型*
- 2.2 数值类型
- 2.2.1 整数类型 int
- 2.2.2 长整数类型 long
- 2.2.3 浮点数类型 float
- 2.2.4 数学库模块 math
- 2.2.5 复数类型 complex*
- 2.3 字符串类型 str
- 2.3.1 字符串类型的字面值形式
- 2.3.2 字符串类型的操作
- 2.3.3 字符的机内表示
- 2.3.4 字符串类型与其他类型的转换
- 2.3.5 字符串库 string
- 2.4 布尔类型 bool
- 2.4.1 关系运算
- 2.4.2 逻辑运算
- 2.4.3 布尔代数运算定律*
- 2.4.4 Python 中真假的表示与计算*
- 2.5 列表和元组类型
- 2.5.1 列表类型 list
- 2.5.2 元组类型 tuple
- 2.6 数据的输入和输出
- 2.6.1 数据的输入
- 2.6.2 数据的输出
- 2.6.3 格式化输出
- 2.7 编程案例:查找问题
- 2.8 练习
- 第 3 章 数据处理的流程控制
- 3.1 顺序控制结构
- 3.2 分支控制结构
- 3.2.1 单分支结构
- 3.2.2 两路分支结构
- 3.2.3 多路分支结构
- 3.3 异常处理
- 3.3.1 传统的错误检测方法
- 3.3.2 传统错误检测方法的缺点
- 3.3.3 异常处理机制
- 3.4 循环控制结构
- 3.4.1 for 循环
- 3.4.2 while 循环
- 3.4.3 循环的非正常中断
- 3.4.4 嵌套循环
- 3.5 结构化程序设计
- 3.5.1 程序开发过程
- 3.5.2 结构化程序设计的基本内容
- 3.6 编程案例:如何求 n 个数据的最大值?
- 3.6.1 几种解题策略
- 3.6.2 经验总结
- 3.7 Python 布尔表达式用作控制结构*
- 3.8 练习
- 第 4 章 模块化编程
- 4.1 模块化编程基本概念
- 4.1.1 模块化设计概述
- 4.1.2 模块化编程
- 4.1.3 编程语言对模块化编程的支持
- 4.2 Python 语言中的函数
- 4.2.1 用函数减少重复代码 首先看一个简单的用字符画一棵树的程序:
- 4.2.2 用函数改善程序结构
- 4.2.3 用函数增强程序的通用性
- 4.2.4 小结:函数的定义与调用
- 4.2.5 变量的作用域
- 4.2.6 函数的返回值
- 4.3 自顶向下设计
- 4.3.1 顶层设计
- 4.3.2 第二层设计
- 4.3.3 第三层设计
- 4.3.4 第四层设计
- 4.3.5 自底向上实现与单元测试
- 4.3.6 开发过程小结
- 4.4 Python 模块*
- 4.4.1 模块的创建和使用
- 4.4.2 Python 程序架构
- 4.4.3 标准库模块
- 4.4.4 模块的有条件执行
- 4.5 练习
- 第 5 章 图形编程
- 5.1 概述
- 5.1.1 计算可视化
- 5.1.2 图形是复杂数据
- 5.1.3 用对象表示复杂数据
- 5.2 Tkinter 图形编程
- 5.2.1 导入模块及创建根窗口
- 5.2.2 创建画布
- 5.2.3 在画布上绘图
- 5.2.4 图形的事件处理
- 5.3 编程案例
- 5.3.1 统计图表
- 5.3.2 计算机动画
- 5.4 软件的层次化设计:一个案例
- 5.4.1 层次化体系结构
- 5.4.2 案例:图形库 graphics
- 5.4.3 graphics 与面向对象
- 5.5 练习
- 第 6 章 大量数据的表示和处理
- 6.1 概述
- 6.2 有序的数据集合体
- 6.2.1 字符串
- 6.2.2 列表
- 6.2.3 元组
- 6.3 无序的数据集合体
- 6.3.1 集合
- 6.3.2 字典
- 6.4 文件
- 6.4.1 文件的基本概念
- 6.4.2 文件操作
- 6.4.3 编程案例:文本文件分析
- 6.4.4 缓冲
- 6.4.5 二进制文件与随机存取*
- 6.5 几种高级数据结构*
- 6.5.1 链表
- 6.5.2 堆栈
- 6.5.3 队列
- 6.6 练习
- 第 7 章 面向对象思想与编程
- 7.1 数据与操作:两种观点
- 7.1.1 面向过程观点
- 7.1.2 面向对象观点
- 7.1.3 类是类型概念的发展
- 7.2 面向对象编程
- 7.2.1 类的定义
- 7.2.2 对象的创建
- 7.2.3 对象方法的调用
- 7.2.4 编程实例:模拟炮弹飞行
- 7.2.5 类与模块化
- 7.2.6 对象的集合体
- 7.3 超类与子类*
- 7.3.1 继承
- 7.3.2 覆写
- 7.3.3 多态性
- 7.4 面向对象设计*
- 7.5 练习
- 第 8 章 图形用户界面
- 8.1 图形用户界面概述
- 8.1.1 程序的用户界面
- 8.1.2 图形界面的组成
- 8.1.3 事件驱动
- 8.2 GUI 编程
- 8.2.1 UI 编程概述
- 8.2.2 初识 Tkinter
- 8.2.3 常见 GUI 构件的用法
- 8.2.4 布局
- 8.2.5 对话框*
- 8.3 Tkinter 事件驱动编程
- 8.3.1 事件和事件对象
- 8.3.2 事件处理
- 8.4 模型-视图设计方法
- 8.4.1 将 GUI 应用程序封装成对象
- 8.4.2 模型与视图
- 8.4.3 编程案例:汇率换算器
- 8.5 练习
- 第 9 章 模拟与并发
- 9.1 模拟
- 9.1.1 计算机建模
- 9.1.2 随机问题的建模与模拟
- 9.1.3 编程案例:乒乓球比赛模拟
- 9.2 原型法
- 9.3 并行计算*
- 9.3.1 串行、并发与并行
- 9.3.2 进程与线程
- 9.3.3 多线程编程的应用
- 9.3.4 Python 多线程编程
- 9.3.5 小结
- 9.4 练习
- 第 10 章 算法设计和分析
- 10.1 枚举法
- 10.2 递归
- 10.3 分治法
- 10.4 贪心法
- 10.5 算法分析
- 10.5.1 算法复杂度
- 10.5.2 算法分析实例
- 10.6 不可计算的问题
- 10.7 练习
- 第 11 章 计算+X
- 11.1 计算数学
- 11.2 生物信息学
- 11.3 计算物理学
- 11.4 计算化学
- 11.5 计算经济学
- 11.6 练习
- 附录
- 1 Python 异常处理参考
- 2 Tkinter 画布方法
- 3 Tkinter 编程参考
- 3.1 构件属性值的设置
- 3.2 构件的标准属性
- 3.3 各种构件的属性
- 3.4 对话框
- 3.5 事件
- 参考文献