2021年7月14日 星期三

Python 內建 GUI 模組 tkinter 測試 (四) : 基本用法複習整理

我在 2016 年中開始學習 Python 內建的 GUI 模組 tkinter, 才整理了一篇入門筆記就停擺了. 到了2018 年想改寫語音處理程式再次拾起, 但也只再寫了兩篇筆記又停下來. 最近為了工作上方便管理資料庫, 計畫要寫個 GUI 介面搭配網路爬蟲來使工作自動化, tkinter 的學習經過了五年還停滯不前實在說不過去, 所以想趁此機會把 tkinter 一口氣學完, 故先複習之前的筆記並做個摘要整理. 

本系列之前的文章參考 :

Python 內建 GUI 模組 tkinter 測試 (一) : 建立視窗
Python 內建 GUI 模組 tkinter 測試 (二) : 對話框
Python 內建 GUI 模組 tkinter 測試 (三) : 版面管理員

tkinter 的說明文件參考 :

https://pythonspot.com/en/tkinter/
Graphical User Interfaces with Tk

茲將前三篇筆記摘要整理如下 : 


1. 匯入模組 :

這幾年因為機器學習的推波助瀾, Python 3 現在已是主流, 所以學習 GUI 套件應該改用 Python 3 的 tkinter 模組 (注意是小寫的 t), 而非 Python 2 的 Tkinter 模組 (注意是大寫的 T). 使用 tkinter 模組通常即可開發一般的 GUI 程式, 但若要使用更進階的元件也可以同時匯入其子模組 ttk : 

>>> import tkinter as tk                     # 匯入 tkinter 並取簡名 tk
>>> from tkinter import ttk               # 匯入 tkinter 的子模組 ttk
>>> tk  
<module 'tkinter' from 'C:\\Python37\\lib\\tkinter\\__init__.py'>
>>> ttk   
<module 'tkinter.ttk' from 'C:\\Python37\\lib\\tkinter\\ttk.py'>

原始版的 tkinter 提供了 19 種視窗元件 (widget, 或稱控制項), 其外觀依賴於作業系統, 為了美化視窗元件外觀與提供更多好用元件, tkinter 另外發展了主題版 (Themed tkinter) 的子模組 ttk, 它提供了 18 種外觀美化的主題元件 (其中 12 種與 tkinter 相同), 合計有 25 種元件可用 :  


 tkinter 元件 ttk 元件 說明
 1. Label 1. Label 標籤文字
 2. Button 2. Button 按鈕
 3. Radiobutton 3. Radiobutton 單選圓鈕
 4. Checkbutton 4. Checkbutton 核取方塊 (多選)
 5. Entry 5. Entry 文字欄位 (單行)
 6. Text  文字區域 (多行)
 7. Scrollbar 6. Scrollbar 捲軸
 8. Listbox  清單方塊
 9. Spinbox 7. Spinbox 數值微調器
 10. Scale 8. Scale 尺規
 11. Frame 9. Frame 框架
 12. LabelFrame 10. LabelFrame 標籤框架
 13. Toplevel  頂層視窗
 14. PanedWindow 11.PanedWindow 視窗面板
 15. Menu  選單
 16. OptionMenu  功能表
 17. Menubutton 12. MenuButton 選單按鈕
 18. Canvas  畫布
 19. Message  訊息 (可多行)
  13. Combobox 下拉式選單
  14. Notebook 頁籤面板
  15. Progressbar 進度條
  16. Separator 分隔線
  17. Sizegrip 尺寸調整器
  18. Treeview 樹狀表格


2. 視窗程式基本架構 :    

tkinter 模組基本視窗程式結構如下 : 

import tkinter as tk                                  # 匯入 tkinter 模組並取簡名為 tk
root=tk.Tk()                                             # 建立根視窗 Tk 物件 
root.title("Hello")                                     # 設定標題
label=tk.Lable(root, "Hello!")                 # 建立標籤元件
label.pack()                                              # 將標籤元件加入視窗
root.mainloop()                                        # 將根視窗加入事件監視迴圈並持續描繪視窗

使用 ttk 的基本架構如下 :

import tkinter as tk                                   # 匯入 tkinter 模組並取簡名為 tk
from tkinter import ttk                             # 匯入 ttk 子模組
root=tk.Tk()                                             # 建立根視窗 Tk 物件 (仍用 tk)
root.title("Hello")                                     # 設定標題
label=ttk.Lable(root, "Hello!")               # 建立標籤元件
label.pack()                                              # 將標籤元件加入視窗
root.mainloop()                                        # 將根視窗加入事件監視迴圈並持續描繪視窗

注意, 建立根視窗物件仍然要用 tk.Tk(), 建立元件才用 ttk.

如果視窗元件物件後續不會再用到就不須建立物件參考, 可以直接用鏈式呼叫 :

ttk.Label(root, "Hello").pack()   

視窗預設是可拖曳調整大小, 放到最大或縮小, 也可以呼叫根視窗 Tk 物件的 geometry() 方法設定視窗大小 :

root.geometry("400x300")

呼叫根視窗物件的 resizable() 方法傳入 (0,0) 或 (False, False) 可限制視窗不可調整大小 : 

root.resizable(0,0)                         # 設定視窗不可變更大小
root.resizable(False, False)           # 設定視窗不可變更大小


3. 建立元件 : 

原始版 tkinter 使用模組簡名 tk 呼叫方法, 主題版則用子模組名稱 ttk. 


(1). 標籤元件 : 

label=tk.Label(root, text="Hello World!")

更改標籤內容 :

label.configure("你是在說哈囉嗎?")


(2). 按鈕元件 : 

button=tk.Button(root, text="OK", command=clickOK)    

按鈕可用 command 參數綁定一個事件處理函式名稱 (不用括號), 此處為 clickOK, 須在呼叫之前定義. 另外一個方法是呼叫元件的 bind() 方法來綁定指定的事件, 例如按滑鼠右鍵或按下鍵盤等, bind() 的介面如下 :

bind(event, handler)   

第一參數 event 是一個代表特定事件的字串, 例如最常用的是 "<Button-1>" 表示按下滑鼠左鍵事件, 而 "<Double-Button-1>" 則表示連續按滑鼠左鍵兩次  (滑鼠左, 中, 右鍵分別以數字 1, 2, 3 表示), 例如 :

button1.bind("<Button-1>", clickOK)

第二參數 handler 為事件處理函式名稱 (不用括號), 但與使用 command 參數綁定不同的是, 定義此 handler 函式時須傳入事件變數 (否則會出現錯誤), 例如 :

def clickOK(e):
    pass

利用此事件物件可取得此事件之相關資訊, 例如 e.x 與 e.y 可取得滑鼠左鍵之座標. 

關於更多 Tkinter 事件處理細節可參考 "Introduction to Programming Using Python (Python 程式設計入門指南)" 這本書的 9-11 節. 


(3). 元件排版 : 

tkinter 元件提供 pack(流水式), grid(網格式), 以及 place(絕對與相對位置) 三種排版方法, 其中 pack 與 grid 兩種方式互斥, 一個視窗容器中不可同時使用 pack 與 grid, 但 place 排版中則可以同時用 pack 與 grid. 

流水式排版 : 呼叫元件的 pack() 方法可將元件如流水般順序放入視窗容器, 預設是由下而上堆疊, 但可以利用可選參數 side (tk.TOP/tk.BOTTOM/tk.LEFT, tk.RIGHT/tk.CENTER) 與 anchor (tk.E/tk.W/tk.S/tk.N/tk.NW/tk.SW/tk.NE/tk.SE/tk.CENTER) 指定元件的放置方位與錨定位置, 預設是 tk.TOP 與 tk.CENTER, 故元件預設會由上而下置中放置 :   

label.pack()
button.pack()
button.pack(side=tk.LEFT, anchor=tk.S)

網格式排版 : 呼叫元件的 grid() 方法 

label.grid(row=0, column=0)     
button.grid()    # 多列單行
button.grid(row=0, column=1)


4. 對話框 : 

tkinter 透過三個子模組提供完整的對話框功能 :


 tkinter 對話框類別 說明
 messagebox 提供訊息輸出與確定, 取消, 是, 否等按鈕回應功能 (輸出)
 simpledialog 提供輸入框與確定, 取消等按鈕回應功能 (輸入)
 filedialog 提供檔案開啟與儲存對話框功能


其中訊息盒 messagebox 透過方法呼叫提供 8 種訊息盒類型, 語法如下 : 

messagebox.functionName(title, message [, detail])

參數 message 是主訊息, 而可選的 detail 則是說明訊息. 


 messagebox 方法 說明 按鈕 圖示 回傳值
 showinfo(title, massage [, detail]) 通知對話框 確定 i "ok"
 showwarning(title, massage [, detail]) 警告對話框 確定 ! "ok"
 showerror(title, massage [, detail]) 錯誤對話框 確定 x "ok"
 askyesno(title, massage [, detail]) 詢問對話框 是/否 ? True/False
 askokcancel(title, massage [, detail]) 詢問對話框 是/取消 ?  True/None
 askyesnocancel(title, massage[, detail]) 詢問對話框 是/否/取消 ? True/False/None
 askquestion(title, massage[, detail]) 詢問對話框 是/否 ? "yes"/"no"
 askretrycancel(title, massage[, detail]) 詢問對話框 重試/取消 ? True/False


使用 messagebox 要先從 tkinter 匯入此子模組並取個方便的簡名例如 mb :

from tkinter import messagebox as mb

例如 :

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

def hello():
    msgbox.showinfo("Info", "Hello World!")

root=tk.Tk()
root.title("Hello")
root.geometry("300x200")
button=ttk.Button(root, text="Hello", command=hello)
button.pack()
root.mainloop()

子模組 simpledialog 就是輸入盒的功能, 可讓使用者輸入資料供程式後續運算, 使用前須從 tkinter 匯入此子模組並取個簡名例如 sd, 語法如下 :

from tkinter import simpledialog as sd

simpledialog 提供了三個方法分別用來讀取字串, 整數, 以及浮點數三種型態之輸入 :


 simpledialog 的方法 說明 回傳值
 askstring(title, message) 顯示字串輸入對話框 字串
 askinteger(title, message) 顯示整數輸入對話框 整數
 askfloat(title, message) 顯示浮點數輸入對話框 浮點數
 

這三個方法有 title 與 message 這兩個必要參數, 其實對於數值輸入的 askinteger() 與 askfloat() 還有如下三個可選參數 :
  • minvalue : 設定最小值
  • maxvalue : 設定最大值
  • initialvalue : 設定初始值 (預設值) 
若輸入值超出 minvalue~maxvalue 的範圍會出現提示對話框.


5. tkinter 元件的共同屬性與方法 : 

每一個 tkinter 元件都是物件, 它們有各自專有之屬性與方法, 也有如下之共同屬性與方法: 


 tkinter 元件共同屬性 說明
 width 元件寬度, px 或字元數 (Label)
 height 元件高度, px 或字元數 (Label)
 bg/background 背景色, 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 fg/foreground 前景色, 可用標準顏色名稱字串例如 'blue' 或 "#0000ff"
 anchor 元件的錨點位置, 可用 "n", "e", "s", "w", "nw", "ne","sw","se","center"
 font 字型, 例如 "Helvetic 20 bold italic" 或 ("Helvetic", 20, "bold", "italic")
 bitmaps 內建的圖形, 例如 "error","info", "question", "warning", "gray12" 等
 relief 元件的邊框, 例如 "groov", "flat", "raised", "ridge", "solid", "sunken"
 cursors 滑鼠在元件上時的游標型式, 例如 : "heart", "cross", "star" 等

 tkinter 元件共同方法 說明
 config() 即時更改元件屬性值
 keys() 以串列傳回元件的所有屬性值
 pack() 將元件以流水方式放入父容器中
 grid() 將元件以網格方式放入父容器中
 place() 將元件以絕對或相對定位方式放入父容器中


6. tkinter 的物件導向寫法 : 

為了程式碼可重用性 (例如協同開發大型程式時), tkinter 程式也可以採用物件導向寫法, 上例之物件導向寫法為 :

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

class Hello():
    def __init__(self):
        root=tk.Tk()
        root.title("Hello")
        root.geometry("300x200")
        button=ttk.Button(root, text="Hello", command=self.hello)
        button.pack()
        root.mainloop()
    def hello(self):
        msgbox.showinfo("Info", "Hello World!")       
Hello()

或者標準一些的寫法 : 

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox

class Hello(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("tkinter GUI 測試")                           
        self.geometry("200x100")
        button=ttk.Button(self, text="Hello", command=self.hello)
        button.pack()
    def hello(self):
        msgbox.showinfo("Info", "Hello World!")       

if __name__ == "__main__":
    win=Hello()
    win.mainloop()

物件導向寫法要注意類別內存取屬性與方法時要用 self, 否則會出現函式未定義之 NameError. 


7. 變數類別 : 

Tkinter 的元件是一種 Python 物件, 其內容存取並非直接使用指定敘述, 而是透過 tkinter 所提供的四種變數類別 (對應 GUI 使用者介面常用的四種資料型別) : 


 tkinter 變數類別 說明
 IntVar 整數變數, 預設值 0
 DoubleVar 浮點數變數, 預設值 0.0
 BooleanVar 布林值變數, 預設 0 (False)
 StringVar 字串變數, 預設空字串 ""


呼叫這些類別的建構子即可建立變數物件, 例如 :

x=IntVar()
x=DoubleVar()
x=BooleanVar()
x=StringVar()

然後將這些變數指定給 tkinter 元件的 textvariable 或 variable 參數, 即可將此變數綁定到元件上, 只要呼叫變數物件的 get() 即可取得元件之內容 (例如取得 Entry 內使用者輸入之值), 呼叫變數物件的 set() 則可設定元件之內容 (例如設定 Entry 內容的預設值), 例如 : 

import tkinter as tk
from tkinter import ttk

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

i=tk.IntVar()
d=tk.DoubleVar()
b=tk.BooleanVar()
s=tk.StringVar()
tk.Label(win, textvariable=i, bg="ivory", width=5).pack()
tk.Label(win, textvariable=d, bg="cyan", width=5).pack()
tk.Label(win, textvariable=b, bg="pink", width=5).pack()
tk.Label(win, textvariable=s, bg="gold", width=5).pack()
win.mainloop()

當這些變數被更改或元件內容被更改時, 對方的值也會動態地被更改, 因為變數物件與 tkinter 元件已經透過 textvariable 參數互相綁定在一起了. 

複習完前三篇的主要內容就可以開始進行下一步的測試學習了. 

參考 :


沒有留言:

張貼留言