Tk 簡介


為什麼學 Tk?

Tk 是一個 GUI 函式庫, 你可以用它來為你的程式加上 圖形使用者介面 (Graphical User Interface), 讓你的程式提供選單, 按鈕, 拉棒, ... 等等方便使用者以滑鼠操作的功能。

學 GUI 函式庫並不是件最熱門最酷的事情; 而且就算已經決定要學 GUI 函式庫, 也還有很多其他更流行的函式庫, 例如 VB 或 VC++ 的 Microsoft Foundation Class, Java 的 AWT 或 Swing, ... 等等。 Tk 比起上述各種函式庫, 要稍微不方便些: 你寫的 Tk 程式, 可以讓使用者以滑鼠操作; 但是你 -- 程式設計師 -- 自己寫程式的過程, 卻必須老老實實地開一個文字檔案編輯器 (例如 nanovim) 一列一列敲進去。 市面上流行的其他函式庫, 則多數都有更簡單的介面, 方便程式設計師用拉選單的方式畫出圖形介面。

那麼為什麼要學 Tk 呢? 如果你在乎的是短暫的方便, 是眼前的流行, 或許並不適合學 Tk; 但如果你在乎的是長遠的學習投資, Tk 就非常值得考慮。

  1. 它可以與眾多語言配合使用, 例如 tcl, perl, ruby, python, guile, java, c, c++, ...
  2. 上述語言, 都有跨作業平臺的版本, 只要選對編譯器, 你寫的程式不論在 MS Windows, Mac, Linux, *BSD, ... 任何作業平臺下, 都可以執行。
  3. 自己用手寫的程式, 比較容易理解/維護/撿片斷作他用。
  4. 就像製作 html 一樣, 學語法比背選單麻煩, 但也更能夠長久保值, 不因為軟體退流行而失去價值。
  5. Tk 是 自由軟體, 不會因為廠商停止支援而失去活力。

[組合軟體]

程式語言與作業系統的市場是多變的, 今天最流行的語言或 OS, 十年後可能已成為歷史。 如果你希望未來二者不論如何更迭興衰, 現在所學的東在西廿年之後仍舊還能有一些用處; 如果你希望花很多時間所學的東西, 可以和最多不同的軟體元件溝通, 發揮相乘的效果, 那麼 Tk 就非常值得考慮。

Tk 一樣具有活用組合特性的類似 GUI 函式庫其實還有很多, 例如 wxWidgets, gtk+, qt, ... 等等, 都是自由軟體, 也因此理論上都可以跨作業平臺並與多種語言搭配。 可是網路上似乎找不到較完整客觀的比較文章。 筆者選擇 Tk, 主要原因還是個人經驗與偏好, 同時也因為個人最拿手的是 perl, 而目前與 perl 搭配較好的 GUI 函式庫似乎是 Tk。

本講義的編排

這份講義假設你略熟 tclperl 兩種 Scripting Languages 其中一種。

筆者堅守程式設計師懶惰的美德, 希望只寫一份講義, 就可以適用於不同語言版本的 Tk; 希望這份講義對於來自 tcl 的讀者與來自 perl 的讀者一樣有參考價值。 甚至希望將來有朋友可以共同參與修改這份講義, 只需要為它增加 python 版 / ruby 版 / java 版 / ... 的程式範例, 及少數幾個章節, 馬上就變成其他語言的 Tk 學習手冊, 這樣不是很帥嗎?

為此, 講義主體以觀念為主, 盡量適用於任何版本; 不同語言的 Tk 範例程式則放在不同的目錄下。 講義當中遇有必須針對不同版本解釋之處, 就以顏色區分。 目前有以下三個版本, 範例程式分置於不同的子目錄底下:

  1. 最原始的 tcl 版
  2. 目前較成熟, 由 Nick Ing-Simmons 所移植的 perl-Tk; 此處稱之為 nis 版
  3. 非常有潛力的 Tcl::Tk 模組, 原始作者是 Malcolm Beattie; 這兩年則由 Vadim Konovalov 積極更新; 此處稱之為 mbvk 版

pl/ 目錄底下的程式, 則是更有趣 (也更複雜) 的 "nis/mbvk 混合版"。 目前函式庫本身, 及筆者所寫的範例程式都不成熟, 暫時不推薦 學習這種混合風格。 (花格子背景的段落)

所以總結地說, 遇到有顏色的部分, 請只挑你有興趣的版本來看; 但有時候即便是沒有顏色的部分, 也可能因為講義太舊還沒有更新, 內容其實與你無關。 此外, 如果範例程式的連結失效, 或許把路徑當中的 "pl/" 改成 "nis/" 就可以找到舊版; 當然, 這就只適用於 nis 版。 造成困擾, 抱歉啦 ^_^|||。

取得 Tk

您想學的是...?

  1. Tcl/Tk
  2. NIS 版的 perl-Tk
  3. MBVK 版的 Tcl::Tk for perl

牛刀小試

我們先看三個簡單的範例程式:

hello 程式畫面 add 程式畫面 cfglisting
tcl 版 v v v
nis 版 v v v
nis/mbvk 混合版 v v v
mbvk 版 v v v

執行方式:

Tk 程式所產生的畫面, 由許多 widgets (姑且譯為 視窗元件) 所構成。 例如 hello 的畫面, 只有 1 個明顯的 widget; 而 add 的畫面, 則明顯地可看到 5 個 widgets。

每個 widget, 分屬於不同的 widget class (姑且譯為 元件類別)。 例如 label 就是一個純為顯示資訊用, 不直接接受使用者輸入的類別。 在 hello 的畫面上, 有 1 個 label 元件; 在 add 的畫面上, 則有 2 個 label 元件 -- 固定不變的 "+" 號, 與最右邊的加法答案。 在 hello 的畫面上, 還有 2 個 entry 元件, 與一個 button 元件 (那個 "=") 。 前者用來輸入字串; 後者用來觸發命令。 如何查 label 這個 widget class 的手冊呢?

沒關係, 不需要從頭細讀到尾, 現在只是先熟悉一下如何查手冊。

事實上每個 tk 程式的畫面, 還有一個最基本的 widget -- 最外圍, 包含其他一切 widgets 的東東。 也就是說 hello 其實有兩個 widgets; 而 add 則有六個。 最外面這個 widget 屬於 toplevel 類別。 複雜的程式, 可以有好幾個 toplevel 的 widgets; 不過我們的範例都只會用到一個 toplevel widget。 這個內建的 toplevel widget 有個名字, 叫做 .在 nis 版中, 程式開始要呼叫 MainWindow->new() 產生一個 toplevel widget; 在其他版本中, . 本來就存在, 不必手動產生。 Q: 請查一下這個 widget class 的手冊。 注意: 在 nis 版中, 應查 MainWindow 的手冊。

裝扮你的 widgets

Tk 的視窗元件, 內定都採用醜醜的灰色, 土土的 helvetica 字形。 如 hello 程式所見, 我們可以用 -fg 設定前景顏色, 用 -bg 設定背景顏色, 用 -bd 改變框框的寬度, ... 等等。 這些描述 widgets 的屬性, 稱為 configuration options

想改變 widget 的外觀, 並不限於只在它誕生時設定。 程式當中任何時候, 都可以呼叫 configure 函數來修改 widget 的 configuration option。 例如 add 程式中, 使用者每次按下按鈕要計算加法, 就必須改變最右邊的 label 的顯示字串:

反過來說, 想查詢視窗元件某項屬性設定, 可以呼叫 cget 函數。 例如想查詢 .greetings 的背景顏色, 可以這樣寫:

如何稱呼一個 widget? 在 mvbk 版裡面稱呼方式和 tcl 版一樣 -- 用一個 "句點開頭的字串"。 但是要將一個 widget (例如 ".greetings 拿來當做物件使用 (例如對它呼叫 configure 或 cget), 不能只寫名字 (畢竟 ".greetings" 只是一個字串, 總不能在後面放箭頭吧), 而要用 widget 函數將名字轉成物件, 例如寫成: widget(".greetings")

還有那些 c.o. 可以修改, 讓 Tk 程式變得更美觀呢? 不同類別的 widget classes, 有著不同的 c.o., 所以理論上應針對你想修改的 widget class 去查手冊。 但其實也有不少 c.o. 對不同的 widget classes 一樣都有效。 例如上述的字形/前景顏色/背景顏色等等, 可以適用於 label, entry, button 等等 widget classes。 這些 「所有 widget classes 共同的 c.o.」 如果重複分散寫在各個 widget class 自己的手冊, 非常浪費, 所以 Tk 將它們合併寫在同一篇手冊 options(n) 當中。 馬上查查看有那些屬性可以更改吧: (注意 option 後面要加 s, 不然會查到另外一篇手冊!)

請特別看看 -relief 這個常用 c.o. 是做什麼用的? 還有其他那些值可以變化? 提示: 把框框的寬度 (-bd) 調得很大 (例如 50) 然後照著手冊把 -relief 設定成不同的值, 觀察 tk 如何利用明暗製造眼睛的錯覺, 達到立體的效果。

請注意: label 上面的字串, 是由程式產生的, 所以算是 c.o., 可以用 configure/cget 兩函數改/查; 但是 entry 上面的字串, 是使用者直接輸入的, 不算是 c.o., 不能用 configure/cget 改/查。 這裡要用 entry 類別的 get 函數來查。 這函數是 entry 特有的, 並不適用於 label 或 button, 所以稱為 entry 類別的 widget command。 在 entry 類別的手冊 "WIDGET COMMAND" 一節中可以查到 get 的用法。

產生 + 打包 = 顯示

一個 widget 產生出來之後, 並不直接顯示, 還要用 pack 將它打包顯示在螢幕上:

例如 cfglisting 程式當中, 產生的 label 並未用 pack 打包, 所以沒有顯示。 打包的方向 (-side) 可以有 "top" "bottom" "left" "right" 四個方向。 至於 -fill, 建議永遠填 "both"

初始化 + 事件處理 = 互動介面

Tk 程式包含兩部分: 建立視窗元件與打包顯示的 initialization script, 及後續處理與使用者互動的 event handlers (處理事件的副程式) 。 例如 add 範例的 do_it 副程式, 就是一個 event handler。 在建立 "等號" 按鈕時, 我們就指定: 以後凡是使用者按下 "等號" 按鈕 (產生一個事件), do_it 就要跳出來處理。 注意此時傳給 -command 的參數, 是 副程式的入口, 是一個 位址; 這時候 並沒有 呼叫 do_it 副程式。 這裡只是要告訴 "等號" 按鈕, 以後有需要的時候, 要到那裡去找 do_it 的執行碼; 但現在根本還不到真正呼叫的時候。 現在連兩個數字都還沒有出現哩, 怎麼相加呢?

至於無法與使用者互動的 hello, 則只有 initialization script 而沒有 event handler, 所以這個程式要用 ^c 或 kill 手動終止, 或從選單把它 close 掉。

那麼沒有 event 發生的時候, 你的 Tk 程式在做什麼呢? 就在 event loop 裡面睡覺, 等待 event 發生嘍。 一旦發生 event, 就醒過來執行某個 event handler; 處理完了, 又回去睡覺。 tcl 版的 Tk 自動會進入 event loop, 不必特別呼叫; nis 版mbvk 版則需要在 initialization script 最後面加上一句 MainLoop(), 進入 event loop。 像 cfglisting 就是因為最後面少了這句, 所以印完資料程式就結束。

心得

為了寫一個小視窗就要打那麼多鍵, 真麻煩! 寫 tk 程式確實比目前常見的 visual programming 類工具囉嗦; 但正因為所有的程式碼都攤在陽光下, 所以維護比較容易。 (可用 grep, sed 等工具協助搜尋, 修改程式片斷.) 此外, 它提供了較多的彈性, 可以做一些 visual programming 不容易做的事情。

建議不要花力氣記函數的名稱及參數的用法。 常用的自然會記起來; 記不起來的, 表示不常用, 所以不重要, 需要用到時再查手冊就好了。 倒是專有名詞非常值得記, 因為查手冊時到處都會看到。 是否熟悉這些專有名詞, 將大大影你的響學習效率。

作業

[作業一圖示]

  1. 顯示一個 label, 它的外觀由兩個 entries 與一個 button 來控制。 從這兩個 entries 可以輸入不同的外框, 及外框的寬度; 按下 button 時即生效。 提示: 拿 add 來改。 又, 畫面上這個不藍不綠的顏色叫做 "cyan"。