2
\$\begingroup\$

I have built a small tkinter program that I want to have a few different themes it can change between. In order to dynamically change the colours/font for each widget I have subclassed tkinter, and I am just wondering if there is any smarter way of doing this? I am aware that ttk can do something similar, but then as I understand it you are forced to use one of the ttk themes, rather than the standard tkinter widget design, which I prefer to avoid

Example for the button widget:

class Button(tk.Button):
    objects = []

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        self.__class__.objects.append(self)

    @classmethod
    def set_config(cls, **val):
        for obj in cls.objects:
            obj.config(val)

    def destroy(self):
        cur_obj = next(idx for idx, x in enumerate(self.objects) if x.bindtags() == self.bindtags())
        del self.__class__.objects[cur_obj]
        super().destroy()

A full example of the running code has been included below.
This is not my current implementation, but just a minimal example to show how the color changer works in practice. NB: The tk_dynamic module contains the subclasses for each widget in the same spirit as the one for Button, shown above.

from utils import tk_dynamic as tkd
from tkinter import ttk
import tkinter as tk

themes = ['light', 'dark']
theme_map = {
    'light': {
        tkd.Tk: dict(bg='#f0f0ed'),
        tkd.Label: dict(bg='#f0f0ed', fg='black'),
        tkd.Button: dict(bg='gray85', fg='black'),
        tkd.LabelFrame: dict(bg='#f0f0ed'),
        tkd.Radiobutton: dict(bg='gray85', selectcolor='white')
    },
    'dark': {
        tkd.Tk: dict(bg='black'),
        tkd.Label: dict(bg='black', fg='#ffffff'),
        tkd.Button: dict(bg='gray47', fg='#ffffff'),
        tkd.LabelFrame: dict(bg='black'),
        tkd.Radiobutton: dict(bg='gray47', selectcolor='gray78')
    }
}


class ThemedTkinter:
    def __init__(self, theme=themes[0]):
        self.root = tkd.Tk()

        self.active_theme = theme
        self.theme_var = tk.StringVar(value=theme)
        self.make_widgets()

        self._change_theme()
        self.root.mainloop()

    def make_widgets(self):
        tkd.Label(self.root, text='This is a test application').pack()

        # Create a test row
        lf1 = tkd.LabelFrame(self.root)
        lf1.pack(expand=True, fill=tk.X)
        svar = tk.IntVar()

        tkd.Label(lf1, text='Option 1').pack(side=tk.LEFT)
        tkd.Radiobutton(lf1, text='Off', indicatoron=False, value=0, variable=svar).pack(side=tk.RIGHT)
        tkd.Radiobutton(lf1, text='On', indicatoron=False, value=1, variable=svar).pack(side=tk.RIGHT)

        # Create choice to change theme
        lf2 = tkd.LabelFrame(self.root)
        lf2.pack(expand=True, fill=tk.X)

        tkd.Label(lf2, text='Active theme').pack(side=tk.LEFT)
        theme_choices = ttk.Combobox(lf2, textvariable=self.theme_var, state='readonly', values=themes)
        theme_choices.bind("<FocusOut>", lambda e: theme_choices.selection_clear())
        theme_choices.bind("<<ComboboxSelected>>", lambda e: self._change_theme())
        theme_choices.config(width=11)
        theme_choices.pack(side=tk.RIGHT, fill=tk.X, padx=2)

    def _change_theme(self):
        if self.theme_var.get() != self.active_theme:
            self.active_theme = self.theme_var.get()
            chosen_theme = theme_map[self.active_theme]
            for k, v in chosen_theme.items():
                k.set_config(**v)


ThemedTkinter()
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Please show the entire program. \$\endgroup\$ – Reinderien Nov 13 '20 at 16:05
  • \$\begingroup\$ The entire program is several thousand lines of code, but you can see it here github.com/oskros/MF_run_counter \$\endgroup\$ – oskros Nov 13 '20 at 16:27
  • \$\begingroup\$ yes added an example of the entire program now \$\endgroup\$ – oskros Nov 17 '20 at 14:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.