2021年9月22日 星期三

Python 內建 GUI 模組 tkinter 測試 (十五) : Progressbar 元件

今天繼續測試的 Progressbar (進度條) 元件. 

本系列之前的文章參考 :   


Progressbar 元件 (進度條) 用來顯示某個可能費時稍久的程序之執行進度, 避免使用者以為程式當掉了. 注意, 此元件僅在 ttk 中有, tk 中無此元件. 建立 Progressbar 元件之語法如下 : 

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



Progressbar 元件常用參數如下表 : 


 Progressbar 常用參數 說明
 orient 設定進度條方向=tk.HORIZONTAL (預設)/tk.VERTICAL
 length 設定進度條的長度 (px)
 mode 設定進度條模式="indeterminant" (預設不確定)/"determinate" (確定)
 maximum 設定進度的最大值 (預設 100)
 value 設定進度條的現值 (0~maximum)
 variable 與進度條綁定之類別變數名稱


orient 參數也可以使用字串 "horizontal" 與 "vertical". 不確定模式 (mode="indeterminant") 用於無法精確取得進度資訊時, 進度條會以設定的速度增值到 maximum 後又歸零重新增值, 如此周而復始直到呼叫 stop() 方法停止. 而 "determinant" 模式用於可取得程序進度資訊的情況, 可用此數值來更新 value 參數以更新進度指示器.  

Progressbar 元件常用方法如下表 : 


 Progressbar 常用方法 說明
 start([interval]) 開始進度條間隔 interval 時間 (預設 50ms) 之增值運作 (預設 1.0).
 stop() 停止 start() 開啟之進度條增值運作. 
 step([delta]) 使進度條數值增加 delta (預設 1.0), 但不會超過 maximum 值.


進度條必須呼叫 start() 才會開始增值, 預設是每 50 ms 增加 1.0 直到 100, 故預設情況下 5 秒後會到達預設最大值 100, 但也可以傳入 interval 更改每步的時間間隔, 或呼叫 step() 傳入 delta 修改每步的步幅 (預設 1.0).

預設的進度條是不確定模式 (mode="indeterminate"), 因此其值會從 0 每 50 ms 增加 1 直到 100, 然後又歸零, 周而復始直到呼叫 stop() 為止, 例如 :  


測試 1 : 預設的不確定模式進度條 [看原始碼]

import tkinter as tk
from tkinter import ttk

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

progressbar=ttk.Progressbar(win)
progressbar.pack()
ttk.Button(win, text="開始", command=progressbar.start).pack()     # 進度開始
ttk.Button(win, text="停止", command=progressbar.stop).pack()     # 進度停止 (歸零)
win.mainloop()

此例有兩個按鈕, 按下開始鈕會呼叫 Progressbar 物件的 start() 方法讓進度條開始定時增值; 按下停止鈕則會讓進度條歸零並停止, 結果如下 : 




可見預設不確定模式下, 進度條開始後跑到終點會歸零再繼續, 週而復始不斷重複. 

進度條預設寬度是 100px, 可用 width 參數設定, 例如 : 


測試 2 : 用 length 參數設定進度條長度 [看原始碼]

import tkinter as tk
from tkinter import ttk

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

progressbar=ttk.Progressbar(win, length=250)   # 設定進度條長度
progressbar.pack()
ttk.Button(win, text="開始", command=progressbar.start).pack()
ttk.Button(win, text="停止", command=progressbar.stop).pack()
win.mainloop()

此例添加 length 參數指定長度, 結果如下 : 




進度條預設是水平的, 但可用 orient 參數設定為垂直的, 例如 :


測試 3 : 用 orient 參數設定進度條方向 [看原始碼]

import tkinter as tk
from tkinter import ttk

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

progressbar1=ttk.Progressbar(win, orient="vertical")     # 設為垂直進度條
progressbar1.pack()
ttk.Button(win, text="開始", command=progressbar1.start).pack()
ttk.Button(win, text="停止", command=progressbar1.stop).pack()
win.mainloop()

此例以 orient 參數設定進度條方向為垂直, 結果如下 :




可見垂直的進度條是由下往上增長的. 

如果進度資訊是可取得或可控制的, 則應將 mode 參數設定為 "determinate", 例如 : 


測試 4 : 用 mode 參數將進度條設為 "determinate" 模式 [看原始碼]

import tkinter as tk
from tkinter import ttk

def get_current_value():                  # 傳回目前進度值之顯示字串
    return "目前進度 : " + str(progressbar["value"]) + "%"
def start():
    if progressbar["value"] < progressbar["maximum"]:    # 小於最大值持續增量
        progressbar["value"] += 10               # 進度增量 10
        label["text"]=get_current_value()     # 顯示目前值
    else:
        label["text"]="已完成!"
        
def stop():
    progressbar.stop()                              # 進度條歸零
    label["text"]=get_current_value()     # 顯示目前值

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

progressbar=ttk.Progressbar(win, length=280, mode="determinate")   # 確定模式
progressbar.pack()
ttk.Button(win, text="開始", command=start).pack()
ttk.Button(win, text="停止", command=stop).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例用 mode 參數將進度條設為確定模式, 按下 Start 按鈕時呼叫自訂函式 start(), 先判斷目前值是否超過最大值 (預設 100), 是的話於 Label 顯示已完成, 否的話將進度條的 value 參數增量, 然後於 Label 顯示目前進度值字串, 結果如下 :




每按一次 Start 鈕進度就前進 10 %, 直到 100% 時就顯示完成. 注意, 如果 start() 裡面沒有用 if 判斷現值是否超過最大值的話, 進度值會一直增量下去, 不會受到 maximum 的限制. 

上面範例必須一次一次按開始鈕進度才會往前走, 下面範例使用迴圈來控制進度自動增量, 但因電腦運算速度很快, 必須使用內建模組 time 的 sleep() 函式來控制執行速度, 例如 :


測試 5 : 使用迴圈來控制進度自動增量 [看原始碼]

import tkinter as tk
from tkinter import ttk
import time     

def get_current_value():
    return f"目前進度 : {progressbar['value']}%"      # 改用 f 字串
def start():    
    for i in range(progressbar["maximum"]):
        if progressbar["value"] < progressbar["maximum"]:
            progressbar["value"] += 1
            label["text"]=get_current_value()
            win.update()             # 更新視窗內容
            time.sleep(0.1)          # 休眠 0.1 秒
        else:
            label["text"]="已完成!"
        
def stop():
    progressbar.stop()
    label["text"]=get_current_value()

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

progressbar=ttk.Progressbar(win, length=250, mode="determinate")   # 確定模式
progressbar.pack()
ttk.Button(win, text="開始", command=start).pack()
ttk.Button(win, text="停止", command=stop).pack()
label=ttk.Label(win)
label.pack()
win.mainloop()

此例與上例的主要差別是在自訂函式 start() 中把 if else 放在 for 迴圈內, 由迴圈控制進度自動增量, 因此 Start 鈕只要按一次即可. 其次是引入 time 模組, 利用其 sleep() 函式讓每個迴圈休眠 0.1 秒, 避免進度跑太快. 由於使用了 time.sleep(), 所以每次迴圈都必須呼叫視窗物件的 update() 方法更新視窗內容, 否則不會動態顯示每個迴圈的結果, 而是全部執行完直接顯示 "已完成", 中間過程會看不到, 結果如下 :




另外本例的自訂函式 get_current_value() 之傳回值也改用較新的 f 字串表示. 

參考 :


沒有留言 :