Python 內建介面開發框架 Tkinter入門篇 丙(文末有福利彩蛋,今天可是元宵節)

2023-02-06 06:01:10

以下內容為本人的學習筆記,如需要轉載,請宣告原文連結 微信公眾號「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