2021年9月15日 星期三

Python 內建 GUI 模組 tkinter 測試 (十三) : Spinbox 元件

本篇測試 tkinter 的 Spinbox (數值微調器) 元件. 

本系列之前的文章參考 :   


Spinbox 元件用來在指定的範圍內選取數值, 事實上它是由一個 Entry 元件與兩個按鈕組成的複合元件, 按上下鈕可讓 Entry 元件中的數值以既定的步階上下變動, 在 tk 與 ttk 中都有提供此元件. 建立 Spinbox 元件之語法如下 : 

Spinbox(父容器, **參數列)



Spinbox 元件常用參數如下表 : 


 Spinbox 常用參數 說明
 from_ Spinbox之起始值 (無預設值, 必須設定)
 to  Spinbox之結束值 (無預設值, 必須設定)
 increment 按 Spinbox 上下鈕時數值的增減步階值 (預紹 1 或 1 格)
 format 數值格式字串, 例如 "%10.4f" 表共 10 個數字浮點數含 4 位小數
 command 按 Spinbox 上下鈕時要呼叫之函式名稱
 textvariable 與 Spinbox 綁定之類別變數名稱 (IntVar, DoubleVar, 或 StringVar)
 width Spinbox 數值顯示區的寬度 (字元數, 預設 20 個字元)
 wrap 按向上鈕到最大值時是否變最小值, 反之亦然=True/False (預設)
 values 指定非數值選項的 tuple 或 list (比 from_, to, 與 increment 優先)
 state 設定 Spinbox 狀態=NORMAL (預設)/DISABLED/READONLY


與 Scale 元件不同的是, Spinbox 的參數 from_ 與 to 無預設值, 故必須自行設定才能使用, 建立物件時不指定雖然不會出現錯誤, 但按上下鈕沒有反應, 無法使用. 

Spinbox 元件常用方法如下表 : 


 Spinbox 常用方法 說明
 get() 傳回 Spinbox 的現值 (字串, 即使內容是數值)
 invoke(element) 模擬按上下鍵動作 (element="buttonup"/"buttondown") (ttk 不支援)


Spinbox 元件若沒有設定 from_ 與 to 參數雖然會顯示元件, 但沒有作用, 例如 : 


測試 1 : 未設定 from_ 與 to 參數沒有作用 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win)     # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win)   # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例分別建立 tk 與 ttk 版的 Spinbox 物件, 但在建立物件時未傳入 from_ 與 to 參數, 建立後也未使用 [] 運算子或 config() 方法設定, 雖然可順利執行, 但是按上下鍵並無作用, 結果如下 :




可見下方 ttk 版的上下鍵按鈕較大較寬, 按 tk 版的上下鈕無反應, 按 ttk 版的則固定顯示 0, 就算在 Entry 欄位輸入數值後按上下鍵也無作用. Entry 輸入的寬度預設 20 個字元. 數值型 Spinbox 要正常可用需傳入 from_ 與 to 兩個參數, 例如 : 


測試 2 : 數值型 Spinbox 需設定 from_, to 與 width 參數 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, width=10)      # 建立 tk 版 Spinbox
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, width=10)    # 建立 ttk 版 Spinbox
spinbox_ttk.pack()
win.mainloop()

此例在建立 Spinbox 物件時傳入數值範圍參數 from_ 與 to 使其有作用, 這樣按上下鍵時數字就會以預設的 increment=1 上下跳動. 另外還用 width 參數設定 Entry 輸入欄位寬度為 10 個字元 (預設 20), 結果如下 : 




tk 版的 Spinbox 預設顯示 to 參數的數值, ttk 版的預設不顯示, 但按上下鍵就會顯示 to 參數之值. 按上下鍵時預設增減量為 1, 但可用 increment 參數設定, 例如 :


測試 3 : 用 increment 參數設定增減步階值 (正=順向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=2)     # 順向增減步階值=2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=2)   # 順向增減步階值=2
spinbox_ttk.pack()
win.mainloop()

此例以 increment 參數設定增減步階為 2, 故按上下鍵時每次上下跳兩格, 按向上鍵會按 -10, -8, -6, ... 次序變化 (都顯示偶數值), 結果如下 : 




上例 increment 為正, 表示按向上鍵數值增加, 按向下鍵數值減少 (順向); 但 increment 也可以設為負值, 這樣方向與數值就會變成逆向關係, 即按向上鍵數值減少, 按向下鍵數值增加, 例如 : 


測試 4 : 用 increment 參數設定增減步階值 (負=逆向) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, increment=-2)      # 逆向增減步階值=-2
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, increment=-2)    # 逆向增減步階值=-2
spinbox_ttk.pack()
win.mainloop()

此例將 increment 設為 -2, 因此一開始只能按向下鍵 (因為按向上鍵就超出範圍了), 其值會從 -10 變 -8, -6, ... 因為減少 -2 即加 2. 然後按向上鍵數值反而減少, 因為加 -2 即減 2 之意, 結果如下 : 




不過這種逆向邏輯與常識相反, 故 increment 還是用正值為宜. 

下面範例測試 wrap 參數, 此參數用來設定當按上下鍵到達數值上下限 (即 from_ 與 to 所限定之邊界) 時是否要反折到另一邊, 設為 True 的話值會從 from_ 變成 to, 或從 to 變成 from_, 亦即數值變化變成一個環, 而非碰頂後停住不動. 此 wrap 參數預設值為 False (不反折), 即到達上下限時再按值也不變, 例如 : 


測試 5 : 用 wrap 參數設定到達上下限時數值是否要反折 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, wrap=True)       # 到達上下限時反折
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, wrap=True)     # 到達上下限時反折
spinbox_ttk.pack()
win.mainloop()

此例將 wrap 參數設為 True, 當按向上鍵到達 10 時, 再按就變成 -10, 即反折到 to 去; 同理按向下鍵到達 -10 時, 再按一次就變成 10, 即反折到 from 去, 結果如下 : 




下面範例測試 format 參數, 此參數值為一個表示數值的格式字串, 用法參考 print() 函式, 例如 "%10.4f" 表示數值格式為最多 10 個數字, 其中有 4 位小數, 亦即整數部分最多 6 位數, 例如 :


測試 6 : 用 format 參數設定數值格式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_selection():
    result_label["text"]=var.get()

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_tk["format"]="%5.4f"               # 設定數值格式為 4 位小數, 1 位整數
spinbox_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-1, to=1, increment=0.0005)
spinbox_ttk.config(format="%5.4f")      # 設定數值格式為 4 位小數, 1 位整數
spinbox_ttk.pack()
win.mainloop()

此例使用 [] 與 config() 兩種方式設定 Spinbox 物件之 format 參數, "%5.4f" 表示小數為 4 位數, 故整數為 1 位數, 這是配合 from_=-1, to=1, 與 increment=0.0005 之設定, 結果如下 :




Spinbox 元件的 command 參數用來將上下鍵的按鈕事件綁定到一個事件處理函式, 在此函式中可呼叫 Spinbox 物件的 get() 方法以取得微調器之現值, 例如 : 


測試 7 : 用 get() 方法取得微調器之值 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()              # 取得微調器之值
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())    # 取得微調器之值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10, command=show_value_tk)
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, command=show_value_ttk)
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例添加了 Label 元件來顯示 Spinbox 的現值, Spinbox 則用 command 參數設定了按鈕事件處理函式, 當按上下鍵時會呼叫 Spinbox 的 get() 方法傳回現值, 用來設定 Label 的文字, tk 版使用 [] 運算子, 而 ttk 版則使用 config(), 兩種方法都可以, 結果如下 : 




注意, 即使微調器的內容是數值, 但 get() 方法傳回的是個字串. 除了值接呼叫 Spinbox 物件的 get() 取得其值外, 也可以用 textvariable 參數綁定一個類別變數來存取微調器, 這種方式除了可以c取得 Spinbox 之值, 也可以用來設定初始值, 例如 : 


測試 8 : 用 textvariable 參數綁定類別變數以動態存取微調器 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk.config(text=var_tk.get())    # 透過類別變數取得 Spinbox 現值
def show_value_ttk():
    label_ttk["text"]=var_ttk.get()            # 透過類別變數取得 Spinbox 現值

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

var_tk=tk.StringVar()
var_tk.set(5)                    # 設定微調器初始值
spinbox_tk=tk.Spinbox(win,
                      from_=-10,
                      to=10,
                      textvariable=var_tk,    
                      command=show_value_tk)    # 綁定類別變數
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()

var_ttk=tk.StringVar()
var_ttk.set(7)                    # 設定微調器初始值
spinbox_ttk=ttk.Spinbox(win,
                        from_=-10,
                        to=10,
                        textvariable=var_ttk,    
                        command=show_value_ttk)    # 綁定類別變數
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 Spinbox 元件經由 textvariable 參數綁定了類別變數, 呼叫類別變數的 set() 方法即可設定 Spinbox 的初始值, 呼叫 get() 方法則可取得微調器現值, 結果如下 : 




原本 ttk 的微調器一開始預設是不顯示值的 (tk 則是預設顯示 from_ 之值), 用類別變數的 set() 設定預設值後, 程式一執行就會顯示初始值了. 

除了作為數值微調器外, Spinbox 也可以當選項微調器使用, 意即 Entry 輸入欄位內容是選項字串而非數值, 按上下鈕時選項會上下跳動, 這時 from_, to 與 increment 這三個參數失效 (被 override), 改由 values 參數 (選項字串之 list 或 tuple) 決定微調器的值, 例如 : 


測試 9 : 用 values 參數設定選項 (字串) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")     # 使用元組
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)       # 設定選項
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=["張無忌", "趙敏", "楊不悔", "朱九貞", "周芷若"]              # 使用串列
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, values=values_ttk)       # 設定選項
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例分別使用元組與串列儲存選項, 然後設定為 values 參數之值, 結果如下 :




按上下鍵即可挑選選項作為 Spinbox 之值, 相當於 Radiobutton 的單選功能. 除了字串選項外, values 當然也可以用數值串列, 這樣按上下鍵時就會在這些值之間跳動, 例如 :


測試 10 : 用 values 參數設定選項 (數值) [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=(1, 3, 5, 7, 9, 11, 13, 15)       # 數值選項
spinbox_tk=tk.Spinbox(win, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
label_tk=tk.Label(win)
label_tk.pack()
values_ttk=[1.2, 4.3, 8.7, 9.3, 11.1, 13.2, 34.1, 78.9]     # 數值選項
spinbox_ttk=ttk.Spinbox(win, values=values_ttk)
spinbox_ttk["command"]=show_value_ttk
spinbox_ttk.pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

結果如下 :




Spinbox 物件的 invoke() 方法可用來模擬按上鍵 (傳入 "buttomup") 與按下鍵 (傳入 "buttondown") 的動作, 但此方法僅 tk 可用, ttk 不支援, 例如 : 


測試 11 : 呼叫 invoke() 方法模擬按上下鍵之動作 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk.config(text=spinbox_ttk.get())
def move_up_tk():
    spinbox_tk.invoke("buttonup")             # 模擬按向上鍵動作
def move_down_tk():
    spinbox_tk.invoke("buttondown")        # 模擬按向下鍵動作

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

values_tk=("岳不群", "左冷禪", "定逸師太", "任盈盈", "令狐沖")
spinbox_tk=tk.Spinbox(win, from_=-10, to=10, values=values_tk)
spinbox_tk["command"]=show_value_tk
spinbox_tk.pack()
tk.Button(win, text="上", command=move_up_tk).pack()
tk.Button(win, text="下", command=move_down_tk).pack()
label_tk=tk.Label(win)
label_tk.pack()
win.mainloop()

此例以兩個 Button 元件來模擬 Spinbox 的上下鍵, 按下時會呼叫 invoke(), 結果如下 :




最後來測試 state 參數, 此參數預設為 "normal" 或 tk.NORMAL, 此狀態下 Spinbox 的 Entry 除了可按上下鍵來自動填入跳動的數值或選項外, 也可以直接輸入內容, 這時呼叫 get() 取值時會是輸入之內容. 如果設為 "readonly" 或 tk.READONLY, 則 Entry 欄位無法直接輸入內容, 只能按上下鍵改變內容, 如果設為 "disabled" 或 tk.DISABED 則連上下鍵也動不了, 被禁能了, 例如 : 


測試 12 : 用 state 參數設定 Spinbox 之狀態 [看原始碼]

import tkinter as tk
from tkinter import ttk

def show_value_tk():
    label_tk["text"]=spinbox_tk.get()
def show_value_ttk():
    label_ttk["text"]=spinbox_ttk.get()    

win=tk.Tk()                                                      
win.title("tkinter GUI 測試")
win.geometry("300x200")

spinbox_tk=tk.Spinbox(win, from_=-10, to=10)       # 預設 NORMAL
spinbox_tk.pack()
tk.Button(win, text="確定", command=show_value_tk).pack()            
label_tk=tk.Label(win)
label_tk.pack()
spinbox_ttk=ttk.Spinbox(win, from_=-10, to=10, state="readonly")    # 設為唯讀狀態
spinbox_ttk.pack()
ttk.Button(win, text="確定", command=show_value_ttk).pack()
label_ttk=ttk.Label(win)
label_ttk.pack()
win.mainloop()

此例 tk 版的微調器預設為 NORMAL 狀態, 故既可按上下鍵改變內容, 也可以直接在 Entry 中輸入內容, 按下確定鈕呼叫 get() 方法將取得輸入之值. 而 ttk 版的 state 被設為 "readonly", 故無法於 Entry 中直接輸入內容, 只能按上下鍵, 結果如下 : 




可見 tk 版的 Spinbox 若按上下鍵應取得整數, 但直接輸入浮點數按確定即取得輸入之浮點數. 

沒有留言 :