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)

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

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

最後執行部分也就是步驟 4 ,先建立 Tk 應用程式物件 root ,再建立 HelloView 的物件 app ,並以 rootHelloView() 的參數 (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 及格子版面管理員,設計從上到下依序為 EntryButtonLabel 的視窗。 參考程式碼
2. 寫一個程式 exercise3102.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 TextButtonLabel 的視窗。 參考程式碼
3. 寫一個程式 exercise3103.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 4 個 CheckbuttonLabel 的視窗。 參考程式碼
4. 寫一個程式 exercise3104.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 ttk 的 ComboboxLabel 的視窗。 參考程式碼
5. 寫一個程式 exercise3105.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 ListboxLabel 的視窗,其中 Listbox 需要用 insert() 方法插入 4 個選項。 參考程式碼
6. 寫一個程式 exercise3106.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 4 個 RadiobuttonLabel 的視窗,其中 Radiobutton 要額外設定 "text""value""variable"參考程式碼
7. 寫一個程式 exercise3107.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 SpinboxLabel 的視窗。 參考程式碼
8. 寫一個程式 exercise3108.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 ProgressbarScale 的視窗,其中 Scale 要額外設定 "orient"參考程式碼
9. 寫一個程式 exercise3109.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 CanvasButton 的視窗,其中 Canvas 要額外設定 "width""height" ,也可以設定 "bg"參考程式碼
10. 寫一個程式 exercise3110.py ,利用 Tk 及格子版面管理員,設計從上到下依序為 ToplevelButton 的視窗。 參考程式碼

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