處理 Event


marquee relinq memory cham entry color_model
tcl 版 v v v v v v
nis 版 v v v v v v

其中 color_model 只是一個函式庫, 不能單獨執行。 有幾個程式用到它。

  1. 程式設計師自行安排的 "定時事件"
    1. 用 after 可安排副程式在 (a) 固定時間後 或 (b) 沒有其他事件要處理時 被叫起來執行.
    2. 注意: 像 marquee 範例中這樣的寫法並不是遞迴. 下一個 RotateColor 被叫起來之前, 前一個 RotateColor 早已執行完畢.
    3. 如果把 after 的傳回值記起來, 之後還可以用 after info 取得有關於這個事件的資訊, 或用 after cancel 將它取消. 詳見 after(n) 或 perdoc Tk::After.
  2. 滑鼠與鍵盤
    1. 每當使用者移動滑鼠, 按下按鈕, 或按下鍵盤, 都會產生一個事件 (event). 究竟那個 widget 會被叫起來處理這個事件呢? 要看目前誰擁有 mouse grab 以及誰擁有 keyboard focus 來決定.
    2. 一個 tk 應用程式內可以有一個 widget 佔據住所有 mouse 事件, 只有它自己或它的 children, grandchildren, ... 才收得到 mouse 事件. (在文件中叫做 grab subtree.) 要佔據/釋放 mouse 事件, 可用 grab 命令, 詳見 grab(n) 或 perldoc Tk::grab.
    3. 一般的 grab 稱為 local grab, 可以用來要求使用者先把這件重要的事情做完. 當然使用者還是可以自由地與 螢幕上其他應用程式 互動, 只不不能與 本應用程式 的其他部分互動而已. 例如 Dialog 這個 widget class 就是用這種方式運作.
    4. 有些特殊的情況需要使用 global grab, 例如你的應用程式負責改變整個系統某些非常低階的設定. 如果你設定的是 global grab, 則使用者甚至也無法與 螢幕上其他應用程式 互動. 這對會用電腦的人而言, 是一件非常不禮貌的事情, 應該盡量避免. (當然如果你心目中的使用者是不會用電腦的人, 這麼做可以避免他在與其他程式互動的過程中, 不小心試出你程式的 bugs) 不論是 local grab 或 global grab, 這類 "限制使用者選擇" 的互動方式叫做 modal interaction
    5. 當然如果目前沒有任何一個 widget 佔據住 mouse, 那麼
    6. 要改變鍵盤的掌控權, 可以用 focus 命令. 見 focus(n) 與 perldoc Tk::focus. 從這裡也可以查到如何設定 implicit focus model, 也就是讓 keyboard focus 在這個 tk 應用程式內, 立即跟著滑鼠跑, 滑鼠移到那個 widget 它立即就獲得 keyboard focus (而不需要再按滑鼠按鈕點一下).
    7. 在一個 tk 應用程式內, 按 tab 鍵或 shift-tab 鍵, 會讓 keyboard focus 在各個 widget 之間跳來跳去. 要把一個 widget 排除在外, 可以把 -takefocus 這個 configuration option 設成 0. 請執行 relinquish 並觀察用 tab 鍵選擇按鈕再用 space 按按鈕的效果. 詳見 options(n) 或 perldoc Tk::options. 想要知道跳動的順序, 可以用 tk_focusNext 與 tk_focusPrev. 詳見 focus 的手冊.
  3. 用 bind 指定處理事件的 callback 時, 往往需要為 callback 安排好「事件發生當時」的一些特殊參數, 例如當時滑鼠的 x, y 座標 (見 chameleon 範例), 或當時使用者所按下的究竟是那個鍵 (見 phoneentry 範例), 甚或是「當時事件發生在誰身上」 (見 grid_tut 範例). 這些參數不是在設定 binding 時就可以知道的, 而是每次事件發生時才臨時知道的, 要如何表示呢? 請見 bind(n) 或 perldoc Tk::bind 手冊當中的 SUBSTITUTIONS 一節說明.
  4. 如何寫 callback (event handler): tcl 版, perl 版 Perl 使用者: "bind" 所指定的 callback, 自動會接收到一個額外的參數 (且一定放在其他參數之前): "事件是在那一個 widget 當中發生的呢?" 但是 "-command" 所指定的 callback, 則沒有額外的參數. 請見 relinquish).
  5. Event 的語法:
    1. 要描述一個 event, 至少要指出它的類別 (event type). 以下是比較常用的 event types:
      1. Button/ButtonRelease: 使用者按下/放開某個滑鼠鍵.
      2. Key/KeyRelease: 使用者按/放開下鍵盤上某個鍵. 見 memory 範例. 但是要記得設定 keyboard focus, 否則平常不接受鍵盤輸入的 widget class (例如 Label) 依舊沒有反應. 見 chameleon 範例.
      3. Motion: 滑鼠移動了. 見 chameleon 範例.
      4. Enter/Leave: 滑鼠進入/離開這個 widget. 見 chameleon 範例.
      5. FocusIn/FocusOut: 鍵盤的 focus 落到這個 widget 上/離開這個 widget.
      6. Map/Unmap: 這個 widget 被 geometry manager 放上來/移除掉了. (記得嗎? 一個 widget 並不是一產生就出現在螢幕上, 也並不是一被從螢幕上移除掉就不存在了.)
      7. Visibility: 這個 widget 在螢幕上的 "能見度" 改變. 除了被 geometry manager 放入 master 或自其中移除之外, 還可能因為其他 widgets 或其他應用程式蓋在這個 widget 上面, 或因為移動了 master 的 scrollbar, 或因為 master 的大小改變, 因而改變這個 widget 的能見度.
      8. Configure: 這個 widget 的任何一個屬性 (configuration option) 改變了.
      9. Destroy: 這個 widget 將要被毀掉了.
    2. 有時 type 的後面會加上一個細節描述 (detail), 例如 Button/ButtonRelease 後面加上數字, 可以指定只有在特定的滑鼠鍵按下時才反應; Key/KeyRelease 後面加上一個字元, 可以指定只對鍵盤上特定的鍵有反應. 但是如果你想對所有/很多個鍵有反應, 但是對不同的鍵處理方式稍微不同, 那麼應該不要加 detail, 而是傳參數給 callback. 見 "如何寫 callback".
    3. type 之前還可以加上條件 (modifier), 表示只有在這些條件成立的情況下, 發生 type 所描述的事件, 才會觸發 callback. 常用來修飾 Key 的 modifiers 有 Control, Shift, Alt, Meta; 常用來修飾 Button 的 modifiers 有 Double, Triple; 常用來修飾 Motion 的 modifiers 有 B1, B2, B3.
    4. 注意: <B1-Motion> 表示 "使用者在按著第一個滑鼠鍵不放的情況下, 移動了滑鼠", 屬於 <modifier-type> 的語法; 而 <Button-1> 則表示 "使用者現在按下了第一個滑鼠鍵", 屬於 <type-detail> 的語法.
    5. 可以把好幾個 events 寫成一串, 構成一個 multi-event sequence, 例如 <Key-s><Key-h><Key-o><Key-w> (可以簡寫成 <s><h><o><w>, 詳見手冊) 表示 "使用者按下了 s 鍵, 然後按 h 鍵, 再按 o, 再按 w" (不可以有其他事件穿插其中; 但是兩兩之間所隔的時間長短並無限制.) 這可以用來放一些 easter eggs 進你的程式, 增加趣味...
  6. Binding Tags: 詳見 bindtags(n) 或 perldoc Tk::bindtags
    1. 如果你仔細看 bind 的手冊, 會發覺其實 bind 不只可以作用在一個 widget 上, 也可以作用在一整個 widget class 上, 甚至可以作用在所有的 widgets 上.
    2. 事實上一個 event 可能會觸發不只一個 binding. 每個 widget 記載著一個 binding tags 的 list, 例如假設 .x.y 是一個 Button, 則它的 binding tag list 的內容通常是 .x.y Button . all 共四個元素. (注意: 在 perl/tk 中的順序是: Button .x.y . all) 一旦在一個 widget 內發生一個 event 時, tk 會把這個 widget 的 binding tags list 裡面的每個 tag 所對應到的 callback 逐一叫出來執行.
    3. 可以用 break 或 Tk->break 打斷這個過程. 最常用於限制 entry 內可以鍵入的字元. 在 tcl/tk 中, break 只能用在 bind 當句; 若寫成副程式則 tcl/tk 不認得. 在 perl/tk 中, 由於 class binding (如上例的 Button) 在 widget binding (如上例的 .x.y) 之前, 所以必須使用自己發明的 binding tag, 並放在最前面才有效, 詳見 perldoc Tk::bindtags 解釋為何 perl/tk 與 tcl/tk 有這點不同.
    4. (待續 ...)
  7. 其他重要事項
    1. after 後面只加時間參數, 不加 callback, 則表示完全不同的意思: "睡 ... 這麼久再醒過來繼續執行".
    2. 因為 tk 會把所有的 configure 設定集中起來以批次 (batch) 的方式執行, 所以並不是一執行完 configure 命令後就會生效. 如果你需要它立即生效, 必須呼叫 update 或 update idletasks. 詳見 update(n) 或 perldoc Tk::Widget
    3. 盡量不要給主視窗設定 binding, 因為它的 binding 會被所有的 children, grand-children 繼承, 造成一個事件同一個副程式被觸發許多次. 筆者只知道有一種情況值得這樣做: 要為你的程式加上 hot-key.