ttkbootstrap 学习笔记

官方网址: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()

布局管理器:

这些布局管理器各自有不同的特点和用法,可以根据布局需求选择合适的管理器。

  1. 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()
      

  2. grid() 布局管理器

    • 使用 grid() 方法可以将小部件按照网格状的行和列放置。
    • 可以通过 rowcolumn 参数来指定小部件所在的行和列。
    • 可以使用 rowspancolumnspan 参数来指定小部件所占据的行数和列数。
    • 可以使用 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()
      

  3. place() 布局管理器

    • 使用 place() 方法可以根据绝对位置(相对于父容器的坐标)来放置小部件。
    • 可以通过 xy 参数指定小部件左上角的位置。
    • 可以使用 relxrely 参数来指定小部件相对于父容器的相对位置(百分比)。
    • 可以通过 widthheight 参数指定小部件的宽度和高度。
      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 设计需求来选择合适的布局管理器。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值