Python Tkinter 的排版演練

讓 tk 視窗乖乖聽話

wyatthoho
10 min readFeb 21, 2022

【零】前言

Tkinter ( 簡稱 tk ) 是 Python 的內建套件之一,其功能是用來建立圖形化使用者介面 ( Graphical user interface, GUI )。假設你寫了一個很厲害的程式,像是自動記帳工具,如果想要分享給其他人使用的話,最好要能夠配合美美的圖形化使用者介面,才能讓使用者在視覺上快速理解此程式該如何操作。

在 Python 中能夠製作 GUI 的套件很多,雖然 tkinter 可能不如其他第三方套件像是 PyQT 、 PySide 等等那麼的方便簡易,但它是最基本的,學會它能夠幫助理解 GUI 的機制,若未來要使用其他套件也能更快熟悉!

本文會介紹使用 tkinter 設計 GUI 的過程中,該如何設定主視窗,再進一步規劃視窗的排版,讓視窗能夠拆分出不同的區塊並滿足美觀的需求~

【壹】建立主視窗

一、基本語法

首先,匯入模組

import tkinter as tk

拉出一個空白的主視窗,並命名為root

root = tk.Tk()

接著,此名為root的視窗物件還有許多方法 ( method ) 可以進行調整,例如設定視窗名稱

root.title()

設定視窗大小

root.geometry()

可拖曳改變範圍

root.resizable()

設定icon

root.iconbitmap()

最後,透過以下指令開啟視窗,

root.mainloop()

二、綜合示範

以下建立一個名為 myApp 的視窗,其寬500、高200,並限制視窗大小無法改變,還附上一個 logo 圖案。

import os
import tkinter as tk

fileDir = os.path.dirname(__file__)
pardir = os.path.abspath(os.path.join(fileDir, os.path.pardir))
logoPath = os.path.join(pardir, 'image', 'w.ico')

root = tk.Tk()
root.title('myApp') # 設定視窗名稱
root.geometry('500x200+100+100') # 設定視窗大小及位置(width x height + x + y)
root.resizable(width=0, height=0) # 決定能用滑鼠拖曳來改變多少的視窗大小
root.iconbitmap(logoPath)
root.mainloop() # 啟用視窗

執行後的畫面如下,

一個空白視窗

【貳】版面設計

學會建立主視窗之後,再來將主視窗進行區塊劃分,來達到版面設計的目的。要使用到LabelFrameFrame這兩種元件 ( widget ),兩者的差異在於LabelFrame帶有文字標題,而Frame則無。

一、建立LabelFrame

建立LabelFrame的基本語法如下,

widgetObj = tk.LabelFrame(master, **kw)

在 tkinter 當中,參數master是用來指定該Widget所屬的上層零件,此參數不可省略。其他常用的 keywords 包含:

  1. text:控制顯示名稱
  2. height:控制高度
  3. weight:控制寬度
  4. background, bg:控制底色

二、建立Frame

建立Frame的方式和建立LabelFrame很像,其語法如下,

widgetObj = tk.Frame(master, **kw)

搭配Frame常用的 keywords 有:

  1. height:控制高度
  2. weight:控制寬度
  3. background, bg:控制底色

三、使用grid擺放位置

以上指令雖建立了LabelFrameFrame,但還要透過擺放指令才能在畫面中呈現。在tkinter當中,所有Widget的物件都帶有packgridplace這三種擺放方法,我個人比較推薦的是grid,它是以二維陣列的概念來控制元件所要擺放的位置,其語法如下,

widgetObj.grid(**kw)

常用的 keywords 有:

  1. row, column:控制Widget擺放的列數與行數
  2. rowspan, columnspan:控制Widget跨越的列數與行數
  3. padx, pady:控制Widget「外側」的留白空間
  4. ipadx, ipady:控制Widget「內側」的留白空間
  5. sticky:控制Widget的靠齊方向

當使用grid擺放元件,tkinter 會依照該Widget所包含的內容自動調整至最緊密的大小。若希望將長寬固定,那麼需要透過以下指令,

widgetObj.grid_propagate(0)

四、綜合示範

以下程式碼將在主視窗裡建立一個LabelFrame,且內含四個不同的區塊Frame,為了方便辨識,每個不同區塊會以不同顏色做區隔。

import os
import tkinter as tk

fileDir = os.path.dirname(__file__)
pardir = os.path.abspath(os.path.join(fileDir, os.path.pardir))
logoPath = os.path.join(pardir, 'image', 'w.ico')

root = tk.Tk()
root.title('myApp')
root.geometry('500x200+100+100')
root.resizable(width=0, height=0)
root.iconbitmap(logoPath)

labelFrame1 = tk.LabelFrame(root, text='labelFrame1', height=200, width=500, bg='skyblue')
labelFrame1.grid()

frame1 = tk.Frame(labelFrame1, height=80, width=240, bg='orange')
frame1.grid(row=0, column=0)

frame2 = tk.Frame(labelFrame1, height=80, width=240, bg='tomato')
frame2.grid(row=0, column=1)

frame3 = tk.Frame(labelFrame1, height=80, width=240, bg='mediumpurple')
frame3.grid(row=1, column=0)

frame4 = tk.Frame(labelFrame1, height=80, width=240, bg='mediumseagreen')
frame4.grid(row=1, column=1)

root.mainloop()

關注上面程式碼的第17行到第27行,使用了grid搭配rowcolumn參數來控制元件的位置,讓這些元件能棋盤式的排列。

四個整齊排列的Frame

接著,以上面的程式碼為基礎,來進行幾種不同的調整測試吧~

測試一:propagate

觀察上圖,淺藍色的labelFrame1和底下四個Frame的邊界是貼齊的狀態,也就是說主元件master的邊界已依照其下層元件的實際範圍而調整,試著加上以下指令使邊界固定,

labelFrame1.propagate(0)

或是

labelFrame1.grid_propagate(0)

執行後可以發現淺藍色labelFrame1的邊界並未自動貼齊底下的四個Frame元件,而是維持原先的設定。

淺藍色邊界未對齊其他四個Frame元件

測試二 :rowspan

將綠色的frame4拿掉,再調整番茄色frame2的設定如下,

frame2 = tk.Frame(labelFrame1, height=160, width=240, bg='tomato')
frame2.grid(row=0, column=1, rowspan=2)

由於新增rowspan=2,因此frame2可以跨越兩列的範圍。

橘紅色區塊跨越了兩列

測試三: columnspan

若改拿掉番茄色frame2,並修改橘色frame1的設定如下,

frame1 = tk.Frame(labelFrame1, height=80, width=480, bg='orange')
frame1.grid(row=0, column=0, columnspan=2)

由於新增columnspan=2,因此frame1會跨越兩行的範圍。

黃色區塊跨越了兩行

測試四:sticky, padx, pady, ipadx, ipady

這次不拿掉任何東西,而是將綠色frame4縮小至高40、寬120,可以發現frame4在水平及垂直方向皆自動置中。

frame4 = tk.Frame(labelFrame1, height=40, width=120, ...)
調整綠色區塊大小

再指定sticky=tk.NW參數,將其貼齊邊界,

frame4.grid(row=1, column=1, sticky=tk.NW)

以上的tk.NW指的是西北方,依此類推,可以選擇的值有tk.Ntk.Etk.Stk.Wtk.NEtk.NWtk.SE以及tk.SW

綠色區塊貼齊邊界

再加上padxpady參數來控制外側留白範圍,

frame4.grid(row=1, column=1, padx=10, pady=10, sticky=tk.NW)
綠色區塊外側留白

若將padxpady改為ipadxipady,則「外側留白」的呈現將改為「內側留白」。

frame4.grid(row=1, column=1, ipadx=10, ipady=10, sticky=tk.NW)
綠色區塊內側留白

【參】結語

在整理這篇文章以前,常常在寫 tk 的時候發現介面執行的長相和我原先設想根本不一樣啊 XD,所以才下定決心釐清 tk 的排版機制。但除了排版以外,一個完整的 GUI 還要搭配其他更豐富的功能,因此接下來會再寫一篇文章,羅列一些常用的元件,例如ButtonCheckbuttonCombobox等等,並示範這些元件如何互動以及觸發其他的事件,學會這些就能輕鬆創造屬於自己的小工具啦!

--

--

wyatthoho

在混亂的宇宙裡,我透過寫程式來認識秩序並建立安定。wyatthoho@gmail.com