Skip to content

Create styles and themes for your Python desktop applications

License

Notifications You must be signed in to change notification settings

pyrustic/tkstyle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TkStyle

TkStyle is a Python library to style your GUI with a modern and pragmatic paradigm. It's part of the Pyrustic Open Ecosystem.

Figure

Cyberpunk theme made with TkStyle

Installation | Reference | Cyberpunk

Overview

TkStyle is a styling library for Tkinter that takes advantage of the autocomplete feature of IDEs so that you hardly need any prior Tkinter styling knowledge.

Each Tkinter widget has a set of options that allow you to define its look. For example, the tkinter.Button widget has the background and foreground options to change the background color and the text color on the button respectively.

TkStyle reproduces for each widget a class which bears the name of the widget and which has attributes representing the options to modify the appearance of the widget.

Here is the definition of the tkstyle.Button class which is supposed to modify the look of the tkinter.Button widget:

class Button(_Style):
    _CLASS_NAME = "Button"

    def __init__(self):
        super().__init__()
        self.activeBackground = None  # "#ececec"
        self.activeForeground = None  # "#000000"
        self.anchor = None  # "center"
        self.background = None  # "#d9d9d9"
        self.borderWidth = None  # 1
        self.compound = None  # "none"
        self.default = None  # "disabled"
        self.disabledForeground = None  # "#a3a3a3"
        self.font = None  # TkDefaultFont
        self.foreground = None  # "#000000"
        self.height = None  # 0
        self.highlightBackground = None  # "#d9d9d9"
        self.highlightColor = None  # "#000000"
        self.highlightThickness = None  # 1
        self.justify = None  # "center"
        self.padX = None  # 3
        self.padY = None  # 1
        self.relief = None  # "raised"
        self.repeatDelay = None  # 0
        self.repeatInterval = None  # 0
        self.state = None  # "normal"
        self.underline = None  # -1
        self.width = None  # 0
        self.wrapLength = None  # 0

Since a style is a Python object and thanks to the autocomplete feature of the IDEs, we no longer need to know by heart the options to change the look of widgets:

Figure

PyCharm's autocomplete

Since Tkinter is a mature GUI toolkit, it sometimes indicates the legal values of an option when you don't set the correct value. For example, if you don't know which values the relief option of the tkinter.Button widget accepts, you can put an arbitrary string like oops and at runtime Tkinter will raise an informative exception:

_tkinter.TclError: bad relief "oops": must be flat, groove, raised, ridge, solid, or sunken

These details combined make TkStyle a great modern paradigm for GUI styling that will save you a lot of time.

Style an instance of a widget

This code snippet shows how to style the instance of a widget:

import tkinter as tk
import tkstyle


root = tk.Tk()

# create and pack button_1
button_1 = tk.Button(root, text="Button 1")
button_1.pack(side=tk.LEFT, padx=5, pady=5)
# create and pack button_2
button_2 = tk.Button(root, text="Button 2")
button_2.pack(side=tk.LEFT, padx=5, pady=5)

# create the button_style
button_style = tkstyle.Button()
button_style.background = "tomato"
button_style.foreground = "white"
# apply the button_style to button_2
button_style.target(button_2)

# mainloop
root.mainloop()
Figure

Custom style applied to a button

Style a megawidget

A megawidget is a custom widget built with other native widgets.

For example, megawidget.Table is built with tkinter.Listbox, tkinter.Label, and tkinter.Scrollbar.

Figure

Table

Since a megawidget is not a native widget, it does not have a class that represents it in TkStyle.

So how do you style a megawidget ?

Well, megawidgets subclass tkinter.Frame or tkinter.Toplevel and TkStyle allows styles to be nested like Matryoshka dolls.

Here's how we can style the Listboxes that make up a Table:

import tkinter as tk
import tkstyle
from megawidget.table import Table


root = tk.Tk()

# table titles
titles = ("Username", "Password")
# table data
data = [("Jackieman", "Ydfj87mAfw"),
        ("Salvador", "Dqmpa644dga")]
# create and pack table
table = Table(root, titles=titles, data=data)
table.pack()

# create the listbox_style
listbox_style = tkstyle.Listbox()
listbox_style.background = "tomato"
listbox_style.foreground = "white"

# create the table_style
table_style = tkstyle.Frame()  # megawidgets subclass tk.Frame
# add the listbox_style to the table_style by specifying
# a XResources-like pattern that matches Listboxes: "*Listbox"
table_style.add(listbox_style, pattern="*Listbox")
# apply the table_style to table
table_style.target(table)

# mainloop
root.mainloop()
Figure

Table with a custom style

Create a theme

A theme is a collection of styles. While a style allows you to change the look of a particular (mega)widget instance, a theme allows you to apply a style to multiple (mega)widgets or also to a particular widget.

In this example, we'll create a theme that changes the look of all the buttons:

import tkinter as tk
import tkstyle


def get_button_style():
    # create the button_style
    button_style = tkstyle.Button()
    button_style.background = "tomato"
    button_style.foreground = "white"
    return button_style


def get_theme():
    # create the theme
    theme = tkstyle.Theme()
    # add the button_style to the theme
    button_style = get_button_style()
    theme.add(button_style, pattern="*Button")
    # the previous line could be this:
    # theme.add(button_style)
    # When you don't set a pattern, by default, the added style
    # class name prefixed with "*" is used as pattern
    return theme


root = tk.Tk()
theme = get_theme()
theme.target(root)

# create and pack button_1
button_1 = tk.Button(root, text="Button 1")
button_1.pack(side=tk.LEFT, padx=5, pady=5)
# create and pack button_2
button_2 = tk.Button(root, text="Button 2")
button_2.pack(side=tk.LEFT, padx=5, pady=5)

# mainloop
root.mainloop()
Figure

Custom theme for buttons

In this other example, the theme contains a style that targets a particular instance of tkinter.Button:

import tkinter as tk
import tkstyle


def get_button_style():
    # create the button_style
    button_style = tkstyle.Button()
    button_style.background = "tomato"
    button_style.foreground = "white"
    return button_style


def get_theme():
    # create the theme
    theme = tkstyle.Theme()
    # add the button_style to the theme
    button_style = get_button_style()
    theme.add(button_style, pattern="*mybutton")
    return theme


root = tk.Tk()
theme = get_theme()
theme.target(root)

# create and pack button_1
button_1 = tk.Button(root, text="Button 1")
button_1.pack(side=tk.LEFT, padx=5, pady=5)
# create and pack button_2
button_2 = tk.Button(root, name="mybutton", text="Button 2")
button_2.pack(side=tk.LEFT, padx=5, pady=5)

# mainloop
root.mainloop()
Figure

Custom theme but constrained to fit to button_2

So in short:

  • You don't need to know the options by heart to customize the look of a widget.
  • You don't need to learn yet another DSL.
  • TkStyle uses object oriented programming and takes advantage of your IDE.
  • There is a flag that allows TkStyle to forgive your mistakes (by default ignore_error = True), so your app doesn't crash just because you misspelled a color name.
  • You can determine which widgets used a given style since a style is just a Python object and therefore your IDE can locate usages.
  • You can use code organization best practices to manage the styling aspect of your project since TkStyle lets you use object oriented programming.

I invite you to check out the Cyberpunk dark theme which uses the TkStyle library.

$ pip install cyberpunk-theme
import tkinter as tk
from cyberpunk_theme import Cyberpunk
from cyberpunk_theme.widget.button import get_button_style_4


root = tk.Tk()
# apply the Cyberpunk theme to the GUI
cyberpunk_theme = Cyberpunk()
cyberpunk_theme.target(root)

# write your awesome code here
# ...
# ...

button = tk.Button(root, text="Button")
button.pack()

# do you need to set dynamically a specific style to a button ?
# there are 10 styles for buttons ! from the black to the red style !
button_style_4 = get_button_style_4()
button_style_4.target(button)

# mainloop
root.mainloop()

Installation

First time

Install for the first time:

$ pip install tkstyle

Upgrade

To upgrade TkStyle:

$ pip install tkstyle --upgrade --upgrade-strategy eager