簡單的 Widget Classes


recruit optexp optexp2
tcl 版 v v v
nis 版 v v v
nis/mbvk 混合版 - v v
mbvk 版 v v v

Widget 族譜

widget 之間, 有兩個重要的關係: 父與子, 主與僕。

從 widget 誕生的那一句話當中, 可以看出父子關係。 例如 optexp 裡面的這句話:

optexp 裡面, 所有 widgets 的族譜 決定了 costr 是 show 的 child, 或者說 show 是 costr 的 parent。 show 還有另外一個 child -- watchme。 同樣地, ctrl 有三個 children, 分別是 action, just 與 anchor。 其中 just 又有三個 children 至於 show 與 ctrl 共同的 parent "." (main window) 則是所有 widgets 的老祖宗。 右圖是這家子的族譜。

其實一個 widget 的全名, 應該連同它的祖宗八代完整寫出, 這稱為它的 pathname。 例如 "left" 是 "just" 的兒子, "just" 又是 "ctrl 的兒子, 所以 "left" 的 pathname 是 ".ctrl.just.left" 其中最前面的 "." 正是主視窗。 在 nis 版當中, pathname 比較少直接用到。 為了節省變數, 又為了反應 pathname, 建議模倣 pathname 的命名方式, 以一層層的 anonymous hash 來存放 widgets。

至於主僕關係, 則可以從 pack 那一句話當中看出。 打包句中的主角是 slave, 打包的目的地是 master。 平常最簡單的狀況, pack 句中不加任何 -in 的選項, 就表示打包動作以 parent 為目的地。 例如

這句話裡面, 並沒有用 -in 指明要打包到那裡去, 所以主角 .show.costr 會被打包到它的 parent 也就是 .show 裡面去。 此處 .show 是 master, .show.costr 是 slave。 初學者可以暫且如此想: parent 就是 master; child 就是 slave。 閱讀手冊時, 很有幫助。 至於程式當中出現 -in 的狀況 (也就是 parent 不等於 master, child 不等於 slave 的狀況), 本篇暫不討論。

Pack 打包規則: 永遠保留矩形空洞

Pack 的打包規則如下: 想像一開始 master widget M 是一個長方形, 很大的 cavity 空洞。 每次把一個 slave Si 給 pack 進 M 時, 就把 M 框框中剩下來的 cavity 的上下左右某側 分配給 Si, 而留下更小的一個長方形 cavity 給後來的 slaves: Si+1, Si+2, ... 例如 optexp2 裡面, 主視窗就是 M, 而它的 slaves S1, ... S4 則按照下列順序打包: bd, watchme, bg, text。

optexp2 的畫面。 注意右邊中間偏下的黃色空格 optexp2 的畫面上的 widgets 變小了, 黃色的底全漏了出來









至於 Si 分配到的這塊空間叫做它的 parcel, 不論它是否填滿整個 parcel, 其他 slave 都不會來搶用。 請將 optexp2 程式當中所有 -fill=>"both" 刪除。 執行程式時, 將視窗拉大, 就會看到如圖的狀況: 分配到每個 widget 的 parcel 沒有被填滿, 結果漏出了一大片主視窗黃色的底。 一般設計畫面不會希望漏底, 所以說平時打包幾乎都用 -fill=>"both" 這其實應該變成內定值才對。

簡單的 pack 方式: 用 frame 組織成許多層; 同一層朝同一個方向 對於初學者而言, optexp 的打包方式或許比較簡單。 先將版面切成幾大塊, 使得同一塊裡面的所有 widgets 很單純地由左到右, 或者很單純地由上到下並列。 如此一來, 打包時可以用一句話同時打包好幾個 slaves, 因為它們的 -side 都相同。 像這種 "一大塊" 的目的純粹是為了打包方便, 其實並不需要讓使用者看到, 採用的就是 frame 這種 widget class 。

建議初學者略過以下, 直接跳到下一節。

既然一個 slave widget S 所分配到的 parcel 可能比 S 要求的空間還要大, 那麼

  1. 如果 parcel 真的比較大, 那麼 S 是否應該向兩側或上下延伸, 佔滿整個 parcel 呢? 用 -fill 指定 (可以是 none, x, y, 或 both).
  2. 如果 parcel 真的比較大, 而且並未用 -fill 讓 S 完全填滿 parcel, 那麼 S 應該靠向 parcel 的那個角落呢? 用 -anchor 指定.
  3. 如果 master 有額外的空間, 那麼 S 分到的 parcel 是否應該跟著放大? 用 -expand 指定. (只要說 yes 或 no 就可以了, 不必說明是 x 方向或 y 方向. 因為若 -side 為左右, 則 y 方向的多餘空間當然會分給 S; 若 -side 為上下, 則 x 方向的多餘空間當然會分給 S.)
  4. 事實上可以用 -padx 與 -pady 要求 packer 在分配 parcel 給 S 時, 必須在 x 方向與 y 方向額外多留多少空間.

有關 expansion 的規則

  1. master 放大時, 先 pack 的 slaves 優先 expand; master 縮小時, 後 pack 的 slaves 最先消失.
  2. master 放大時, 額外的寬度由所有 "處於同一層", 且 -side 為左右的 slaves 平均吸收; 額外的高度由所有 "處於同一層", 且 -side 為上下的 slaves 平均吸收;
  3. 結論一: 通常只有工作空間 (例如繪圖軟體的畫布部分, editor 的文字視窗, 資料庫軟體的資料視窗) 用 -fill both -expand yes; 至於 menu bar 與 status bar 通常只用 -fill x (或 -fill both, 沒有差別) 而不用 expand.
  4. 結論二: 重要的 slave widgets 先 pack. 例如 menu bar 比 status bar 重要, status bar 可能比工作空間重要, 所以就按照這個順序 pack. 這樣在極端的情況下, 使用者還有機會可以做存檔等動作.

是非/單選/複選

Checkbutton 可以用來表達是非題, 通常與一個變數綁在一起 (用 -variable 指定): 使用者選取按鈕, 變數的值就變成 -onvalue 所指定的值; 使用者清除按鈕, 變數的值就變成 -offvalue 所指定的值。 反之, 程式改變變數的值, 按鈕也會跟著改變成 「選取」 或 「清除」 的狀態。 例如:

如此一來, 按鈕被選取時, 變數的值就自動變成 "night"; 被清除時, 變數的值就自動變成 "day"。 反之亦然。 (至於 -text 影響的是顯示在螢幕上的字串, 與變數的值無關。) 你也可以從程式裡面按按鈕, 像這樣:

這裡的 toggle 函數是翻轉按鈕的狀態 -- 如果原來是選取的狀態, 就將之清除; 如果原來是清除的狀態, 就將之選取起來。 另外還有 select 可以 「選取」; deselect 可以 「清除」。

Radiobutton 很少單獨使用。 通常都是好幾個 radiobuttons 指定相同的 -variable, 因而與同一個變數綁在一起, 構成一個單選題。 每個 radiobutton 指定的 -value 就是變數對應到該按鈕的值。 例如 recruit 例中, 使用者如果按下左邊的 M 鍵, 變數 sex 的值就自動變成 "boy"; 如果按下右邊的 F 鍵, 變數 sex 的值就自動變成 "girl"。 還是一樣, -text 指定的是畫面上顯示的字串, 與變數的值無關。

Listbox 也可以用來表達選擇題。 你可以呼叫 insert 函數把選擇題的項目逐一加入 listbox; 呼叫 curselection 函數查詢目前被圈選起來的項目; 還可以呼叫 selectionSet 函數, 直接從程式裡面圈選某些項目。

至於想表達複選題, 至少有兩種方法:

  1. 使用 listbox, 將 -selectmode 設成 extended (詳見手冊)
  2. 把好幾個彼此獨立運作的 checkbuttons 並列在一起。

調整數值大小

Scale 可以讓使用者以拖曳的方式控制某數量的大小, 這也是吸引筆者學 Tk 的最重要工具之一 -- 其他 widgets 可以做的事, 其實在文字模式底下用 ncurses 其實也都可以做得到; 但文字模式底下的拉棒確實沒有 scale 好用。

首先在建立 widget 時, 用 -from 與 -to 設定數字範圍, 並且用 -orient 指定擺設的方向 (垂直或水平)。 為了讓你的程式對使用者的拖曳立即反應, 還要用 -command 指定一個副程式來處理拖曳事件。 這個副程式會收到一個參數, 也就是 scale 目前的數值。 mbvk 版目前沒有這個功能, 必須在副程式裡面自己呼叫 get 函數 (下詳)。

有時候我們並不需要 event handler (就是 -command 的參數啦) 立即反應, 而是稍後在程式其他部分回頭檢查 scale 目前的值。 此時可以呼叫 get 函數, 取得 scale 目前的數值。 另外 (通常在程式一開始時) 也可以呼叫 set 函數, 從程式裡面把 scale 拖到指定的位置。

如果你要控制的數值, 它的變化範圍很大或很小, 可能需要用 -resolution 選項來改變拖曳時數字變化的單位。 (內定為 1)

長度的單位

讀者是否注意到, 在兩類場合下, Tk 表達長度的單位不太一樣?

諸如 label, entry, button 等等顯示字串的 widgets, 指定寬度時, 以字元的個數為單位; 但是像 scale 的長度, 或是將來要看的 canvas (畫布), 則 以螢幕的點數為單位

事實上後者還可以用 c (公分), m (公厘), i (英吋), p (1/72 英吋) 等單位計量。 例如 -length=>"2.5i" 指定長度為 2.5 英吋。

即便是 label, entry, button, 也未必都以字元為單位。 這些 widgets 上面可以用 -bitmap 貼黑白圖案, 或用 -image 貼彩色圖案。 遇到這種情況就變得與 scale 的 -length 一樣, 以螢幕的點數為單位, 或是要在數字後面明確標示出長度單位。 詳見 Tk_GetPixels(3)

Tk 手冊導讀

至此, 我們已學過 label, entry, button, frame, checkbutton, radiobutton, listbox, scale 等等類別。 這些 widget classes 既是類別的名稱, 也是用以產生 widgets 的命令 (例如呼叫 label 就會產生一個標籤元件), 所以在手冊當中, 又稱之為 widget commands

以下舊資料尚未整理


  1. 重要名詞:
    1. Class command: 用來建立新 widgets 的命令, 例如 wish 內的 label, button, entry, frame, ... 或 perl 內的 Label, Button, Entry, Frame, ...
    2. Widget command: 每個 widget 誕生後, 透過它可以下的命令, 例如每類 widget class 都支援的 configure, cget; 按鈕類 widget classes 共有的 flash, invoke; 又如 scale 特有的 identify 等.
  2. 心得: tk 內的元件與功能太多了, 常常令人頭昏, 連手冊都不知道要從那裡查起. 應該先把心裡想做的事歸類, 想清楚它到底是什麼東東:
    1. class command: 製造新的元件
    2. configuration option: 改變一個元件的外觀或特性
    3. widget command: 對一個元件做一些動作, 而這些動作是 configuration option 無法做到的
    4. geometry manager 的 option: 調整小元件在大元件之中的擺設方式
    再去查適當的手冊, 最後用適當語法來表達.
  3. 許多 widgets 都有的屬性: 除了 -relief, -bd, -fg, -bg 等, 還有以下常用屬性. 注意: 並不是每個 widget class 都有這些屬性, 但是只要有這些屬性的 widget class, 它們都不會有其他不一同的意義. 詳見 options(n) 或 perldoc Tk::options
    1. -justify 字串要靠那一側對齊
    2. -image 與 -bitmap
    3. -cursor: 滑鼠的圖案
    4. -font: 字形
    5. -anchor: 視窗內的字串或圖案要放在那一角
    6. -textvariable: 視窗內顯示的字串要隨時反應這個變數的值. Perl 使用者請注意: 傳進去的參數是一個純量變數的 reference, 不是變數的名字, 也不必非得是一個全域變數不可. 見 骨架程式 當中的 $main->{status}{filename}.
  4. 要取得 widget 資訊, 除了查看 options 手冊, 用 cget 取得之外, 另外一個可能要看的手冊是 winfo(n) 或 perldoc Tk::Widget. 在 wish 中是用 winfo 這個命令; 在 perl 中提供的則是所有 widget classes 共同支援的 w.c. 可以取得的資訊有: 這個 widget 的 parent 是誰, 它有那些 children, 它的大小及位置 (geometry), ... 等等.
  5. tcl/tk 與 perl/tk 使用者: 不要急著自己寫複雜的 widgets. 除了 "骨架程式" 當中提到的 tk_messageBox, tk_getOpenFile, tk_getSaveFile, tk_chooseColor 之外, 還可以考慮使用 tix 內的 mega widgets.

作業

  1. recruit 程式裡面, 使用的 widgets 似乎不甚合理, 只是運氣好恰巧可用而已。 如果工作時段有早, 中, 大夜三班, 該怎麼辦? 請改用 checkbutton 選取性別; 改用 radiobutton 選取工作時段, 並且將班別改成早/中/大夜三班。