大部分物件導向程式語言在開發 GUI 的部份會採取 MVC 模式 (Model-View-Controller) , M 是我們的 Encrypt 類別 (class) , V 為介面,而 C 為 M 與 V 之間的控制器
依 MVC 模式,三個部分都應該是獨立開發的類別,這是說 M 是資料計算的核心類別, V 是介面類別, C 為控制 M 與 V 之間交流的類別,倒是這在我們的例子略顯複雜,並不見得能簡化 GUI 的開發,因此我們不打算採取三個獨立類別的開發方式。
標準模組庫 (standard library) 中的圖形介面模組 (module) 為 tkinter , tkinter 在國外已經發展的相當成熟,許多程式語言 (programming language) 也都直接內建 Tk 。
我們的 V 跟 C 都會寫在 EncryptGUI 類別中,首先看到 V 的部份,也就是我們的 GUI 。設計 GUI ,首先要認識使用哪一種版面管理 (layout management) , Tk 有三種幾何版面管理員 (geometry manager) ,如下表
版面管理員名稱 | 說明 |
---|---|
定位版面管理員 | 自訂每個元件的座標。 |
包裹版面管理員 | 由程式自動安排每個元件的位置。 |
格子版面管理員 | 表格示安排每個元件,須指定元件所在的格數。 |
其次要認識有哪些視窗元件,每個圖形介面程式庫提供的元件 (component) 都差不多,不過用的名稱,也就是類別的識別字 (identifier) 都不太一樣,以下是 Tk 的元件列表
類別名稱 | 中文用詞 |
---|---|
Button | 按鈕 |
Canvas | 畫布 |
Checkbutton | 複選方塊 |
Entry | 文字欄位 |
Frame | 視窗 |
Label | 文字標籤 |
LabelFrame | 標籤視窗 |
ListBox | 選取清單 |
Menu | 選單 |
MenuButton | 選單元件 |
Message | 對話視窗 |
OptionMenu | 下拉式選單 |
PanedWindow | 面板 |
RadioButton | 單選方塊 |
Scale | 控制桿 |
Scrollbar | 捲軸 |
SpinBox | 旋鈕 |
Text | 文字方塊 |
Toplevel | 頂層視窗元件 |
關於 GUI 元件的英文或中文名稱,基本上各程式庫或作者的用詞都不太一樣,基本上這沒有什麼一致或權威的用詞,因此讀者只好自己習慣,倒是本書的用詞會保持一致的 ^_^
通常寫小程式就直接用包裹版面管理員 (pack manager) ,如下例
001 | from tkinter import * |
002 | |
003 | root = Tk() |
004 | text = Label(root, text="Tk's job!!", |
005 | width="30", height="5", |
006 | bg="black", fg="white") |
007 | text.pack() |
008 | root.mainloop() |
009 | |
010 | # 檔名: tk_demo.py |
011 | # 作者: Kaiching Chang |
012 | # 時間: July, 2014 |
用 Tk 首先要 import tkinter ,這裡用 from … import 之後星號表示所有名稱,這使我們在程式中可以直接使用 tkinter 中的名稱,不需要連帶寫出 tkinter
001 | from tkinter import * |
然後建立 Tk 物件
003 | root = Tk() |
這個視窗只有一個文字標籤, Label 型態的 text
004 | text = Label(root, text="Tk's job!!", |
005 | width="30", height="5", |
006 | bg="black", fg="white") |
這裡用了幾個參數 (parameter) ,文字標籤的文字 text 為 "Tk's job!!" ,高 height 為 5 ,寬 width 為 30 ,背景顏色 bg 為 "black" (黑色),前景文字顏色為 "white" (白色)。
來執行看看囉!結果如下
至於定位版面管理員 (place manager) 須自行定義每個元件在視窗的座標,這有點麻煩,我們替 Encrypt 類別設計的 GUI 打算用格子版面管理員 (grid manager) ,預計做出如下的 GUI
GUI 的建置放在類別中,每個元件都用實體屬性 (instance attribute) 建立,不難,純 GUI 外觀程式,也就是 V 的部分如下
001 | from tkinter import * |
002 | from tkinter.ttk import * |
003 | |
004 | # Encrypt 的 GUI 類別 |
005 | class EncryptGUI(Frame): |
006 | # 設定初值 |
007 | def __init__(self, master=None): |
008 | Frame.__init__(self, master) |
009 | self.grid() |
010 | self.createWidgets() |
011 | |
012 | # 建立所有視窗元件 |
013 | def createWidgets(self): |
014 | self.it = Label(self) |
015 | self.it["text"] = "Input:" |
016 | self.it.grid(row=0, column=0) |
017 | self.ifd = Entry(self) |
018 | self.ifd["width"] = 60 |
019 | self.ifd.grid(row=0, column=1, |
020 | columnspan=6) |
021 | |
022 | self.ot = Label(self) |
023 | self.ot["text"] = "Output:" |
024 | self.ot.grid(row=1, column=0) |
025 | self.ofd = Entry(self) |
026 | self.ofd["width"] = 60 |
027 | self.ofd.grid(row=1, column=1, |
028 | columnspan=6) |
029 | |
030 | self.nb = Button(self) |
031 | self.nb["text"] = "New" |
032 | self.nb.grid(row=2, column=0) |
033 | self.lb = Button(self) |
034 | self.lb["text"] = "Load" |
035 | self.lb.grid(row=2, column=1) |
036 | self.sb = Button(self) |
037 | self.sb["text"] = "Save" |
038 | self.sb.grid(row=2, column=2) |
039 | self.eb = Button(self) |
040 | self.eb["text"] = "Encode" |
041 | self.eb.grid(row=2, column=3) |
042 | self.db = Button(self) |
043 | self.db["text"] = "Decode" |
044 | self.db.grid(row=2, column=4) |
045 | self.db["command"] = self.dm |
046 | self.cb = Button(self) |
047 | self.cb["text"] = "Clear" |
048 | self.cb.grid(row=2, column=5) |
049 | self.cb2 = Button(self) |
050 | self.cb2["text"] = "Copy" |
051 | self.cb2.grid(row=2, column=6) |
052 | |
053 | self.dt = Label(self) |
054 | m = "something happened" |
055 | self.dt["text"] = m |
056 | self.dt.grid(row=3, column=0, |
057 | columnspan=7) |
058 | |
059 | # GUI 執行部分 |
060 | if __name__ == '__main__': |
061 | root = Tk() |
062 | app = EncryptGUI(master=root) |
063 | app.mainloop() |
064 | |
065 | # 檔名: encrypt_gui.py |
066 | # 作者: Kaiching Chang |
067 | # 時間: July, 2014 |
EncryptGUI 部分屬性與方法的命名是基於排版需求,因此採取非常簡短的命名方式,例如 it 為 input text 的頭字母縮寫詞。
EncryptGUI 類別直接繼承 (inherit) tkinter 中的 Frame ,繼承的寫法是在類別識別字後面加上小括弧,小括弧裡為所要繼承的類別名稱,由於 Frame 為沒有元件的視窗,因此我們可以在 EncryptGUI 上加入需要的視窗元件
005 | class EncryptGUI(Frame): |
__init__() 方法 (method) 需要預設參數 master ,這裡先呼叫 Frame 的 __init__()
006 | # 設定初值 |
007 | def __init__(self, master=None): |
008 | Frame.__init__(self, master) |
009 | self.grid() |
010 | self.createWidgets() |
注意下面呼叫 grid() ,這是繼承自 Frame 的方法,表示我們在這個 GUI 中要使用格子版面管理,最後呼叫的 createWidgets() 則是我們待會用來加入視窗元件的方法。
接下來呼叫 createWidgets() ,這個方法建立三個 Label 、兩個 Entry 及七個 Button ,全都設定好相關的格子,其中
019 | self.ifd.grid(row=0, column=1, |
020 | columnspan=6) |
這樣的設定是 self.ifd 在從上數下來第 0 列,從左往右第 1 行,然後水平擴展 6 格,注意,第幾列第幾行都是從 0 開始數。
另外看到屬性用中括弧加字串設定,這些字串就是字典型態的 key 值
015 | self.it["text"] = "Input:" |
類別中定義的屬性會自動轉換成字典,因此可直接由 key-value 的方式設定屬性值。
目前按鈕都沒有動作,因為我們還沒設定相關事件 (event) 處理,這要設定 command 囉!
中英文術語對照
MVC 模式 | Model-View-Controller |
類別 | class |
標準模組庫 | standard library |
模組 | module |
程式語言 | programming language |
版面管理 | layout management |
幾何版面管理員 | geometry manager |
元件 | component |
識別字 | identifier |
包裹版面管理員 | pack manager |
參數 | parameter |
定位版面管理員 | place manager |
格子版面管理員 | grid manager |
實體屬性 | instance attribute |
重點整理
- MVC 為軟體工程把模型、介面及控制分開的發展模式, M 就是就是處理資料的類別, V 為使用者介面的類別, C 則是控制 M 與 V 交流的類別。
- 標準模組庫直接整合 Tk ,首先認識有三種版面管理,分別是定位版面管理員、包裹版面管理員、格子版面管理員,其次要認識有哪些視窗元件。
- 包裹版面管理員適合視窗元件少的小程式使用。
- 定位版面管理員須自行定義每個視窗元件在視窗中的座標。
- 格子版面管理員將視窗分成格子,然後將視窗元件放入指定的格子中。
問題與討論
- 什麼是 MVC 模式?為什麼開發軟體要採用 MVC 模式?
- 試著比較 Tk 的三種幾何版面管理員的差別,找出常用軟體如記事本、瀏覽器等等,適合套用哪一種管理員?
練習
- 仿造 encrypt_gui.py ,寫一個新程式 tk_demo2.py ,裡頭設置一個 Label 及 Button 。
- 承接上一個單元的猜數字遊戲,將新程式寫在 guessgame_gui.py 中,替猜數字遊戲設計圖形介面,裡頭至少需要一個建立新遊戲的 New 按鈕及猜測用的 Guess 按鈕,另外需要一個顯示結果的 Label 。
the end
沒有留言:
張貼留言