Python 入門指南 5.0

單元 32 - 視窗元件的排版及樣式主題

~~學習進度表~~

ttk 在 Tk 8.5 之後引入,讓 Tk 程式 (program) 比較具有現代風格

EncryptView

M   V   C
↓      ↓
      Encrypt     EncryptController

不過 Tk 發展比較緩慢, Tk 8.5 首次正式釋出是在西元 2007 年,最新的 8.6.13 則是在 2022 年 11 月釋出。

這裡我們把 EncryptView 類別 (class) 用 ttk 擴充,原本 encrypt_view01.py 修改成 encrypt_view.py ,完整程式如下

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 從標準程式庫引入 Tk
from tkinter import *
# 從 tkinter 引入 ttk
from tkinter import ttk

# Encrypt 的 View 類別
class EncryptView(ttk.Frame):
    # 建構子設定初值
    def __init__(self, master=None):
        # 呼叫父類別的建構子
        ttk.Frame.__init__(self, master)
        # 設定視窗標題
        self.winfo_toplevel().title("編密碼小工具")
        # 使用格子版面管理員
        self.grid()
        # 呼叫建立視窗元件的方法
        self.createWidgets()
        # 建立樣式屬性
        self.style = ttk.Style()
        # 設定樣式主題
        self.style.theme_use("alt")
        # 依次取得每一個視窗元件
        for child in self.winfo_children():
            # 將視窗元件加入邊界
            child.grid_configure(padx=5, pady=3)

    # 建立所有視窗元件
    def createWidgets(self):
        # 建立文字輸入標籤
        self.input_text = ttk.Label(self)
        # 設定文字輸入標籤的文字
        self.input_text["text"] = "輸入:"
        # 設定文字輸入標籤在格子的位置
        self.input_text.grid(row=0, column=0,
                             sticky=N+E)
        # 建立文字輸入欄位
        self.input_field = ttk.Entry(self)
        # 設定文字輸入欄位的寬度
        self.input_field["width"] = 77
        # 設定文字輸入欄位在格子的位置
        self.input_field.grid(row=0, column=1,
                              columnspan=6,
                              sticky=N+W)
        # 設定 GUI 開啟後聚焦在文字輸入欄位
        self.input_field.focus()
        # 設定文字輸出標籤
        self.output_text = ttk.Label(self)
        # 設定文字輸出標籤的文字
        self.output_text["text"] = "輸出:"
        # 設定文字輸出標籤格子的位置
        self.output_text.grid(row=1, column=0,
                              sticky=N+E)
        # 設定文字輸出欄位
        self.output_field = ttk.Entry(self)
        # 設定文字輸出欄位的寬度
        self.output_field["width"] = 77
        # 設定文字輸出欄位在格子的位置
        self.output_field.grid(row=1, column=1,
                               columnspan=6,
                               sticky=N+W)
        # 設定文字輸出欄位無法輸入文字
        self.output_field.config(state="disabled")
        # 設定新建按鈕在格子的位置
        self.new_button = ttk.Button(self)
        # 設定新建按鈕的文字
        self.new_button["text"] = "新建"
        # 設定新建按鈕在格子的位置
        self.new_button.grid(row=2, column=0,
                             sticky=N+W)
        # 設定載入按鈕
        self.load_button = ttk.Button(self)
        # 設定載入按鈕的文字
        self.load_button["text"] = "載入"
        # 設定載入按鈕在格子的位置
        self.load_button.grid(row=2, column=1,
                              sticky=N+W)
        # 設定存檔按鈕
        self.save_button = ttk.Button(self)
        # 設定存檔按鈕的文字
        self.save_button["text"] = "存檔"
        # 設定存檔按鈕在格子的位置
        self.save_button.grid(row=2, column=2,
                              sticky=N+W)
        # 設定編碼按鈕
        self.encode_button = ttk.Button(self)
        # 設定編碼按鈕的文字
        self.encode_button["text"] = "編碼"
        # 設定編碼按鈕在格子的位置
        self.encode_button.grid(row=2, column=3,
                                sticky=N+W)
        # 設定解碼按鈕
        self.decode_button = ttk.Button(self)
        # 設定解碼按鈕的文字
        self.decode_button["text"] = "解碼"
        # 設定解碼按鈕在格子的位置
        self.decode_button.grid(row=2, column=4,
                                sticky=N+W)
        # 設定清除按鈕
        self.copy_button = ttk.Button(self)
        # 設定清除按鈕的文字
        self.copy_button["text"] = "清除"
        # 設定清除按鈕在格子的位置
        self.copy_button.grid(row=2, column=5,
                              sticky=N+W)
        # 設定拷貝按鈕
        self.clear_button = ttk.Button(self)
        # 設定拷貝按鈕的文字
        self.clear_button["text"] = "拷貝"
        # 設定拷貝按鈕在格子的位置
        self.clear_button.grid(row=2, column=6,
                               sticky=N+W)
        # 設定訊息欄文字標籤
        self.display_text = ttk.Label(self)
        # 設定訊息欄文字標籤的文字
        self.display_text["text"] = "訊息欄"
        # 設定訊息欄文字標籤在格子的位置
        self.display_text.grid(row=3, column=0,
                               columnspan=7,
                               sticky=N+E)

# GUI 執行部分
if __name__ == '__main__':
    # 建立 Tk 應用物件
    root = Tk()
    # 建立視窗物件
    app = EncryptView(master=root)
    # 呼叫維持視窗運作的 mainloop()
    root.mainloop()

# 檔名: encrypt_view.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 6 月

首先在第 4 行從 tkinter 引入 ttk 的名稱

 4
from tkinter import ttk

這裡單獨引入 ttk 的名稱是為了區別 tkinter 內與 ttk 相同的名稱,像是兩者重複了 12 種視窗元件的識別字 (identifier) ,因此底下使用 ttk 的定義要先加上 ttk. 的前綴。

第 7 行,本來繼承 (inherit) 自 Frame 類別,現在改成繼承自 ttk.Frame

 7
class EncryptView(ttk.Frame):

同樣在第 11 行,改成呼叫 ttk.Frame 的建構子 (constructor)

11
        ttk.Frame.__init__(self, master)

然後在第 19 行與第 21 行,設定 ttk 中的樣式主題,先是在第 19 行建立 ttl.Style 型態 (type) 的屬性 (attribute) style

19
21
        self.style = ttk.Style()
        self.style.theme_use("alt")

然後第 21 行 style 屬性呼叫 theme_use() 方法 (method) ,將樣式主題設定為 "alt" ,注意此處參數 (parameter) "alt" 為字串 (string) 。

ttk 的樣式主題,依作業系統有不同的選擇

作業系統樣式主體
macOS'aqua', 'clam', 'alt', 'default', 'classic'
Windows 11'winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative'
Ubuntu'clam', 'alt', 'default', 'classic'

Ubuntu 中的 Python 預設沒有安裝 tkinter ,因此在 Ubuntu 中使用 Tk 需要自行安裝。

本書主要截圖採用 macOS 系統,樣式設定為 'alt' ,如下圖所示

下圖示範在 Windows 11 設定為 'vista'

下圖示範在 Ubuntu 設定為 'clam'

總之可以依自己使用的作業系統設定自己喜好的樣式主體。

其實也可以套用第三方的樣式主題,有關第三方的樣式主題請參考 https://wiki.tcl-lang.org/page/List+of+ttk+Themes

接下來是利用 for 迴圈 (loop) ,替視窗元件加入邊界

23
25
        for child in self.winfo_children():
            child.grid_configure(padx=5, pady=3)

下圖表示在格子中邊界的設定方式

也就是說, pdx 是視窗元件內容的水平到格子兩側的距離, pdy 則是視窗元件內容的垂直到格子上下側的距離,設定 pdxpdy 的作用是讓視窗元件距離格子邊界有點距離,讓視窗元件看起來分隔清楚,不會很擠。

然後也改用 ttk 的視窗元件,因此視窗元件之前要加入前綴 ttk.

30
         self.input_text = ttk.Label(self)

另外也有設定 sticky 屬性,這是設定視窗元件在格子中的對齊方式

35
                             sticky=N+E)

以下是 sticky 屬性值及說明

說明
E向右對齊
W向左對齊
S向下對齊
N向上對齊

如果沒有設定 sticky 屬性,視窗元件預設在格子中置中對齊,而 N+E 就表示向上並且向右對齊,因此視窗元件出現在格子中的東北角,其他設定可類推。

下面在第 45 行,利用呼叫 focus() 方法,設定打開「編密碼小工具」視窗後聚焦在這個視窗元件

45
        self.input_field.focus()

設定聚焦的作用是讓使用者可以直接打鍵盤輸入,如果沒有設定的話,使用者需要先點擊輸入的文字方塊才能輸入。

然後下面第 62 行,利用呼叫 config() 方法把輸出欄位的 state 屬性設定為 "disabled" ,這會鎖住這個文字輸入方塊,讓使用者無法輸入

62
        self.output_field.config(state="disabled")

因為這個文字方塊用來顯示結果,自然不希望使用者做輸入的動作,因此鎖起來。

對比上一個單元做出的視窗,新加入排版及樣式的視窗是不是美觀多了呢?實際執行會發現按下按鈕是有動畫的,但是有動畫還不夠,因為沒有實際發揮按鈕的功能,下一個單元我們會進入 C 的部分,除了定義 Controller 類別外,也會將按鈕跟指定的方法連結。

中英文術語對照
屬性attribute
類別class
建構子constructor
識別字identifier
繼承inherit
迴圈loop
方法method
參數parameter
程式program
型態type
字串string
重點整理
1. ttk 在 Tk 8.5 之後引入,讓 Tk 視窗比較具有現代化的樣式風格。
2. 使用 ttk 樣式的視窗要改成繼承自 ttkFrame 類別。
3. ttk 的樣式主題,依作業系統有不同的選擇。
4. 視窗元件到邊界的距離採用 padxpady 設定。
5. 利用 sticky 可以設定視窗內容在格子中對齊的方式。
6. focus() 方法用來設定聚焦的視窗元件。
7. state 屬性設定為 "disabled" 會鎖住視窗元件。
問題與討論
1. 直接用 Tk 的預設樣式有什麼優點及缺點?
2. 採用 ttk 的樣式有什麼優點及缺點?
練習
1. 將單元 31 的練習 1 加入 ttk 樣式到程式 exercise3201.py 中。 參考程式碼
2. 將單元 31 的練習 2 加入 ttk 樣式到程式 exercise3202.py 中。 參考程式碼
3. 將單元 31 的練習 3 加入 ttk 樣式到程式 exercise3203.py 中。 參考程式碼
4. 將單元 31 的練習 4 加入 ttk 樣式到程式 exercise3204.py 中。 參考程式碼
5. 將單元 31 的練習 5 加入 ttk 樣式到程式 exercise3205.py 中。 參考程式碼
6. 將單元 31 的練習 6 加入 ttk 樣式到程式 exercise3206.py 中。 參考程式碼
7. 將單元 31 的練習 7 加入 ttk 樣式到程式 exercise3207.py 中。 參考程式碼
8. 將單元 31 的練習 8 加入 ttk 樣式到程式 exercise3208.py 中。 參考程式碼
9. 將單元 31 的練習 9 加入 ttk 樣式到程式 exercise3209.py 中。 參考程式碼
10. 將單元 31 的練習 10 加入 ttk 樣式到程式 exercise3210.py 中。 參考程式碼

上一頁 單元 31 - Tk 的視窗元件與 V 的部分
回 Python 入門指南 5.0 首頁
下一頁 單元 33 - C 的部分與設定 command
回 Python 教材首頁
回程式語言教材首頁