Python 入門指南 5.0

單元 31 - Tk 的視窗元件與 V 的部分

~~學習進度表~~

Tk 具備一般常用的視窗元件,因此用 Tk 來開發小工具絕對夠用

EncryptView01
↑
M   V   C
↓      ↓
      Encrypt     EncryptController

若是桌機平台的大型軟體也可用 Tk 來開發,基本上 Python 有哪一種桌機平台的版本,就可連帶用 Tk ,但是如手機、平板、穿戴式或其他平台等等,需要看該平台的 Python 版本有否支援 Tk 。

這邊先來要認識有哪些視窗元件,每個圖形介面程式庫提供的元件 (component) 都差不多,不過用的名稱,也就是類別的識別字 (identifier) 都不太一樣,以下是 Tk 的元件列表

類別說明或常見中文名稱
Button按鈕。
Canvas長方形繪圖區域。
Checkbutton核取按鈕。
Combobox下拉式單選選單。
Entry單行文字輸入欄位。
Frame可容納其他視窗元件的長方形區域類別。
Label文字標籤。
Listbox單選選單。
Menu選單。
Progressbar進度條。
Radiobuttion單選按鈕。
Scale滑動條。
Scrollbar卷軸。
Separator分隔線。
Spinbox微調選單。
Style設定樣式或主題的類別。
Text多行文字輸入欄位。
Toplevel新增視窗的類別。

關於 GUI 元件的英文或中文名稱,基本上各程式庫或作者的英文、中文用詞都不太一樣,,基本上這沒有什麼一致或權威的用詞,因此讀者只好自己習慣,倒是本書的用詞會保持一致的。

我們預計做出如下的 GUI

基本上「輸入:」、「輸出:」跟「訊息欄」都是文字標籤,也就是 Label ,「輸入:」及「輸出:」後面的凹陷下去的視窗元件是文字輸入欄,文字輸入欄的型態為 Entry ,下面「新建」、「載入」、「存檔」、「編碼」、「解碼」、「清除」、「拷貝」等七個是按鈕,按鈕的型態為 Button 。

雖說這裡只用到三種共十二個視窗元件,倒是接下來要用類別 (class) 要設計 GUI ,用類別的原因很簡單,就是把跟 GUI 相關的程式部分都打包在類別中, V 的部分用 EncryptView 類別,而 C 的部分用 EncryptController 類別,主要目的是讓整體程式碼比較好組織及維護。

然而一下子跳進 GUI 類別設計有點繁雜,這裡先列出步驟

  1. 定義繼承 (inherit) 自 Frame 的類別,例如 HelloView ;
  2. 在 __init__() 方法中呼叫 Frame 的 __init__() ,然後進行相關設定,最後呼叫 createWidgets() 方法;
  3. 定義 createWidgets() 方法,其為自訂建立視窗元件的方法;
  4. 執行部分先建立 Tk 應用程式物件 root ,然後建立 HelloView 的物件,並以 root 當作 master 的參數,最後呼叫 mainloop() 方法。

以下先將上個單元的 tk_demo3.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
# 從標準程式庫引入 Tk
from tkinter import *

# Hello 的 View 類別
class HelloView(Frame):
    # 建構子設定初值
    def __init__(self, master=None):
        # 呼叫父類別的建構子
        Frame.__init__(self, master)
        # 設定視窗標題
        self.winfo_toplevel().title("Hello World!")
        # 使用格子版面管理員
        self.grid()
        # 呼叫建立視窗元件的方法
        self.createWidgets()

    # 建立視窗元件
    def createWidgets(self):
        # 建立文字輸入標籤
        self.text = Label(self)
        # 設定文字輸入標籤的文字
        self.text["text"] = "Hello World!"
        # 設定文字輸入標籤的寬度
        self.text["width"] = "30"
        # 設定文字輸入標籤的高度
        self.text["height"] = "5"
        # 設定文字輸入標籤的背景顏色
        self.text["bg"] = "black"
        # 設定文字輸入標籤的文字顏色
        self.text["fg"] = "white"
        # 設定文字輸入標籤在格子的位置
        self.text.grid(row=0, column=0)

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

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

首先,步驟 1 定義繼承自 Frame 的類別,繼承的寫法就是在類別名稱之後加上小括弧,小括弧中放父類別 (superclass) 的識別字

 5
class HelloView(Frame):

因此 HelloView 就是一種 Frame 類別,所有的視窗元件就是要加入到 HelloView 上。

步驟 2 關於 __init__() 方法的定義

 7
 9
11
13
15
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.winfo_toplevel().title("Hello World!")
        self.grid()
        self.createWidgets()

注意這裡要先呼叫 Frame 的 __init__()

 9
        Frame.__init__(self, master)

這是因為 HelloView 要發揮 Frame 的完整功能,就要先呼叫 Frame 的 __init__() ,另外 master 需要以 Tk 應用程式物件當參數,這在底下建立 HelloView 型態的變數 app 代入

39
    app = HelloView(master=root)

下面依序做必要的設定,像是 winfo_toplevel() 是呼叫上一層的 Tk 應用程式物件,這是因為 Frame 沒有 title() 方法設定視窗標題

11
13
15
        self.winfo_toplevel().title("Hello World!")
        self.grid()
        self.createWidgets()

然後呼叫 grid() ,表示 HelloView 要用格子版面管理員,最後呼叫 createWidgets() ,這用來建立所有在 HelloView 上的視窗元件,這也是步驟 3 的部分,其定義如下

18
20
22
24
26
28
30
32
    def createWidgets(self):
        self.text = Label(self)
        self.text["text"] = "Hello World!"
        self.text["width"] = "30"
        self.text["height"] = "5"
        self.text["bg"] = "black"
        self.text["fg"] = "white"
        self.text.grid(row=0, column=0)

HelloView 的 text 屬性 (attribute) 為文字標籤 Label ,因此往後要操作這個文字標籤都可透過 text ,然後著接下來 text 的每一個屬性都是透過字典的 key:value 方式來設定,這是因為類別的屬性預設可用字典的方式存取。

createWidgets() 是定義所有視窗元件的方法,單獨設計成一個方法 (method) 是為了讓 __init__() 更簡潔,雖然說也可以把定義視窗元件的部分都寫在 __init__() 中,但是這樣子 __init__() 就會過長,不易閱讀。

最後執行部分也就是步驟 4 ,先建立 Tk 應用程式物件 root ,再建立 HelloView 的物件 app ,並以 root 當 HelloView() 的參數 (parameter) ,最後 root 再呼叫 mainloop() 方法

35
37
39
41
if __name__ == '__main__':
    root = Tk()
    app = HelloView(master=root)
    root.mainloop()

執行後會得到如下的視窗

按照同樣的步驟設計 EncryptView 類別,完整程式如下

  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
# 從標準程式庫引入 Tk
from tkinter import *

# Encrypt 的 View 類別
class EncryptView(Frame):
    # 建構子設定初值
    def __init__(self, master=None):
        # 呼叫父類別的建構子
        Frame.__init__(self, master)
        # 設定視窗標題
        self.winfo_toplevel().title("編密碼小工具")
        # 使用格子版面管理員
        self.grid()
        # 呼叫建立視窗元件的方法
        self.createWidgets()

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

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

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

注意到三個地方,先是第 31 行及第 44 行

31
44
                               columnspan=6)
                               columnspan=6)

然後是第 93 行

93
                               columnspan=7)

columnspan 為延伸多少行,這是說文字輸入欄位及文字輸出欄位延伸 6 行,最底下訊息欄文字標籤則是延伸 7 行。

注意這裡 column 為行,直為行, row 為列,橫為列。

程式執行結果如下

EncryptView 類別是純粹的 V ,完全採 Tk 預設的樣式,按下按鈕也沒有任何像是按下動畫的反應,下一個單元我們加入 ttk 的主題樣式設定,讓這個「編密碼小工具」更美觀點。

中英文術語對照
屬性attribute
元件component
類別class
識別字identifier
繼承inherit
方法method
參數parameter
父類別superclass
重點整理
1. Tk 的視窗元件包括各種按鈕、文字輸入欄、文字標籤、拉桿、捲軸、文字方塊等等。
2. EncryptView 類別預計要有 3 個文字標籤、 2 的文字輸入欄及 7 個按鈕。
3. 利用類別設計 Tk 的 GUI ,首先定義繼承自 Frame 的類別,然後在 __init__() 方法中呼叫 Frame 的 __init__() ,接著定義 createWidgets() 方法逐一建立視窗中的元件。
問題與討論
1. Tk 有哪些視窗元件?常用軟體如記事本、瀏覽器等等,用了哪些視窗元件呢?
2. 利用 Tk 的 GUI 類別設計步驟為何?又為什麼要改用類別設計?
練習
1. 寫一個程式 exercise3101.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Entry 、 Button 、 Label 的視窗。 參考程式碼
2. 寫一個程式 exercise3102.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Text 、 Button 、 Label 的視窗。 參考程式碼
3. 寫一個程式 exercise3103.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 4 個 Checkbutton 、 Label 的視窗。 參考程式碼
4. 寫一個程式 exercise3104.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 ttk 的 Combobox 、 Label 的視窗。 參考程式碼
5. 寫一個程式 exercise3105.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Listbox 、 Label 的視窗,其中 Listbox 需要用 insert() 方法插入 4 個選項。 參考程式碼
6. 寫一個程式 exercise3106.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 4 個 Radiobutton 、 Label 的視窗,其中 Radiobutton 要額外設定 "text" 、 "value" 、 "variable" 。 參考程式碼
7. 寫一個程式 exercise3107.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Spinbox 、 Label 的視窗。 參考程式碼
8. 寫一個程式 exercise3108.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Progressbar 、 Scale 的視窗,其中 Scale 要額外設定 "orient"。 參考程式碼
9. 寫一個程式 exercise3109.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Canvas 、 Button 的視窗,其中 Canvas 要額外設定 "width" 及 "height" ,也可以設定 "bg" 。 參考程式碼
10. 寫一個程式 exercise3110.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 Toplevel 、 Button 的視窗。 參考程式碼

上一頁 單元 30 - GUI 的基本概念與 MVC 模式
回 Python 入門指南 5.0 首頁
unit-32-layout-and-styling-of-window-widgets下一頁 單元 32 - 視窗元件的排版及樣式主題
回 Python 教材首頁
回程式語言教材首頁