Python ttkbootstrap学习

tkinter知识

记录一些tkinter库的知识。

pack布局

个人觉得布局其实是很负责的,因此这里对布局进行一个补充说明。

pack布局是根据添加组件的顺序依次排列所有组件

pack() 方法的参数有:side, fill, padx/pady, ipadx/ipady, anchor, expand

side: 决定组件停靠的方向,选项:left, right, top, bottom

  • 'top': 向上停靠 默认

  • 'bottom':向下停靠

  • 'left':向左停靠

  • 'right':向右停靠

ttk.Button(root, text="Button 1", bootstyle=SUCCESS).pack(side=RIGHT, padx=5, pady=10)
ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE)).pack(side=RIGHT, padx=5, pady=10)
image-20240827143331453

fill :表示填充分配空间:

  • x:水平方向填充
  • y:竖直方向填充
  • both:水平和竖直方向填充
  • none:不填充(默认值)

expand :表示是否填充父组件的额外空间

  • True:扩展整个空白区,使能 fill 属性
    • fill=X:当GUI窗体大小发生变化时,widget在X方向跟随GUI窗体变化
    • fill=Y:当GUI窗体大小发生变化时,widget在Y方向跟随GUI窗体变化
    • fill=BOTH:当GUI窗体大小发生变化时,widget在X、Y两方向跟随GUI窗体变化
  • False:关闭 fill属性
ttk.Button(root, text="Button 1", bootstyle=SUCCESS).pack(side=LEFT, fill=Y, padx=5, pady=10, expand=True)
ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE)).pack(side=LEFT, fill=X, padx=5, pady=10, expand=True)
image-20240827150008288

padx/pady: 组件外,组件跟邻近组件或窗体边界的距离(外边距),默认值:0

  • 水平方向上的外边距

  • 垂直方向上的外边距

    ttk.Button(root, text="Button 1", bootstyle=SUCCESS).pack(side=LEFT, fill=Y, padx=20, pady=20, expand=True)
    ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE)).pack(side=LEFT, fill=BOTH, padx=20, pady=20, expand=True)
    
image-20240827151406078

ipadx/ipady: 组件内,组件文本跟组件边界之间的距离(内边距),默认值:0

  • 水平方向上的内边距

  • 垂直方向上的内边距

    ttk.Button(root, text="Button 1", bootstyle=SUCCESS).pack(side=TOP, fill=Y, padx=20, pady=20, ipadx=10, ipady=10, expand=True)
    ttk.Button(root, text="Button 2", bootstyle=(INFO, OUTLINE)).pack(side=TOP, fill=BOTH, padx=20, ipadx=0, ipady=0, pady=20, expand=True)
    
    image-20240827151813100

pack布局示例

以ttkbootstrap的官方示例简单的数据输入 - ttkbootstrap为例进行说明,示例源码:

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

这里不讨论实现的内容,仅讨论是如何使用pack布局实现的。

image-20240828225626576

首先,最外层都是Frame,都是使用pack布局方式,side都是默认值TOP。

然后各个Frame内部根据需要可以调整布局方式,当前都是用的pack布局,但是side不一样,Name、Address、Phone都是LEFT,两个按钮为RIGHT。

按照这种方式,理论上使用pack都能实现大部分想要的布局。

ttkbootstrap

官网:ttkbootstrap - ttkbootstrap

GitHub:israel-dryer/ttkbootstrap:tkinter 的增压主题扩展,支持受 Bootstrap 启发的按需现代平面风格主题。 --- israel-dryer/ttkbootstrap: A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap. (github.com)

Themes

支持的主题

light主题:轻量级主题 - ttkbootstrap --- Light themes - ttkbootstrap

dark主题:深色主题 - ttkbootstrap --- Dark themes - ttkbootstrap

不同主题具体的颜色值,可以查看安装包的源代码路径themes\standard.py

查看所有的主题名字:

from ttkbootstrap.themes.standard import *
print (STANDARD_THEMES.keys())
# dict_keys(['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 'yeti', 'pulse', 'united', 'morph', 'journal', 'darkly', 'superhero', 'solar', 'cyborg', 'vapor', 'simplex', 'cerculean'])

配置主题的方式,创建window时通过themename指定:

root = ttk.Window(themename="darkly")

也可以直接运行python -m ttkbootstrap查看支持的主题:

image-20240828223913234

但是如果遇到运行时报错,比如:

PS C:\ProgramData\miniconda3> .\python.exe -m ttkbootstrap
Traceback (most recent call last):
  File "C:\ProgramData\miniconda3\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\ProgramData\miniconda3\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\__main__.py", line 298, in <module>
    bagel = setup_demo(app)
  File "C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\__main__.py", line 173, in setup_demo
    m = ttk.Meter(
  File "C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\widgets.py", line 718, in __init__
    self._setup_widget()
  File "C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\widgets.py", line 759, in _setup_widget
    self._draw_meter()
  File "C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\widgets.py", line 856, in _draw_meter
    img.resize((self._metersize, self._metersize), Image.CUBIC)
AttributeError: module 'PIL.Image' has no attribute 'CUBIC'

则可以通过将库中的源码更改以下,更改文件为C:\ProgramData\miniconda3\lib\site-packages\ttkbootstrap\widgets.py的856行,具体要看你的安装路径。

将:

img.resize((self._metersize, self._metersize), Image.CUBIC)

更改为:

img.resize((self._metersize, self._metersize), Image.LANCZOS)

动态更改主题

下面是一个示例,可以动态的在Menu菜单中更改窗口的主题:

import ttkbootstrap as ttk
from ttkbootstrap.constants import *

def setup_ui(style):
    root = style.master
    root.geometry('500x300')
    ttk.Button(root, text='Text', bootstyle='danger.TButton').pack(side=TOP, padx=0, pady=0)
    ttk.Button(root, text='Text', bootstyle='info.TButton').pack(side=TOP, padx=0, pady=0)

    # 创建顶级菜单
    top_menubar = ttk.Menu(root)
    submenu = ttk.Menu(top_menubar)
    top_menubar.add_cascade(label='Setting', menu=submenu)
    submenu.add_command(label='Font')
    # 创建Themes下的子菜单
    themes_submenu = ttk.Menu(submenu)
    submenu.add_cascade(label='Themes', menu=themes_submenu, underline=0)
    def make_adder(name):
        def adder():
            style.theme_use(name)
        return adder
    for each in ['cosmo', 'flatly', 'litera', 'minty', 'lumen', 'sandstone', 
                'yeti', 'pulse', 'united', 'morph', 'journal', 'darkly', 
                'superhero', 'solar', 'cyborg', 'vapor', 'simplex', 'cerculean']:
        if each == 'journal': # 在light主题和drak主题之间插入一个分割线
            themes_submenu.add_separator()
        themes_submenu.add_command(label=each, command=make_adder(each))
    root.config(menu=top_menubar)

    return root

if __name__ == "__main__":
    default_theme = 'cosmo'
    style = ttk.Style(theme=default_theme)
    root = setup_ui(style)
    root.mainloop()
GIF 2024-9-7 14-21-05

组件样式

ttkbootstrap 组件有数十种预定义样式bootstyle关键字可以修改组件typecolor

Style Colors 样式颜色

实际颜色值是为每个主题定义的,也是定义在themes\standard.py

比如默认主题litera,颜色定义为:

"litera": {
    "type": "light",
    "colors": {
        "primary": "#4582ec",
        "secondary": "#adb5bd",
        "success": "#02b875",
        "info": "#17a2b8",
        "warning": "#f0ad4e",
        "danger": "#d9534f",
        "light": "#F8F9FA",
        "dark": "#343A40",
        "bg": "#ffffff",
        "fg": "#343a40",
        "selectbg": "#adb5bd",
        "selectfg": "#ffffff",
        "border": "#bfbfbf",
        "inputfg": "#343a40",
        "inputbg": "#fff",
        "active": "#e5e5e5",
    },
},

示例:

import ttkbootstrap as ttk
from ttkbootstrap.constants import *

root = ttk.Window()

ttk.Button(root, text='primary', bootstyle=PRIMARY).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='secondary', bootstyle=SECONDARY).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='success', bootstyle=SUCCESS).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='info', bootstyle=INFO).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='warning', bootstyle=WARNING).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='danger', bootstyle=DANGER).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='light', bootstyle=LIGHT).pack(side=LEFT, padx=5, pady=5)
ttk.Button(root, text='dark', bootstyle=DARK).pack(side=LEFT, padx=5, pady=5)

root.mainloop()
image-20240827153430043
print (root.style.colors)
# (('primary', '#4582ec'), ('secondary', '#adb5bd'), ('success', '#02b875'), ('info', '#17a2b8'), ('warning', '#f0ad4e'), ('danger', '#d9534f'), ('light', '#F8F9FA'), ('dark', '#343A40'), ('bg', '#ffffff'), ('fg', '#343a40'), ('selectbg', '#adb5bd'), ('selectfg', '#ffffff'), ('border', '#bfbfbf'), ('inputfg', '#343a40'), ('inputbg', '#fff'), ('active', '#e5e5e5'))

Style Types 样式类型

bootstyle也可以制显示的窗口组件的类型

示例,创建两个button,但是类型不同:

import ttkbootstrap as ttk
from ttkbootstrap.constants import *

root = ttk.Window()

ttk.Button(root, text="Solid Button", bootstyle=SUCCESS).pack(side=LEFT, padx=5, pady=10)
ttk.Button(root, text="Outline Button", bootstyle=(SUCCESS, OUTLINE)).pack(side=LEFT, padx=5, pady=10)

root.mainloop()
image-20240827154934382

bootstyle 关键字用法

关键字的后台有一个正则表达式,用于解析输入并将其转换为适当的 ttk 样式。

可以传入字符串格式的keywords,也可以传入可迭代对象的keywords,例如使用 listtuples

以下所有变体都是合法的,并且将产生相同的样式:

  • "info-outline"
  • "info outline"
  • "outline-info"
  • ("info", "outline")
  • (INFO, OUTLINE)

一般直接使用宏的方式,宏定义在ttkbootstrap\constants.py,通过from ttkbootstrap.constants import *方式引入。

Style guide 风格指南

所有 ttkbootstrap 样式都使用已在 ttk 组件中添加。

Colors 颜色

颜色在所有组件上都可用,并且可以通过关键字进行更改。

以下是每种颜色的关键字:

image-20240827160010288

Button 按钮

按钮 - ttkbootstrap --- Button - ttkbootstrap

Button具有多种样式类型,默认情况下颜色为primary 。另外还支持 disabled state的特殊样式。

Solid button (默认) 实心按钮

默认样式具有纯色背景,悬停时会变亮,按下时会变暗。当 Widget 具有焦点时,按钮内会显示一个虚线环。

image-20240827160829936

Outline button 轮廓按钮

这种风格的特点是轮廓细腻。按下或悬停时,按钮将变为类似于默认按钮样式的纯色。当 Widget 具有焦点时,按钮内会显示一个虚线环。

image-20240827160922392

此样式具有一个具有标签外观的按钮。悬停时或按下时,文本颜色将更改为 info,以模拟 HTML 超链接上预期的效果。按下按钮时,会有轻微的移位缓解,给人一种运动的感觉。当 Widget 具有焦点时,按钮内会显示一个虚线环。

image-20240827161123479

Other button styles 其他按钮样式

Disabled button Disabled 禁用按钮

此样式不能通过bootstyle关键字应用,它是通过state参数配置的。

# create the button in a disabled state
Button(state="disabled")

示例

import ttkbootstrap as ttk
from ttkbootstrap.constants import *

root = ttk.Window()

ttk.Button(root, text="Solid Button", bootstyle=INFO).pack(side=LEFT, padx=5, pady=10)
ttk.Button(root, text="Outline Button", bootstyle=(SUCCESS, OUTLINE)).pack(side=LEFT, padx=5, pady=10)
ttk.Button(root, text="Link Button", bootstyle=(SUCCESS, LINK)).pack(side=LEFT, padx=5, pady=10)
ttk.Button(root, text="Link Button", bootstyle="success-link", state="disabled").pack(side=LEFT, padx=5, pady=10)

root.mainloop()
image-20240827161509543

DateEntry 日期条目

日期条目 - ttkbootstrap --- DateEntry - ttkbootstrap

组件实现源码:widgets.py

此 Widget 由两个 Widget:Entry和 Button。Entry 组件的行为与默认的 Entry 组件相同,日历Button的行为与默认的实心按钮相同。

按下日历按钮时,将调用 DatePickerPopup。应用于弹出窗口的默认颜色为 primary

此小组件还支持 disabled statereadonly stateinvalid state 的特殊样式。

image-20240827185359092

支持的参数

  • dateformat=r"%x":格式化字符串,支持的格式见Python strftime reference cheatsheet,比如一些常见的格式:"%Y-%m-%d"、"%Y-%m-%d"、'%Y/%m/%d-%H:%M:%S'
  • startdate=None:Specifies the first day of the week. 0=Monday, 1=Tuesday, etc...
  • startdate:显示日期,默认是当前的日期,支持输入格式为datetime
  • bootstyle="":配置组件的风格,支持primary, secondary, success, info, warning, danger, light, dark
  • state:只支持使用configure(self, cnf=None, **kwargs):函数配置,cnf参数不需要填写,**kwargs支持两个参数:
    • state='readonly':只读日期条目
    • state='disabled':禁用日期输入

获取值

获取日期也是通过configure(self, cnf=None, **kwargs):函数得到,cnf参数需要填写,支持下面的参数值:

  • "dateformat":获取dateformat参数值
  • "firstweekday":获取firstweekday参数
  • "startdate":获取startdate参数
  • "bootstyle":获取bootstyle参数
  • "state":获取Entry和Button的"state"值,返回格式为{"Entry": entrystate, "Button": buttonstate}
  • 获取日期:日期其实就是Entry的值,获取方式为cur_date = d.entry.get()

示例

import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from datetime import datetime, timedelta  

root = ttk.Window()

ttk.DateEntry(dateformat='%Y-%m-%d').pack(side=TOP, padx=5, pady=10)
ttk.DateEntry(dateformat='%Y/%m/%d').pack(side=TOP, padx=5, pady=10)
ttk.DateEntry(dateformat='%Y/%m/%d-%H:%M:%S-%w').pack(side=TOP, padx=5, pady=10)
ttk.DateEntry(dateformat='%Y/%m/%d', startdate=datetime.strptime("2000-1-1", "%Y-%m-%d").date()).pack(side=TOP, padx=5, pady=10)
ttk.DateEntry(bootstyle="INFO").pack(side=TOP, padx=5, pady=10)

# 不支持初始化的时候传值, 不知道为什么? d = ttk.DateEntry(state='readonly')
d = ttk.DateEntry(bootstyle="INFO")
d.configure(state='readonly')
print (d.configure(cnf='state'))
d.pack(side=TOP, padx=5, pady=10)

# 不支持初始化的时候传值, 不知道为什么? d = ttk.DateEntry(state='disabled')
d = ttk.DateEntry()
d.configure(state='disabled')
d.pack(side=TOP, padx=5, pady=10)
print (d.entry.get())  # 2024/8/27

root.mainloop()
image-20240827194947056

Labelframe 标签框

这个组件tkinter是没有的。

此 Widget 样式具有样式化的边框和标签。默认情况下,border 和 label 使用主题定义的边框和前景色默认值。使用所选颜色时,标签文本和边框都使用此颜色。

labelframe

参数:

  • STANDARD OPTIONS:class, cursor, style, takefocus

  • WIDGET-SPECIFIC OPTIONS:labelanchor, text, underline, padding, labelwidget, width, height

Labelframe 本质还是一个container,如果里面没有任何其它组件,则不会显示的,比如:

option_lf = ttk.Labelframe(root, text="Lable Frame", padding=15)
option_lf.pack(fill=X, expand=YES, anchor=N)
image-20240828232349605

当在container中创建了一个组件时,就会显示Labelframe 的text:

option_lf = ttk.Labelframe(root, text="Lable Frame", padding=15)
option_lf.pack(fill=X, expand=YES, anchor=N)
path_lbl = ttk.Label(option_lf, text="Path", width=8)
path_lbl.pack(side=LEFT, padx=(15, 0))
image-20240828232453283

Gallery

这里是将官方的gallery进行简化得到的一些示例。

官方示例:图库 - ttkbootstrap --- Gallery - ttkbootstrap

Collapsing Frame折叠框架

折叠框架 - ttkbootstrap --- Collapsing Frame - ttkbootstrap

file search image example

notebook

PC 清洁工 - ttkbootstrap --- PC Cleaner - ttkbootstrap

image-20240908002230428
posted @ 2024-09-08 21:47  zhengcixi  阅读(364)  评论(0编辑  收藏  举报
回到顶部