### 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 三個函數的執行。

圖 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 事件
- 參考文獻