本博客高度參考了這篇文章,可以認爲是該文章的部分譯文,強烈推薦。
對原作者和網站提供如此高質量的教程表示感謝!
圖形用戶界面(Graphical User Interface,簡稱GUI),是指採用圖形方式顯示計算機的操作界面。相較於命令行,這種方式上手難度低,更加容易爲普通用戶所接受。
Python中有許多GUI框架,但Tkinter
是唯一一種被寫入標準庫中的。Tkinter
是跨平臺(cross-platform)的,這意味着相同的代碼可以在Windows、Linux和macOS上運行,而且GUI中的可視化元素(按鈕、文本框等)可以根據其所在的操作系統進行自動渲染。
當然,Tkinter
也有其不足之處。其中一點就是,它顯得“過時”了——許多可視化元素的樣式看起來像上個世紀的個人電腦中的一樣。如果你追求新潮、酷炫,那麼Tkinter
多半沒法滿足你的要求;如果只是想實現某些功能而不怎麼注重外觀,那麼輕量級的Tkinter
足以讓你快速、便捷地構建自己的應用。
1. 窗口(window)
在一個GUI中,最基礎最底層的元素是窗口(window)。如果一個把一個完整的GUI比作一幅畫,那麼window就是畫布,所有的操作都在這張畫布上進行。所以,在編寫GUI之前,需要創建這樣一張畫布——用編程的語言來說,就是要先實例化這樣一個window。
首先,打開Python的交互式窗口,導入tkinter
(在所有的交互代碼中,均省略了’>>>’):
import tkinter as tk
然後,實例化一個window
:
window = tk.Tk()
這時,操作系統就會彈出一個新的窗口,大概長這個樣子:
至此,畫布就準備好了,因爲現在什麼都沒做,所以畫布是空白的。我們可以向這張畫布上寫一些字符:
greeting = tk.Label(text='Hello Tkinter!')
greeting.pack()
最終彈窗的結果是這樣的:
第一行代碼的意思是,實例化一個Label
,在實例化的過程中,通過text
指定了該變量要顯示的內容。
第二行代碼的意思是,將該實例固定到畫布上。
Label
是Tkinter
中的一個類,單詞翻譯成中文的意思是“說明性短語”,在GUI領域,這個類被叫做“控件”(widget),通過向窗口中添加不同的控件,並對它們進行組合,就形成了各式各樣的GUI了。
Windows操作系統之所以這樣命名,就是因爲它本質上就是許多window的組合,當然這些組合是非常複雜的。這些window爲用戶屏蔽了看上去很枯燥的命令操作,從而提升了用戶體驗,但代價也是很巨大的,就是速度變慢。這種變慢對於普通用戶來說可能並沒有什麼感覺,但是對於服務器而言,卻很可能是致命的,這也是爲什麼絕大部分服務器都採用沒有GUI的Linux操作系統的原因之一。
2. 控件(widget)
如果說窗口是畫布,那麼控件就是不同的顏料了。它們也是組成一個GUI所必不可少的東西,是用戶與程序進行交互的基礎。在Tkinter
中,每一種控件都由一個類來定義,一些常見的控件及其說明如下:
控件 | 說明 |
---|---|
Label | 用於在屏幕上打印文本 |
Button | 按鈕,可以包含文字,並在單擊時進行相應的操作 |
Entry | 只允許單行文本輸入的控件 |
Text | 允許多行文本輸入的控件 |
Frame | 一個矩形區域,用於對控件分組或爲其提供填充 |
當然,還有許多其他的控件,但上面這些確實是最基礎的,熟練使用這些控件,就可以開發一些有趣的東西,並對Tkinter
的運用有相當程度的理解。
2.1 使用Label
控件
Label
控件可以用來展示文本或者圖片。這些文本(圖片)是不能夠被用戶編輯的,上面你已經看到了一個展示文本的例子,而實際上我們還可以對展示的文本進行更多的操作:
greeting = tk.Label(
text='Hello Tkinter!',
foreground="white",
background="black"
)
greeting.pack()
很直觀,我們改變了前景色和背景色,彈窗如下:
關於顏色,可以查看顏色手冊。當然也可以使用十六進制的RGB顏色值,這更加靈活。
Tkinter
還提供了縮寫,如果你覺得background
這個單詞太長了,可以用簡寫bg
代替它,同理,可以用fg
代替foreground
。
通過指定寬度和高度,來控制控件的大小:
greeting = tk.Label(
text='Hello Tkinter!',
fg="white",
bg="black",
width=10,
height=10
)
greeting.pack()
窗口如下:
這裏就存在一個問題了:寬度和高度都指定的是“10”,怎麼渲染出來的不是一個正方形呢?這是因爲在Tkinter
中,寬度和高度都是以文本單元(text unit)來度量的。具體來說,1單位的寬度由系統中的字符“0”或者數字0的寬度來決定,1單位的高度由系統中的字符“0”或者數字0的高度來決定。
採用這種設置而不是用英寸、釐米、像素等度量,也是爲了保證程序在跨平臺運行時顯示一致。以字符來度量意味着控件大小與用戶機器上的默認字體是相關的,這樣就保證了文本能夠正確地嵌入到Label和Button中。
2.2 使用Button
控件
顧名思義,Button
控件就是用來展示一個按鈕的,點擊該按鈕,可以實現一個操作。
事實上,我們可以將Button
理解爲一個可以點擊的Label
。用於創建Label
的關鍵字參數也適用於Button
控件:
button = tk.Button(
text="Click me!",
width=25,
height=5,
bg="blue",
fg="yellow",
)
彈窗是這樣的:
2.3 使用Entry
控件
當你需要收集用戶輸入的一些信息,比如用戶名、密碼等,這時候就可以使用Entry
控件了。它顯示爲一個小的文本框,用戶可以在其中輸入一些文本。創建和設計Entry
控件的工作原理與上面提到的Label
和Button
非常類似:
entry = tk.Entry(fg="yellow", bg="blue", width=50)
彈窗如下:
上面已經說過,Entry
是一個單行輸入的文本框,那麼一個重要的功能就是如何使用它從用戶那裏獲取信息。對於一個Entry
實例,最常用的方法有三個:
.get()
:獲取Entry
實例中的文本.delete()
:刪除Entry
實例中的文本.insert()
:向Entry
實例中插入文本
下面通過具體的例子來演示這些方法的作用。
首先,創建一個GUI,其窗口顯示的內容如下:
生成這個窗口的腳本爲:
import tkinter as tk
window = tk.Tk()
label = tk.Label(text='name')
entry = tk.Entry()
label.pack()
entry.pack()
需要注意的一點是,兩個控件都是居中對齊的,這也是.pack()
方法的特性之一,後文會對這個特性進行說明。
單擊一下輸入框,就可以在裏面輸入內容了,假設我們輸入“Real Python”。爲了驗證各個方法的作用,我們在交互式窗口接着輸入:
entry.get()
則會打印出“Real Python”的字符。
.delete()
方法用於刪除字符,其使用與對待Python中的字符串的使用方式很相似,需要傳入一個參數告訴Python刪除的字符的位置:
entry.delete(0)
即刪除了第一個字符,這時彈窗爲:
如果想一次性刪除好幾個字符,還需要傳入第二個整數,用於告訴Python刪除字符的終止位置:
entry.delete(0, 4)
這時,彈窗爲:
第二個數字爲終止字符的位置,該位置的字符不會被刪除,上面的代碼實際上只刪除了位置爲0、1、2、3的字符。
如果想一次性刪除全部,可以用如下的方法實現:
entry.delete(0, tk.END)
這時,彈窗裏面的Entry
控件又爲空了:
與之相反,你可以通過.insert()
方法來向控件中添加字符:
entry.insert(0, "Python")
第一個整數告訴Python從哪裏開始插入字符。如果Entry
控件中沒有任何字符,那麼新字符總是插入到控件的開始位置上,與你傳入的第一個參數無關。
這時的窗口又變成了這樣:
再繼續往裏面插值:
entry.insert(0, "Real ")
會發現,窗口變成了第一次輸入後的樣子:
2.4 使用Text
控件
可以將Text
控件理解爲一個可以輸入很多行文本的Entry
。事實上,它的作用就是如此。
與Entry
控件類似,Text
控件也有三種主要的方法:
.get()
:獲取Text
實例中的文本.delete()
:刪除Text
實例中的文本.insert()
:向Text
實例中插入文本
需要注意的是,儘管方法名稱完全一致,但由於多行文本的原因,其使用方法略有不同。
首先,我們重新創建一個帶Text
控件的GUI:
window = tk.Tk()
text_box = tk.Text()
text_box.pack()
GUI應該具有如下樣式:
單擊該Text
中的任意位置,然後就可以輸入字符了,這裏我輸入了“Hello World”,佔用了兩行。
與Entry
控件相類似,可以採用.get()
方法來提取Text
控件中的內容。但需要注意的是,Text
控件的.get()
方法至少需要一個參數。如果只傳入一個參數,那麼你將得到該位置上的字符;如果想要得到數個字符,則需要傳入開始位置參數和終止位置參數。由於Text
控件中的內容可能包含多行,因此,每個參數都必須包含兩部分內容:字符的行號與字符在該行中的位置。
所以,位置參數的格式是<line>.<char>
。爲了直觀的理解,嘗試一下下面的代碼:
text_box.get("1.0")
# 'H'
text_box.get("1.0", "1.5")
# 'Hello'
text_box.get("1.0", tk.END)
# 'Hello\nWorld\n'
注意到,換行符也是一個字符。
在理解了.get()
方法之後,相應的.delete()
方法和.insert()
方法也是通過類似的方式進行調用的。具體不再贅述,只需要記住,\n
也是一個字符。
2.5 使用Frame
控件
一個好的GUI不但有很多控件,它們之間還得通過合適的方式組合起來纔行,Frame
控件就可以滿足這個要求。
可以把Frame
控件理解爲其他控件的容器,在實例化其他控件時,通過master
參數來指定該控件屬於哪個Frame
。具體的,看以下代碼:
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
frame_b = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
frame_a.pack()
frame_b.pack()
window.mainloop()
彈窗如下:
代碼的理解也是比較直觀的:
- 5、6行實例化了兩個
Frame
控件; - 8、9行實例化了一個
Label
控件,並通過master
參數,指定了該控件包含於frame_a
中; - 11、12行操作類似;
- 14、15行將兩個
Frame
控件pack到window上; - 17行啓動了window的主循環,以保證窗口處於打開狀態。
接下來,如果我改變14和15行的順序,那麼彈窗是什麼樣的呢?
...
frame_b.pack()
frame_a.pack()
...
彈窗如下:
通過前後兩次實驗的對比,可以這樣理解Frame
控件的工作原理:其他控件在實例化時,通過master
參數指定了其所屬的Frame
,然後將其pack到該Frame
上。最後,將Frame
實例pack到主window上,就顯示出來各個控件了。