官方网址:ttkbootstrap - ttkbootstrap
老版本写法:
这里使用 tk.Tk()
import tkinter as tk
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
root = tk.Tk()
b1 = ttk.Button(root, text="Button 1", bootstyle=SUCCESS)
b1.pack(side=LEFT, padx=5, pady=10)
b2 = ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE))
b2.pack(side=LEFT, padx=5, pady=10)
root.mainloop()
新版本写法:
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
root = ttk.Window()
b1 = ttk.Button(root, text="Button 1", bootstyle=SUCCESS)
b1.pack(side=LEFT, padx=5, pady=10)
b2 = ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE))
b2.pack(side=LEFT, padx=5, pady=10)
root.mainloop()
两者的区别:
tk.Tk() 属于 tkInter 库:
而 ttk.Window() 属于 ttkbootstrap库,样式更丰富:
每种按钮的颜色:
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
root = ttk.Window()
b1 = ttk.Button(root, text='primary', bootstyle=PRIMARY)
b1.pack(side=LEFT, padx=5, pady=5)
b2 = ttk.Button(root, text='secondary', bootstyle=SECONDARY)
b2.pack(side=LEFT, padx=5, pady=5)
b3 = ttk.Button(root, text='success', bootstyle=SUCCESS)
b3.pack(side=LEFT, padx=5, pady=5)
b4 = ttk.Button(root, text='info', bootstyle=INFO)
b4.pack(side=LEFT, padx=5, pady=5)
b5 = ttk.Button(root, text='warning', bootstyle=WARNING)
b5.pack(side=LEFT, padx=5, pady=5)
b6 = ttk.Button(root, text='danger', bootstyle=DANGER)
b6.pack(side=LEFT, padx=5, pady=5)
b7 = ttk.Button(root, text='light', bootstyle=LIGHT)
b7.pack(side=LEFT, padx=5, pady=5)
b8 = ttk.Button(root, text='dark', bootstyle=DARK)
b8.pack(side=LEFT, padx=5, pady=5)
root.mainloop()
主题样式:
简单的文本输入:
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
class DataEntryForm(ttk.Frame):
def __init__(self, master):
super().__init__(master, padding=(20, 10))
self.pack(fill=BOTH, expand=YES)
# form variables
self.name = ttk.StringVar(value="")
self.address = ttk.StringVar(value="")
self.phone = ttk.StringVar(value="")
# form header
hdr_txt = "Please enter your contact information"
hdr = ttk.Label(master=self, text=hdr_txt, width=50)
hdr.pack(fill=X, pady=10)
# form entries
self.create_form_entry("name", self.name)
self.create_form_entry("address", self.address)
self.create_form_entry("phone", self.phone)
self.create_buttonbox()
def create_form_entry(self, label, variable):
"""Create a single form entry"""
container = ttk.Frame(self)
container.pack(fill=X, expand=YES, pady=5)
lbl = ttk.Label(master=container, text=label.title(), width=10)
lbl.pack(side=LEFT, padx=5)
ent = ttk.Entry(master=container, textvariable=variable)
ent.pack(side=LEFT, padx=5, fill=X, expand=YES)
def create_buttonbox(self):
"""Create the application buttonbox"""
container = ttk.Frame(self)
container.pack(fill=X, expand=YES, pady=(15, 10))
sub_btn = ttk.Button(
master=container,
text="Submit",
command=self.on_submit,
bootstyle=SUCCESS,
width=6,
)
sub_btn.pack(side=RIGHT, padx=5)
sub_btn.focus_set()
cnl_btn = ttk.Button(
master=container,
text="Cancel",
command=self.on_cancel,
bootstyle=DANGER,
width=6,
)
cnl_btn.pack(side=RIGHT, padx=5)
def on_submit(self):
"""Print the contents to console and return the values."""
print("Name:", self.name.get())
print("Address:", self.address.get())
print("Phone:", self.phone.get())
return self.name.get(), self.address.get(), self.phone.get()
def on_cancel(self):
"""Cancel and close the application."""
self.quit()
if __name__ == "__main__":
app = ttk.Window("Data Entry", "superhero", resizable=(False, False))
DataEntryForm(app)
app.mainloop()
代码解释下:
def create_form_entry(self, label, variable):
"""
创建一个单独的表单输入框
:param label: 输入框标签文本
:param variable: 输入框关联的变量
"""
# 创建一个容器框架,用于放置标签和输入框
container = ttk.Frame(self)
container.pack(fill=X, expand=YES, pady=5)
# 创建标签,并设置标签文本和宽度
lbl = ttk.Label(master=container, text=label.title(), width=10)
lbl.pack(side=LEFT, padx=5)
# 创建输入框,并设置关联的变量
ent = ttk.Entry(master=container, textvariable=variable)
ent.pack(side=LEFT, padx=5, fill=X, expand=YES)
def create_buttonbox(self):
"""Create the application buttonbox"""
# 创建一个容器框架,用于放置按钮
container = ttk.Frame(self)
container.pack(fill=X, expand=YES, pady=(15, 10))
# 创建 "Submit" 按钮,并设置样式和命令
sub_btn = ttk.Button(
master=container,
text="Submit",
command=self.on_submit,
bootstyle=SUCCESS,
width=6,
)
sub_btn.pack(side=RIGHT, padx=5)
sub_btn.focus_set() # 设置焦点在提交按钮上,以便键盘交互
# 创建 "Cancel" 按钮,并设置样式和命令
cnl_btn = ttk.Button(
master=container,
text="Cancel",
command=self.on_cancel,
bootstyle=DANGER,
width=6,
)
cnl_btn.pack(side=RIGHT, padx=5) # 将取消按钮放置在提交按钮右侧
写一个注册页面:
import ttkbootstrap as tk # 导入ttkbootstrap库
import ttkbootstrap.constants as tkc # 导入ttkbootstrap的常量
root = tk.Window(themename='litera') # 创建主窗口,使用litera主题
root.geometry('600x500+500+500') # 设置窗口大小和位置 300x500 表示窗口的大小为宽度 300 像素,高度 500 像素。 +500+500 表示窗口的位置,其中第一个 +500 表示窗口距离屏幕左侧的水平距离为 500 像素,第二个 +500 表示窗口距离屏幕顶部的垂直距离为 500 像素。
root.title("注册页面") # 设置窗口标题
root.wm_attributes("-topmost", 1) # 窗口置顶显示
username_str_var = tk.StringVar() # 创建用户名的StringVar
password_str_var = tk.StringVar() # 创建密码的StringVar
gender_str_var = tk.IntVar() # 创建性别的IntVar
hobby_list = [ # 创建兴趣列表,每个元素为一个IntVar和一个字符串
[tk.IntVar(), '吃'],
[tk.IntVar(), '喝'],
[tk.IntVar(), '玩'],
[tk.IntVar(), '乐']
]
tk.Label(root,width=10).grid() # 空白标签,占位用
tk.Label(root,text='用户名:').grid(row=1,column=1,sticky=tk.W,pady=10) # 用户名标签
tk.Entry(root,textvariable=username_str_var).grid(row=1,column=2,sticky=tk.W) # 用户名输入框
tk.Label(root,text='密码:').grid(row=2,column=1,sticky=tk.W,pady=10) # 密码标签
tk.Entry(root,textvariable=password_str_var).grid(row=2,column=2,sticky=tk.W) # 密码输入框
tk.Label(root,text='性别:').grid(row=4,column=1,sticky=tk.W,pady=10) # 性别标签
radio_frame=tk.Frame() # 创建性别单选按钮的框架
radio_frame.grid(row=4,column=2,sticky=tk.W) # 放置性别单选按钮的框架
tk.Radiobutton(radio_frame,text='男',variable=gender_str_var,value=1).pack(side=tk.LEFT,padx=5) # 男性单选按钮
tk.Radiobutton(radio_frame,text='女',variable=gender_str_var,value=0).pack(side=tk.LEFT,padx=5) # 女性单选按钮
tk.Radiobutton(radio_frame,text='保密',variable=gender_str_var,value=-1).pack(side=tk.LEFT,padx=5) # 保密单选按钮
tk.Label(root,text='兴趣:').grid(row=6,column=1,sticky=tk.W,pady=10) # 兴趣标签
check_frame=tk.Frame() # 创建兴趣多选框的框架
check_frame.grid(row=6,column=2,sticky=tk.W) # 放置兴趣多选框的框架
tk.Checkbutton(check_frame,text=hobby_list[0][1],variable=hobby_list[0][0]).pack(side=tk.LEFT,padx=5) # 吃的多选框
tk.Checkbutton(check_frame,text=hobby_list[1][1],variable=hobby_list[1][0],bootstyle="square-toggle").pack(side=tk.LEFT,padx=5) # 喝的多选框
tk.Checkbutton(check_frame,text=hobby_list[2][1],variable=hobby_list[2][0]).pack(side=tk.LEFT,padx=5) # 玩的多选框
tk.Checkbutton(check_frame,text=hobby_list[3][1],variable=hobby_list[3][0]).pack(side=tk.LEFT,padx=5) # 乐的多选框
tk.Label(root,text='生日:').grid(row=7,column=1,sticky=tk.W,padx=10) # 生日标签
data_entry=tk.DateEntry() # 创建日期选择框
data_entry.grid(row=7,column=2,sticky=tk.W,pady=10) # 放置日期选择框
print(data_entry.entry.get()) # 打印日期选择框的值(这里打印默认值)
tk.Label(root,text="").grid(row=9,column=2,sticky=tk.W) # 空白标签,占位用
button=tk.Button(root,text='提交',width=20) # 创建提交按钮
button.grid(row=10,column=2,sticky=tk.W) # 放置提交按钮
def get_info(): # 获取用户信息的函数
data={ # 将用户信息存储在字典中
'username':username_str_var.get(),
'password': password_str_var.get(),
'gender':gender_str_var.get(),
'hobby':[h for v,h in hobby_list if v.get()], # 获取选中的兴趣
'birth':data_entry.entry.get() # 获取选择的生日
}
print(data) # 打印用户信息
button.config(command=get_info) # 设置提交按钮的命令为get_info函数
root.mainloop() # 运行窗口主循环
计算器:
import ttkbootstrap as ttk # 导入ttkbootstrap库
from ttkbootstrap.constants import * # 导入ttkbootstrap的常量
class Calculator(ttk.Frame): # 创建计算器类,继承自ttk.Frame
def __init__(self, master, **kwargs): # 初始化方法
super().__init__(master, padding=10, **kwargs) # 调用父类的初始化方法,并设置padding
ttk.Style().configure("TButton", font="TkFixedFont 12") # 配置按钮的字体样式
self.pack(fill=BOTH, expand=YES) # 放置计算器界面
# 创建StringVar和DoubleVar对象用于存储数据
self.digitsvar = ttk.StringVar(value=0)
self.xnum = ttk.DoubleVar()
self.ynum = ttk.DoubleVar()
self.operator = ttk.StringVar(value="+")
# 检查bootstyle参数,设置按钮样式
if "bootstyle" in kwargs:
self.bootstyle = kwargs.pop("bootstyle")
else:
self.bootstyle = None
# 创建数字显示区域和数字按钮区域
self.create_num_display()
self.create_num_pad()
def create_num_display(self): # 创建数字显示区域
# 创建数字显示区域的框架
container = ttk.Frame(master=self, padding=2, bootstyle=self.bootstyle)
container.pack(fill=X, pady=20) # 放置数字显示区域框架
digits = ttk.Label( # 创建数字显示的Label
master=container,
font="TkFixedFont 14",
textvariable=self.digitsvar,
anchor=E,
)
digits.pack(fill=X) # 放置数字显示的Label
def create_num_pad(self): # 创建数字按钮区域
# 创建数字按钮区域的框架
container = ttk.Frame(master=self, padding=2, bootstyle=self.bootstyle)
container.pack(fill=BOTH, expand=YES) # 放置数字按钮区域框架
# 数字按钮的布局矩阵
matrix = [
("%", "C", "CE", "/"),
(7, 8, 9, "*"),
(4, 5, 6, "-"),
(1, 2, 3, "+"),
("±", 0, ".", "="),
]
for i, row in enumerate(matrix): # 遍历矩阵的行
container.rowconfigure(i, weight=1)
for j, num_txt in enumerate(row): # 遍历矩阵的列
container.columnconfigure(j, weight=1)
btn = self.create_button(master=container, text=num_txt) # 创建按钮
btn.grid(row=i, column=j, sticky=NSEW, padx=1, pady=1) # 放置按钮
def create_button(self, master, text): # 创建按钮的方法
# 根据按钮文本确定按钮样式
if text == "=":
bootstyle = SUCCESS
elif not isinstance(text, int):
bootstyle = SECONDARY
else:
bootstyle = PRIMARY
return ttk.Button( # 创建按钮
master=master,
text=text,
command=lambda x=text: self.on_button_pressed(x), # 按钮点击事件绑定
bootstyle=bootstyle,
width=2,
padding=10,
)
def reset_variables(self): # 重置变量方法
self.xnum.set(value=0)
self.ynum.set(value=0)
self.operator.set("+")
def on_button_pressed(self, txt): # 处理按钮点击事件的方法
"""Handles and routes all button press events."""
display = self.digitsvar.get()
# remove operator from screen after button is pressed
if len(display) > 0:
if display[0] in ["/", "*", "-", "+"]:
display = display[1:]
if txt in ["CE", "C"]: # 清除操作
self.digitsvar.set("")
self.reset_variables()
elif isinstance(txt, int): # 数字按钮按下
self.press_number(display, txt)
elif txt == "." and "." not in display: # 小数点按钮按下
self.digitsvar.set(f"{display}{txt}")
elif txt == "±": # 正负号按钮按下
self.press_inverse(display)
elif txt in ["/", "*", "-", "+"]: # 运算符按钮按下
self.press_operator(txt)
elif txt == "=": # 等于号按钮按下
self.press_equals(display)
def press_number(self, display, txt): # 处理数字按钮按下的方法
"""A digit button is pressed"""
if display == "0":
self.digitsvar.set(txt)
else:
self.digitsvar.set(f"{display}{txt}")
def press_inverse(self, display): # 处理正负号按钮按下的方法
"""The inverse number button is pressed"""
if display.startswith("-"):
if len(display) > 1:
self.digitsvar.set(display[1:])
else:
self.digitsvar.set("")
else:
self.digitsvar.set(f"-{display}")
def press_operator(self, txt): # 处理运算符按钮按下的方法
"""An operator button is pressed"""
self.operator.set(txt)
display = float(self.digitsvar.get())
if self.xnum.get() != 0:
self.ynum.set(display)
else:
self.xnum.set(display)
self.digitsvar.set(txt)
def press_equals(self, display): # 处理等于号按钮按下的方法
"""The equals button is pressed."""
if self.xnum.get() != 0:
self.ynum.set(display)
else:
self.xnum.set(display)
x = self.xnum.get()
y = self.ynum.get()
op = self.operator.get()
if all([x, y, op]):
result = eval(f"{x}{op}{y}")
self.digitsvar.set(result)
self.reset_variables()
if __name__ == "__main__":
app = ttk.Window(
title="Calculator",
themename="flatly",
size=(650, 450),
resizable=(False, False),
)
Calculator(app) # 创建计算器实例
app.mainloop() # 运行主循环
折叠框架:
注意这里的图片的地址是同级别下:
from pathlib import Path # 导入Path模块,用于处理文件路径
import ttkbootstrap as ttk # 导入ttkbootstrap库
from ttkbootstrap.constants import * # 导入ttkbootstrap的常量
from ttkbootstrap.style import Bootstyle # 导入Bootstyle类
IMG_PATH = Path(__file__).parent / 'assets' # 图片路径
class CollapsingFrame(ttk.Frame): # 创建CollapsingFrame类,继承自ttk.Frame
"""A collapsible frame widget that opens and closes with a click."""
def __init__(self, master, **kwargs): # 初始化方法
super().__init__(master, **kwargs) # 调用父类的初始化方法
self.columnconfigure(0, weight=1) # 配置列权重
self.cumulative_rows = 0 # 初始累计行数
# widget images
self.images = [
ttk.PhotoImage(file=IMG_PATH/'img1.png'), # 图片1
ttk.PhotoImage(file=IMG_PATH/'img1.png') # 图片2
]
def add(self, child, title="", bootstyle=PRIMARY, **kwargs): # 添加子部件方法
"""Add a child to the collapsible frame
Parameters:
child (Frame):
The child frame to add to the widget.
title (str):
The title appearing on the collapsible section header.
bootstyle (str):
The style to apply to the collapsible section header.
**kwargs (Dict):
Other optional keyword arguments.
"""
if child.winfo_class() != 'TFrame': # 检查子部件类型
return
style_color = Bootstyle.ttkstyle_widget_color(bootstyle) # 获取样式颜色
frm = ttk.Frame(self, bootstyle=style_color) # 创建框架
frm.grid(row=self.cumulative_rows, column=0, sticky=EW) # 放置框架
# header title
header = ttk.Label( # 创建标题Label
master=frm,
text=title,
bootstyle=(style_color, INVERSE)
)
if kwargs.get('textvariable'): # 如果有文本变量
header.configure(textvariable=kwargs.get('textvariable')) # 配置文本变量
header.pack(side=LEFT, fill=BOTH, padx=10) # 放置标题Label
# header toggle button
def _func(c=child): return self._toggle_open_close(c) # 创建按钮点击事件函数
btn = ttk.Button( # 创建按钮
master=frm,
image=self.images[0],
bootstyle=style_color,
command=_func
)
btn.pack(side=RIGHT) # 放置按钮
# assign toggle button to child so that it can be toggled
child.btn = btn # 将按钮分配给子部件
child.grid(row=self.cumulative_rows + 1, column=0, sticky=NSEW) # 放置子部件
# increment the row assignment
self.cumulative_rows += 2 # 更新累计行数
def _toggle_open_close(self, child): # 打开或关闭部件方法
"""Open or close the section and change the toggle button
image accordingly.
Parameters:
child (Frame):
The child element to add or remove from grid manager.
"""
if child.winfo_viewable(): # 如果部件可见
child.grid_remove() # 移除部件
child.btn.configure(image=self.images[1]) # 修改按钮图标
else:
child.grid() # 放置部件
child.btn.configure(image=self.images[0]) # 修改按钮图标
if __name__ == '__main__':
app = ttk.Window(minsize=(300, 1)) # 创建窗口
cf = CollapsingFrame(app) # 创建CollapsingFrame实例
cf.pack(fill=BOTH) # 放置CollapsingFrame
# option group 1
group1 = ttk.Frame(cf, padding=10) # 创建子部件框架1
for x in range(5): # 添加复选按钮
ttk.Checkbutton(group1, text=f'Option {x + 1}').pack(fill=X)
cf.add(child=group1, title='Option Group 1') # 添加到CollapsingFrame中
# option group 2
group2 = ttk.Frame(cf, padding=10) # 创建子部件框架2
for x in range(5): # 添加复选按钮
ttk.Checkbutton(group2, text=f'Option {x + 1}').pack(fill=X)
cf.add(group2, title='Option Group 2', bootstyle=DANGER) # 添加到CollapsingFrame中,指定样式
# option group 3
group3 = ttk.Frame(cf, padding=10) # 创建子部件框架3
for x in range(5): # 添加复选按钮
ttk.Checkbutton(group3, text=f'Option {x + 1}').pack(fill=X)
cf.add(group3, title='Option Group 3', bootstyle=SUCCESS) # 添加到CollapsingFrame中,指定样式
app.mainloop() # 运行主循环
文件备份使用程序:
"""
Author: Israel Dryer
Modified: 2021-12-12
Adapted from: http://www.leo-backup.com/screenshots.shtml
"""
from datetime import datetime
from random import choices
import ttkbootstrap as ttk
from ttkbootstrap.style import Bootstyle
from tkinter.filedialog import askdirectory
from ttkbootstrap.dialogs import Messagebox
from ttkbootstrap.constants import *
from tkinter.scrolledtext import ScrolledText
from pathlib import Path
PATH = Path(__file__).parent / 'assets'
class BackMeUp(ttk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.pack(fill=BOTH, expand=YES)
image_files = {
'properties-dark': 'icons8_settings_24px.png',
'properties-light': 'icons8_settings_24px_2.png',
'add-to-backup-dark': 'icons8_add_folder_24px.png',
'add-to-backup-light': 'icons8_add_book_24px.png',
'stop-backup-dark': 'icons8_cancel_24px.png',
'stop-backup-light': 'icons8_cancel_24px_1.png',
'play': 'icons8_play_24px_1.png',
'refresh': 'icons8_refresh_24px_1.png',
'stop-dark': 'icons8_stop_24px.png',
'stop-light': 'icons8_stop_24px_1.png',
'opened-folder': 'icons8_opened_folder_24px.png',
'logo': 'backup.png'
}
self.photoimages = []
imgpath = Path(__file__).parent / 'assets'
for key, val in image_files.items():
_path = imgpath / val
self.photoimages.append(ttk.PhotoImage(name=key, file=_path))
# buttonbar
buttonbar = ttk.Frame(self, style='primary.TFrame')
buttonbar.pack(fill=X, pady=1, side=TOP)
## new backup
_func = lambda: Messagebox.ok(message='Adding new backup')
btn = ttk.Button(
master=buttonbar, text='New backup set',
image='add-to-backup-light',
compound=LEFT,
command=_func
)
btn.pack(side=LEFT, ipadx=5, ipady=5, padx=(1, 0), pady=1)
## backup
_func = lambda: Messagebox.ok(message='Backing up...')
btn = ttk.Button(
master=buttonbar,
text='Backup',
image='play',
compound=LEFT,
command=_func
)
btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)
## refresh
_func = lambda: Messagebox.ok(message='Refreshing...')
btn = ttk.Button(
master=buttonbar,
text='Refresh',
image='refresh',
compound=LEFT,
command=_func
)
btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)
## stop
_func = lambda: Messagebox.ok(message='Stopping backup.')
btn = ttk.Button(
master=buttonbar,
text='Stop',
image='stop-light',
compound=LEFT,
command=_func
)
btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)
## settings
_func = lambda: Messagebox.ok(message='Changing settings')
btn = ttk.Button(
master=buttonbar,
text='Settings',
image='properties-light',
compound=LEFT,
command=_func
)
btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)
# left panel
left_panel = ttk.Frame(self, style='bg.TFrame')
left_panel.pack(side=LEFT, fill=Y)
## backup summary (collapsible)
bus_cf = CollapsingFrame(left_panel)
bus_cf.pack(fill=X, pady=1)
## container
bus_frm = ttk.Frame(bus_cf, padding=5)
bus_frm.columnconfigure(1, weight=1)
bus_cf.add(
child=bus_frm,
title='Backup Summary',
bootstyle=SECONDARY)
## destination
lbl = ttk.Label(bus_frm, text='Destination:')
lbl.grid(row=0, column=0, sticky=W, pady=2)
lbl = ttk.Label(bus_frm, textvariable='destination')
lbl.grid(row=0, column=1, sticky=EW, padx=5, pady=2)
self.setvar('destination', 'd:/test/')
## last run
lbl = ttk.Label(bus_frm, text='Last Run:')
lbl.grid(row=1, column=0, sticky=W, pady=2)
lbl = ttk.Label(bus_frm, textvariable='lastrun')
lbl.grid(row=1, column=1, sticky=EW, padx=5, pady=2)
self.setvar('lastrun', '14.06.2021 19:34:43')
## files Identical
lbl = ttk.Label(bus_frm, text='Files Identical:')
lbl.grid(row=2, column=0, sticky=W, pady=2)
lbl = ttk.Label(bus_frm, textvariable='filesidentical')
lbl.grid(row=2, column=1, sticky=EW, padx=5, pady=2)
self.setvar('filesidentical', '15%')
## section separator
sep = ttk.Separator(bus_frm, bootstyle=SECONDARY)
sep.grid(row=3, column=0, columnspan=2, pady=10, sticky=EW)
## properties button
_func = lambda: Messagebox.ok(message='Changing properties')
bus_prop_btn = ttk.Button(
master=bus_frm,
text='Properties',
image='properties-dark',
compound=LEFT,
command=_func,
bootstyle=LINK
)
bus_prop_btn.grid(row=4, column=0, columnspan=2, sticky=W)
## add to backup button
_func = lambda: Messagebox.ok(message='Adding to backup')
add_btn = ttk.Button(
master=bus_frm,
text='Add to backup',
image='add-to-backup-dark',
compound=LEFT,
command=_func,
bootstyle=LINK
)
add_btn.grid(row=5, column=0, columnspan=2, sticky=W)
# backup status (collapsible)
status_cf = CollapsingFrame(left_panel)
status_cf.pack(fill=BOTH, pady=1)
## container
status_frm = ttk.Frame(status_cf, padding=10)
status_frm.columnconfigure(1, weight=1)
status_cf.add(
child=status_frm,
title='Backup Status',
bootstyle=SECONDARY
)
## progress message
lbl = ttk.Label(
master=status_frm,
textvariable='prog-message',
font='Helvetica 10 bold'
)
lbl.grid(row=0, column=0, columnspan=2, sticky=W)
self.setvar('prog-message', 'Backing up...')
## progress bar
pb = ttk.Progressbar(
master=status_frm,
variable='prog-value',
bootstyle=SUCCESS
)
pb.grid(row=1, column=0, columnspan=2, sticky=EW, pady=(10, 5))
self.setvar('prog-value', 71)
## time started
lbl = ttk.Label(status_frm, textvariable='prog-time-started')
lbl.grid(row=2, column=0, columnspan=2, sticky=EW, pady=2)
self.setvar('prog-time-started', 'Started at: 14.06.2021 19:34:56')
## time elapsed
lbl = ttk.Label(status_frm, textvariable='prog-time-elapsed')
lbl.grid(row=3, column=0, columnspan=2, sticky=EW, pady=2)
self.setvar('prog-time-elapsed', 'Elapsed: 1 sec')
## time remaining
lbl = ttk.Label(status_frm, textvariable='prog-time-left')
lbl.grid(row=4, column=0, columnspan=2, sticky=EW, pady=2)
self.setvar('prog-time-left', 'Left: 0 sec')
## section separator
sep = ttk.Separator(status_frm, bootstyle=SECONDARY)
sep.grid(row=5, column=0, columnspan=2, pady=10, sticky=EW)
## stop button
_func = lambda: Messagebox.ok(message='Stopping backup')
btn = ttk.Button(
master=status_frm,
text='Stop',
image='stop-backup-dark',
compound=LEFT,
command=_func,
bootstyle=LINK
)
btn.grid(row=6, column=0, columnspan=2, sticky=W)
## section separator
sep = ttk.Separator(status_frm, bootstyle=SECONDARY)
sep.grid(row=7, column=0, columnspan=2, pady=10, sticky=EW)
# current file message
lbl = ttk.Label(status_frm, textvariable='current-file-msg')
lbl.grid(row=8, column=0, columnspan=2, pady=2, sticky=EW)
self.setvar('current-file-msg', 'Uploading: d:/test/settings.txt')
# logo
lbl = ttk.Label(left_panel, image='logo', style='bg.TLabel')
lbl.pack(side='bottom')
# right panel
right_panel = ttk.Frame(self, padding=(2, 1))
right_panel.pack(side=RIGHT, fill=BOTH, expand=YES)
## file input
browse_frm = ttk.Frame(right_panel)
browse_frm.pack(side=TOP, fill=X, padx=2, pady=1)
file_entry = ttk.Entry(browse_frm, textvariable='folder-path')
file_entry.pack(side=LEFT, fill=X, expand=YES)
btn = ttk.Button(
master=browse_frm,
image='opened-folder',
bootstyle=(LINK, SECONDARY),
command=self.get_directory
)
btn.pack(side=RIGHT)
## Treeview
tv = ttk.Treeview(right_panel, show='headings', height=5)
tv.configure(columns=(
'name', 'state', 'last-modified',
'last-run-time', 'size'
))
tv.column('name', width=150, stretch=True)
for col in ['last-modified', 'last-run-time', 'size']:
tv.column(col, stretch=False)
for col in tv['columns']:
tv.heading(col, text=col.title(), anchor=W)
tv.pack(fill=X, pady=1)
## scrolling text output
scroll_cf = CollapsingFrame(right_panel)
scroll_cf.pack(fill=BOTH, expand=YES)
output_container = ttk.Frame(scroll_cf, padding=1)
_value = 'Log: Backing up... [Uploading file: D:/sample_file_35.txt]'
self.setvar('scroll-message', _value)
st = ScrolledText(output_container)
st.pack(fill=BOTH, expand=YES)
scroll_cf.add(output_container, textvariable='scroll-message')
# seed with some sample data
## starting sample directory
file_entry.insert(END, 'D:/text/myfiles/top-secret/samples/')
## treeview and backup logs
for x in range(20, 35):
result = choices(['Backup Up', 'Missed in Destination'])[0]
st.insert(END, f'19:34:{x}\t\t Uploading: D:/file_{x}.txt\n')
st.insert(END, f'19:34:{x}\t\t Upload {result}.\n')
timestamp = datetime.now().strftime('%d.%m.%Y %H:%M:%S')
tv.insert('', END, x,
values=(f'sample_file_{x}.txt',
result, timestamp, timestamp,
f'{int(x // 3)} MB')
)
tv.selection_set(20)
def get_directory(self):
"""Open dialogue to get directory and update variable"""
self.update_idletasks()
d = askdirectory()
if d:
self.setvar('folder-path', d)
class CollapsingFrame(ttk.Frame):
"""A collapsible frame widget that opens and closes with a click."""
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.columnconfigure(0, weight=1)
self.cumulative_rows = 0
# widget images
self.images = [
ttk.PhotoImage(file=PATH/'icons8_double_up_24px.png'),
ttk.PhotoImage(file=PATH/'icons8_double_right_24px.png')
]
def add(self, child, title="", bootstyle=PRIMARY, **kwargs):
"""Add a child to the collapsible frame
Parameters:
child (Frame):
The child frame to add to the widget.
title (str):
The title appearing on the collapsible section header.
bootstyle (str):
The style to apply to the collapsible section header.
**kwargs (Dict):
Other optional keyword arguments.
"""
if child.winfo_class() != 'TFrame':
return
style_color = Bootstyle.ttkstyle_widget_color(bootstyle)
frm = ttk.Frame(self, bootstyle=style_color)
frm.grid(row=self.cumulative_rows, column=0, sticky=EW)
# header title
header = ttk.Label(
master=frm,
text=title,
bootstyle=(style_color, INVERSE)
)
if kwargs.get('textvariable'):
header.configure(textvariable=kwargs.get('textvariable'))
header.pack(side=LEFT, fill=BOTH, padx=10)
# header toggle button
def _func(c=child): return self._toggle_open_close(c)
btn = ttk.Button(
master=frm,
image=self.images[0],
bootstyle=style_color,
command=_func
)
btn.pack(side=RIGHT)
# assign toggle button to child so that it can be toggled
child.btn = btn
child.grid(row=self.cumulative_rows + 1, column=0, sticky=NSEW)
# increment the row assignment
self.cumulative_rows += 2
def _toggle_open_close(self, child):
"""Open or close the section and change the toggle button
image accordingly.
Parameters:
child (Frame):
The child element to add or remove from grid manager.
"""
if child.winfo_viewable():
child.grid_remove()
child.btn.configure(image=self.images[1])
else:
child.grid()
child.btn.configure(image=self.images[0])
if __name__ == '__main__':
app = ttk.Window("Back Me Up")
BackMeUp(app)
app.mainloop()
布局管理器:
这些布局管理器各自有不同的特点和用法,可以根据布局需求选择合适的管理器。
pack()
布局管理器:
- 使用
pack()
方法可以简单地将小部件按照水平或垂直方向依次排列。- 可以通过
side
参数控制小部件的放置方向(LEFT、RIGHT、TOP、BOTTOM)。- 可以使用
fill
参数进行填充(BOTH、X、Y、NONE)。- 可以通过
expand
参数来指定是否扩展小部件以填充剩余空间。import tkinter as tk root = tk.Tk() # 使用 pack() 布局管理器放置三个按钮 btn1 = tk.Button(root, text="Button 1") btn1.pack(side=tk.LEFT) # 左侧排列 btn2 = tk.Button(root, text="Button 2") btn2.pack(side=tk.LEFT, padx=10) # 左侧排列,间距为 10 像素 btn3 = tk.Button(root, text="Button 3") btn3.pack(side=tk.RIGHT) # 右侧排列 root.mainloop()
grid()
布局管理器:
- 使用
grid()
方法可以将小部件按照网格状的行和列放置。- 可以通过
row
和column
参数来指定小部件所在的行和列。- 可以使用
rowspan
和columnspan
参数来指定小部件所占据的行数和列数。- 可以使用
sticky
参数来指定小部件在单元格内的对齐方式。import tkinter as tk root = tk.Tk() # 使用 grid() 布局管理器放置三个按钮 btn1 = tk.Button(root, text="Button 1") btn1.grid(row=0, column=0) # 第 0 行,第 0 列 btn2 = tk.Button(root, text="Button 2") btn2.grid(row=0, column=1, padx=10) # 第 0 行,第 1 列,横向间距为 10 像素 btn3 = tk.Button(root, text="Button 3") btn3.grid(row=1, column=0, columnspan=2) # 第 1 行,跨越两列 root.mainloop()
place()
布局管理器:
- 使用
place()
方法可以根据绝对位置(相对于父容器的坐标)来放置小部件。- 可以通过
x
和y
参数指定小部件左上角的位置。- 可以使用
relx
和rely
参数来指定小部件相对于父容器的相对位置(百分比)。- 可以通过
width
和height
参数指定小部件的宽度和高度。import tkinter as tk root = tk.Tk() # 使用 place() 布局管理器放置三个标签 lbl1 = tk.Label(root, text="Label 1", bg="red") lbl1.place(x=50, y=50) # 左上角坐标 (50, 50) lbl2 = tk.Label(root, text="Label 2", bg="green") lbl2.place(x=100, y=100, width=100, height=50) # 左上角坐标 (100, 100),宽度 100,高度 50 lbl3 = tk.Label(root, text="Label 3", bg="blue") lbl3.place(relx=0.5, rely=0.5, anchor=tk.CENTER) # 相对于父容器居中放置 root.mainloop()
每种布局管理器都有其适用的场景和优势,需要根据具体的 GUI 设计需求来选择合适的布局管理器。