2023年6月22日 星期四

Python 學習筆記 : Tkinter 的版面布局容器 Frame 與 PanedWindow

雖然 Frame 物件我還沒時間做一個較完整的測試, 但這周在用 Tkinter 開發公司的維運小軟體時直接拿來上陣使用, 發覺用 Frame 元件搭配最簡單常用的 pack 版面布局也可以將會疊串在一起的 Entry, RadionButton, CheckButton, 與 Button 等元件水平擺放得很整齊.

下面以簡化的系統登入與操作按鈕等元件來說明 Frame 在排版上的妙用. 視窗中包含帳號密碼輸入框, 登入按鈕, 指令輸入框, 執行按鈕, 以及一個執行結果文字區域等元件 : 

 
測試 1 : pack() 的堆疊式的版面布局 

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('Frame + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
account=tk.Entry(root, width=10)  # 帳號輸入框
account.pack()  # 元件放入視窗
password=tk.Entry(root, width=10, show='*')  # 密碼輸入框
password.pack()  # 元件放入視窗
login_btn=tk.Button(root, text='登入', command=login)  # 登入按鈕
login_btn.pack()  # 元件放入視窗
command=tk.Entry(root, width=20)  # 指令輸入框
command.pack()  # 元件放入視窗
run_btn=tk.Button(root, text='執行', command=run)  # 登入按鈕
run_btn.pack()  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

結果如下 : 




可見 pack 版面管理員的布局方式基本上就是由上而下堆疊, 雖然可以在呼叫 pack() 時傳入 side=tk.RIGHT/LEFT/TOP/BOTTOM 指定上下左右之垂直水平位置, 或傳入 anchor=tk.E/W/S/N/CENTER/NW/NE/SW/SE 來定位元件在父容器中的位置, 但光用 side 與 anchor 參數無法達成讓上方輸入框與按鈕元件以水平方式排在最上方, 且登入在左, 執行在右的需求. 

解決辦法之一是使用 Frame 容器來放置元件, 並且每個元件在呼叫 pack() 時傳入 side=tk.LEFT 指定由左向右水平排列, 版面結構如下圖所示 :




測試 2 : 改用 Frame 來當元件容器 

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('Frame + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
frame=tk.Frame(root)  # 外部 Frame 元件
frame.pack()
# 建立以 Frame 為父容器的元件
account=tk.Entry(frame, width=10)  # 帳號輸入框
account.pack(side=tk.LEFT)  # 元件放入視窗
password=tk.Entry(frame, width=10, show='*')  # 密碼輸入框
password.pack(side=tk.LEFT)  # 元件放入視窗
login_btn=tk.Button(frame, text='登入', command=login)  # 登入按鈕
login_btn.pack(side=tk.LEFT)  # 元件放入視窗
command=tk.Entry(frame, width=20)  # 指令輸入框
command.pack(side=tk.LEFT)  # 元件放入視窗
run_btn=tk.Button(frame, text='執行', command=run)  # 登入按鈕
run_btn.pack(side=tk.LEFT)  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

此例先建立一個 Frame 元件, 然後先後建立以此 Frame 元件為父容器 (不是 root 了) 的輸入框 Entry 與 Button 按鈕等元件, 但 Text 元件依然是以 root 為父元件. 結果如下 :



 
可見輸入框與按鈕都在 Frame 內呈水平排列. 注意, Frame 內的所有元件呼叫 pack() 時均傳入 tk.LEFT, 這表示元件是在 Frame 內由左向右依序排列之意, 並非定位在左邊 (定位要用 ANCHOR 參數); 同理, tk.RIGHT 表示由右向左排列. 如果將上面範例的 command 與 run_btn 兩個元件的 pack() 改為傳入 tk.RIGHT 會讓此兩元件順序顛倒 : 


測試 3 : 呼叫 pack() 傳入 side=tk.RIGHT 為由右向左排列

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('Frame + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
frame=tk.Frame(root)  # 外部 Frame 元件
frame.pack()

account=tk.Entry(frame, width=10)  # 帳號輸入框
account.pack(side=tk.LEFT)  # 元件放入視窗
password=tk.Entry(frame, width=10, show='*')  # 密碼輸入框
password.pack(side=tk.LEFT)  # 元件放入視窗
login_btn=tk.Button(frame, text='登入', command=login)  # 登入按鈕
login_btn.pack(side=tk.LEFT)  # 元件放入視窗
command=tk.Entry(frame, width=20)  # 指令輸入框
command.pack(side=tk.RIGHT)  # 元件放入視窗
run_btn=tk.Button(frame, text='執行', command=run)  # 登入按鈕
run_btn.pack(side=tk.RIGHT)  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

此例程式碼與上面不同之處僅在於 command 與 run_btn 這兩個元件呼叫 pack() 時傳入 side=tk.RIGHT 而已, 結果兩個元件就由右向左排列 :




所以放入 Frame 容器的元件呼叫 pack() 時一律傳入 side_tk.LEFT 即可. 

如果要讓這些 Frame 內水平排列的元件有所區隔, 可以用一個無顯示字串的 Label 來當隱形的佔位元件, 可透過 width (字元數, 不是 px) 來控制間隔距離, 例如 :


測試 4 : 利用無顯示字串的 Label 元件佔位

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('Frame + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
frame=tk.Frame(root)  # 外部 Frame 元件
frame.pack()

account=tk.Entry(frame, width=10)  # 帳號輸入框
account.pack(side=tk.LEFT)  # 元件放入視窗
password=tk.Entry(frame, width=10, show='*')  # 密碼輸入框
password.pack(side=tk.LEFT)  # 元件放入視窗
login_btn=tk.Button(frame, text='登入', command=login)  # 登入按鈕
login_btn.pack(side=tk.LEFT)  # 元件放入視窗
placeholder=tk.Label(frame, width=10)  # 佔位用的 Label
placeholder.pack(side=tk.LEFT)
command=tk.Entry(frame, width=20)  # 指令輸入框
command.pack(side=tk.LEFT)  # 元件放入視窗
run_btn=tk.Button(frame, text='執行', command=run)  # 登入按鈕
run_btn.pack(side=tk.LEFT)  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

此例在 login 鈕與 command 輸入框之間插入一個無顯示字串 (即 text='') 且設定 width=10 的 Lable 元件當佔位元件 (空元件), 這樣就在兩元件之間製造了 10 個字元寬度的間隔, 結果如下 :




注意, 間隔寬度若設太大, 撐破 Frame 的寬度時, 後面的元件可能會跳到下一行而影響版面外觀. 

除了 Frame 容器外, 也可以用 PanedWindow 來做版面布局, 茲將上面範例中的 Frame 改成 PanedWindow, 結果相同 :


測試 5 : 使用 PanedWindow 排版 

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('pw + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
pw=tk.PanedWindow(root)  # 外部 pw 元件
pw.pack()

account=tk.Entry(pw, width=10)  # 帳號輸入框
account.pack(side=tk.LEFT)  # 元件放入視窗
password=tk.Entry(pw, width=10, show='*')  # 密碼輸入框
password.pack(side=tk.LEFT)  # 元件放入視窗
login_btn=tk.Button(pw, text='登入', command=login)  # 登入按鈕
login_btn.pack(side=tk.LEFT)  # 元件放入視窗
placeholder=tk.Label(pw, width=10)  # 佔位用的 Label
placeholder.pack(side=tk.LEFT)
command=tk.Entry(pw, width=20)  # 指令輸入框
command.pack(side=tk.LEFT)  # 元件放入視窗
run_btn=tk.Button(pw, text='執行', command=run)  # 登入按鈕
run_btn.pack(side=tk.LEFT)  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

此例建立一個 PanedWindow 物件 pw, 然後底下程式碼中的 frame 全部改成 pw, 結果如下 :




可見結果與上面用 Frame 排版的效果一模一樣. 

除了直接呼叫元件的 pack() 方法將元件加入父容器 PanedWindow 物件外, 也可以呼叫該物件的 add() 方法, 它預設也是用 tk.LEFT 由左向右將元件放入容器中, 例如 :


測試 6 : 使用 PanedWindow 的 add() 方法將元件加入容器 

import tkinter as tk
from tkinter import ttk

def login():
    pass

def run():
    pass

# 建立視窗
root=tk.Tk()  # 建立視窗物件
root.title('pw + Pack 布局測試') # 設定視窗標題
root.geometry('800x600')  # 設定視窗尺寸

# 建立登入介面與指令輸入介面
pw=tk.PanedWindow(root)  # 外部 pw 元件
pw.pack()

account=tk.Entry(pw, width=10)  # 帳號輸入框
pw.add(account)  # 元件放入視窗
password=tk.Entry(pw, width=10, show='*')  # 密碼輸入框
pw.add(password)  # 元件放入視窗
login_btn=tk.Button(pw, text='登入', command=login)  # 登入按鈕
pw.add(login_btn)  # 元件放入視窗
placeholder=tk.Label(pw, width=10)  # 佔位用的 Label
pw.add(placeholder)
command=tk.Entry(pw, width=20)  # 指令輸入框
pw.add(command)  # 元件放入視窗
run_btn=tk.Button(pw, text='執行', command=run)  # 登入按鈕
pw.add(run_btn)  # 元件放入視窗
# 建立執行結果文字區域
result=tk.Text(root)
result.pack(fill=tk.BOTH, expand=True)
result.pack()

root.mainloop()

結果與上面範例相同. 

沒有留言 :