以下內容為本人的學習筆記,如需要轉載,請宣告原文連結 微信公眾號「ENG八戒」https://mp.weixin.qq.com/s/B1hH5Qzd2RkAiiUId1tLWw
本文大概 2874 個字,閱讀需花 10 分鐘
內容不多,但也花了一些精力
如要交流,歡迎關注我然後評論區留言
謝謝你的點贊收藏分享
進入正文之前先說一件小事,本公眾號已改名為【ENG八戒】,原名是【englyf】。改名的理由是什麼?以後會告訴朋友們的!
另外文末有福利彩蛋,畢竟今天是元宵節!
這篇文章屬於系列文章《Python 內建介面開發框架 Tkinter入門篇》的第三篇,上接《Python 內建介面開發框架 Tkinter入門篇 乙》,歡迎關注我的微信公眾號「ENG八戒」檢視這個系列相關文章。
介面佈局
關於 Tkinter 框架的 GUI 佈局,其實官方沒有提供對應的圖形化工具可用,但是網上有一些開源的小工具可以使用。這裡不打算介紹這些小工具的使用,而是直接用框架提供的幾何圖形管理器來佈局,比如上面提到過的 pack() 就是其中一種。這裡提到的幾何圖形管理器也就是其它框架裡常說的佈局管理器。
Tkinter 框架提供的佈局管理器有:pack、grid、place 三種。每一個控制元件只可以使用一種佈局管理器,不同控制元件使用的佈局管理器可以不一樣。
pack
形象點說, pack 就是把控制元件包裝在一個矩形區域,這個區域大小足夠放置控制元件,而且預設置中。pack 是最簡單的佈局管理器,也稱之為包裝佈局。
直接試一試用 pack 來佈局三個靜態標籤 Label,預設設定(pack() 傳入引數為空)
import tkinter as tk
window = tk.Tk()
lbl_1 = tk.Label(
master=window,
text="label 1",
fg="black",
bg="red",
width=10,
height=5
)
lbl_1.pack()
lbl_2 = tk.Label(
master=window,
text="label 2",
fg="black",
bg="yellow",
width=10,
height=5
)
lbl_2.pack()
lbl_3 = tk.Label(
master=window,
text="label 3",
fg="black",
bg="blue",
width=10,
height=5
)
lbl_3.pack()
window.mainloop()
看看顯示效果
可以看到,預設 pack 會在父視窗 window 中垂直方向按順序包裝排列這三個靜態標籤 Label。
那麼,如果我需要讓這幾個標籤水平排列呢?可以這樣子改
lbl_1.pack(side=tk.LEFT)
...
lbl_2.pack(side=tk.LEFT)
...
lbl_3.pack(side=tk.LEFT)
看看顯示效果
pack(side=tk.TOP) 和預設設定等價。
簡單彙總介紹一下其它的引數
引數 | 賦值 | 說明 |
---|---|---|
after | 控制元件 widget | 將此控制元件包裝在控制元件 widget 後邊 |
anchor | NSEW (or subset) | 根據方向定位此控制元件,NSEW 表示北南東西四個方向 |
before | 控制元件 widget | 將此控制元件包裝在控制元件 widget 前邊 |
expand | bool 型別值 | 跟著父控制元件一起伸縮 |
fill | NONE、X、Y、BOTH | 選擇當控制元件伸縮時按照哪個方向填充 |
ipadx | amount | 在x方向新增內部填充 |
ipady | amount | 在y方向新增內部填充 |
padx | amount | 在x方向新增填充 |
pady | amount | 在y方向新增填充 |
side | TOP、BOTTOM、LEFT、RIGHT | 把控制元件往哪邊新增 |
很多時候開發介面都需要讓裡邊的控制元件跟隨視窗自動拉伸大小,來看一下上面的程式碼應該怎麼改
lbl_1.pack(fill=tk.BOTH, expand=tk.TRUE)
...
lbl_2.pack(fill=tk.BOTH, expand=tk.TRUE)
...
lbl_3.pack(fill=tk.BOTH, expand=tk.TRUE)
啟動的時候,還沒有拉伸視窗
然後拉伸看看
grid
如名字表述,grid 會把父視窗劃分成行列,然後根據呼叫時傳入引數 row,column 確定把控制元件放置在對應的行列中。grid 也稱之為格子布局。
還是以靜態標籤為例,建立 3x3 的矩陣標籤。為了凸顯各個標籤的邊界,這裡還需要新增 Frame 控制元件,每個標籤放置於單獨的 Frame 中。
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(
master=frame,
text=f"Row {i}\nColumn {j}"
)
label.pack()
window.mainloop()
可以看到,上面這個例子佈局時,只有 Frame 才需要應用 grid 管理器,因為每個標籤和 Frame 一一對應,所以標籤不需要重複應用格子布局。
看看顯示效果
簡單彙總介紹一下其它的引數
引數 | 賦值 | 說明 |
---|---|---|
column | 列序號 | 指定放置的列,從0開始 |
columnspan | 列數 | 放置的控制元件橫跨多少列 |
ipadx | amount | 在 x 方向新增內部填充 |
ipady | amount | 在 y 方向新增內部填充 |
padx | amount | 在 x 方向新增外部填充 |
pady | amount | 在 y 方向新增外部填充 |
row | 行序號 | 指定放置的行,從0開始 |
rowspan | number | 放置的控制元件橫跨多少行 |
sticky | NSEW | 如果單元格比較大,那麼控制元件的指定邊界將貼著單元格。NSEW分別對應頂部、底部、右邊、左邊邊界 |
細心的朋友會發現,一旦拉伸上面的那個矩陣標籤視窗,視窗介面就會露出部分底面,矩陣標籤沒有跟隨一起拉伸。
這樣明顯和我們的預期不符合,這個問題怎麼解決呢?
可以呼叫父視窗的 columnconfigure() 和 rowconfigure() 方法配置各列和行的伸縮比。這兩個方法都有三個輸入引數,看下面的表格
引數 | 說明 |
---|---|
index | 序號,指定特定的行或列,可以是單個行列序號值,也可以是代表多個行或列的列表 |
weight | 伸縮權重 |
minsize | 最小寬度值 |
現在來看看怎麼改,才能讓矩陣標籤跟隨父視窗一起拉伸?
import tkinter as tk
window = tk.Tk()
for i in range(3):
window.rowconfigure(i, weight=1)
window.columnconfigure(i, weight=1)
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(
master=frame,
text=f"Row {i}\nColumn {j}"
)
label.pack()
window.mainloop()
看看拉伸之後的顯示效果
perfect!另外需要提一下,grid 具有 pack 能做的所有功能,但是使用的形式更簡單,因此應該作為更優先的佈局管理器。
place
place 用於對控制元件精確定位的場合。使用的時候需要傳入引數 x 和 y 分別用於指定控制元件的放置位置座標值 x 和 y,傳入的 x 和 y 是基於父控制元件的左上角為原點的座標系,單位是畫素點。
大多數介面應用裡,控制元件都不需要精確的定位。但是某些,比如地圖應用裡,就的確需要對元素的精確定位了。
下面舉個例子,在視窗裡不同位置放置各一個標籤
import tkinter as tk
window = tk.Tk()
label1 = tk.Label(
master=window,
text="place (0, 0)",
bg="yellow"
)
label1.place(x=0, y=0)
label2 = tk.Label(
master=window,
text="place (40, 40)",
bg="blue"
)
label2.place(x=40, y=40)
window.mainloop()
看看上面程式碼的顯示效果
說回來,place 的引數xy單位是畫素,那麼在不同的系統下,字型型別和大小都是不同的,那麼被放置的控制元件就有可能超出視窗邊界。因此 place 真的不常用,關於 place 進一步的資訊就不再展開了。
互動
上面介紹的內容都僅限於 Tkinter 介面的視覺化設計,那麼現在是時候介紹一下Tkinter 介面和使用者的互動了。
比如,Tkinter 介面對於事件的響應是怎麼發生的?
一般,在 Tkinter 中透過預先繫結事件和響應處理函式,每當事件發生時,主視窗的 mainloop 就會收到事件,然後根據繫結資訊,查詢到響應處理函式並呼叫,來達到互動的效果。繫結的通用方法是呼叫各個控制元件的 bind()。
比如,下面我們來實現一個簡單的鍵盤響應互動,每次按鍵按下時就把對應的按鍵列印出來
import tkinter as tk
def handle_keypress(event):
print(event.char)
window = tk.Tk()
window.bind("<Key>", handle_keypress)
window.mainloop()
上面的程式碼沒有新增額外的控制元件,除了有個空白的主視窗。其中,對主視窗 window 繫結了按鍵事件 Key 和處理函式 handle_keypress。
呼叫 bind() 最少要求輸入兩個引數,一個是形式為 "<event_name>" 的事件,另一個是事件處理函式。
定義處理函式 handle_keypress 時,唯一做的事情是把傳入的事件字元列印到標準終端。
每當按下鍵盤的按鍵,命令列終端就會輸出對應的按鍵。
但是,對於按鈕 Button 來說,點選事件的觸發處理可以不需要使用 bind(),僅需要在例項化控制元件 Button 時,傳入處理函式給 command 引數即可,初始化過程會自動繫結 click 事件的響應。
上程式碼看看
import tkinter as tk
def handle_keypress():
print("clicked")
window = tk.Tk()
button = tk.Button(
master=window,
text="click me!",
command=handle_keypress
)
button.pack()
window.mainloop()
執行程式
用滑鼠點選一下介面上的按鈕,發現終端會輸出字串 clicked
由於篇幅受限,本系列教程還未完結,下一篇《Python 內建介面開發框架 Tkinter入門篇 丁》將在本公眾號稍後推送,如果你對此教程有興趣或者想和我一起交流更多精彩內容,歡迎關注我的微信公眾號 【ENG八戒】,等著你哦!
《元宵彩蛋》
鬧元宵了,《八戒陪你一起鬧元宵2023》