Even better Tkinter theme changer
May 8, 2023 3:52:30 GMT -8
Post by Uncle Buddy on May 8, 2023 3:52:30 GMT -8
I keep making the same mistake with these colorizer schemes.
Every time I assume that the newest system couldn't be simpler.
So far, this assumption has always been wrong.
EDIT: The code has been replaced below since the code I originally showed in this post turned out to be a disaster after more testing and trying to use it in the main app.
The color theme switcher below is way simpler than any such beast that I've come up with yet.
In this system, a line is added right after each widget is made to denote its style. Comboboxes need special treatment as shown, since they are ttk widgets so they don't work compatibly with the original tkinter widgets' methods.
The highlighting system for hover effects has also been improved. The system is less convoluted in general and there is less boilerplate, less work for the dev user to do on each area where widgets are constructed.
Here is the new best-yet version of a system that instantly changes the color scheme for every widget in the app including those that do exist and those that don't exist yet. To get the needed imports, wait till I incorporate it into the main app and post all the code.
Every time I assume that the newest system couldn't be simpler.
So far, this assumption has always been wrong.
EDIT: The code has been replaced below since the code I originally showed in this post turned out to be a disaster after more testing and trying to use it in the main app.
The color theme switcher below is way simpler than any such beast that I've come up with yet.
In this system, a line is added right after each widget is made to denote its style. Comboboxes need special treatment as shown, since they are ttk widgets so they don't work compatibly with the original tkinter widgets' methods.
The highlighting system for hover effects has also been improved. The system is less convoluted in general and there is less boilerplate, less work for the dev user to do on each area where widgets are constructed.
Here is the new best-yet version of a system that instantly changes the color scheme for every widget in the app including those that do exist and those that don't exist yet. To get the needed imports, wait till I incorporate it into the main app and post all the code.
import tkinter as tk
from tkinter.ttk import Style, Combobox
import sqlite3
from files import global_db_path, get_current_file, tree_path
from styles import make_formats_dict
from widgets_compound import TabBook, Scrollbar
from scrolling import resize_scrolled_content
from query_strings import (
select_default_format_settings, select_current_colors_type,
select_color_scheme_by_id, select_format_font_scheme, update_current_color_scheme)
select_all_family_trees = "SELECT * FROM family_tree"
def make_family_trees_dict(treebard):
conn = sqlite3.connect(global_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute(select_all_family_trees)
trees = cur.fetchall()
KEYS = ("user_tree_title", "directory", "tree_is_open", "widget")
all_trees = {}
for tup in trees:
all_trees[tup[0]] = dict(zip(KEYS, tup[1:]))
for family_tree_id, values in dict(all_trees).items():
directory = values['directory']
filename = all_trees[family_tree_id]["filename"] = f"{directory}.tbd"
full_path = all_trees[family_tree_id]["full_path"] = f"{tree_path}/{directory}/{filename}"
cur.execute("ATTACH ? as tree", (full_path,))
cur.execute(select_current_colors_type)
colors_type_id = cur.fetchone()
cur.execute("DETACH tree")
formats = make_formats_dict(colors_type_id=colors_type_id)
all_trees[family_tree_id]["formats"] = formats
default_formats = make_formats_dict(colors_type_id=(1,))
all_trees[0] = {
"widget": treebard, "formats": default_formats,
"user_tree_title": "Treebard GPS"}
cur.close()
conn.close()
return all_trees
def get_styles(formats):
styles = {
"bg": {"bg": formats["bg"]},
"label": {
"bg": formats["bg"], "fg": formats["fg"],
"font": formats["output_font"]},
"entry": {
"bg": formats["highlight_bg"],
"fg": formats["fg"], "font": formats["input_font"]},
"button": {
"bg": formats["highlight_bg"], "fg": formats["fg"],
"font": formats["output_font"]},
"tab": {
"bg": formats["bg"],
"fg": formats["fg"],
"font": formats["tab_font"]},
"bgHead": {"bg": formats["head_bg"]},}
return styles
def bind_hoveree(widg, toplevel=None, hovercolor=None):
widg.bind(
"<Enter>",
lambda evt, toplevel=toplevel, hovercolor=hovercolor:
highlight(evt, toplevel, hovercolor))
widg.bind("<Leave>", unhighlight)
def highlight(evt, toplevel, hovercolor):
widg = evt.widget
origcolor = widg["bg"]
widg["bg"] = toplevel.formats[hovercolor]
widg.bind("<Leave>", lambda event, origcolor=origcolor:
unhighlight(event, origcolor))
def unhighlight(evt, origcolor):
evt.widget["bg"] = origcolor
def get_all_descendants (ancestor, deep_list):
""" List every widget in the app by running recursively. """
lst = ancestor.winfo_children()
for item in lst:
deep_list.append(item)
get_all_descendants(item, deep_list)
return deep_list
def configall(dialog, master_tree=None, new_formats=None):
if new_formats:
formats = new_formats
elif master_tree:
for values in all_trees.values():
if values["widget"] == master_tree:
formats = master_tree.formats
break
else:
for values in all_trees.values():
if values["widget"] == dialog:
formats = values["formats"]
break
combo_style = Style()
style_name = f"{dialog}.TCombobox"
combo_style.configure(
style_name, fieldbackground=dialog.formats["highlight_bg"],
background=dialog.formats["head_bg"],
foreground=dialog.formats["fg"])
styles = get_styles(formats)
descendants = []
tabbooks = []
scrollbars = []
get_all_descendants(dialog, descendants)
everybody = descendants + [dialog]
for widg in everybody:
if type(widg).__name__ == "TabBook":
tabbooks.append(widg)
elif type(widg).__name__ == "Scrollbar":
scrollbars.append(widg)
for tabbook in tabbooks:
tabbook.format_tabs(formats)
for scrollbar in scrollbars:
scrollbar.format_scrollbars(formats)
if widg.winfo_class() != "TCombobox":
for option, color in styles[widg.style].items():
widg[option] = color
def recolorize(treebard, all_trees, family_tree, combo):
""" Change the database scheme, recreate all_trees dict, run configall. """
new_colors_type_id = (int(combo.get()),)
for key, values in all_trees.items():
if values["widget"] == family_tree:
tree = values["full_path"]
family_tree_id = key
break
conn = sqlite3.connect(global_db_path)
conn.execute("PRAGMA foreign_keys = 1")
cur = conn.cursor()
cur.execute("ATTACH ? as tree", (tree,))
cur.execute(update_current_color_scheme, new_colors_type_id)
conn.commit()
all_trees = make_family_trees_dict(treebard)
family_tree.formats = new_formats = all_trees[family_tree_id]["formats"]
configall(family_tree, new_formats=new_formats)
cur.execute("DETACH tree")
cur.close()
conn.close()
def config_combobox(toplevel):
combo_style = Style()
style_name = f"{toplevel}.TCombobox"
combo_style.configure(
style_name, fieldbackground=toplevel.formats["highlight_bg"],
background=toplevel.formats["head_bg"],
foreground=toplevel.formats["fg"])
if __name__ == "__main__":
def open_tree():
user_tree_title = tree_selector.get()
if len(user_tree_title) == 0:
return
family_tree = tk.Toplevel()
family_tree.style = "bg"
for values in all_trees.values():
if values["user_tree_title"] == user_tree_title:
values["widget"] = family_tree
family_tree.formats = values["formats"]
break
family_tree.title(user_tree_title)
family_tree.geometry("800x400+500+100")
label = tk.Label(family_tree, text=user_tree_title)
label.style = "label" # Do this for every widget constructor.
entry = tk.Entry(family_tree)
entry.style = "entry"
entry.insert(0, user_tree_title)
button = tk.Button(
family_tree, text="OPEN DIALOG",
command=lambda values=values:
make_dialog(values))
button.style = "button"
combo = Combobox(
family_tree, width=36,
style=f"{family_tree}.TCombobox",
values=(2, 3, 242, 289, 303, 314))
combo.delete(0, "end")
combo.insert(0, "select a new color scheme ID")
color_butt = tk.Button(family_tree, text="RECOLORIZE")
color_butt.style = "button"
color_butt.config(
command=lambda treebard=treebard, all_trees=all_trees,
family_tree=family_tree, color_butt=color_butt:
recolorize(treebard, all_trees, family_tree, combo))
label.grid()
entry.grid()
button.grid()
combo.grid(columnspan=2)
color_butt.grid(sticky="e")
config_combobox(family_tree)
configall(family_tree)
def make_dialog(values):
family_tree = values["widget"]
user_tree_title = values["user_tree_title"]
dlg = tk.Toplevel(family_tree)
dlg.style = "bg"
dlg.formats = formats = family_tree.formats
dlg.title(f"child of {user_tree_title}")
canvas = tk.Canvas(dlg)
canvas.style = "bg"
vsb = Scrollbar(
dlg, formats=formats, command=canvas.yview, hideable=True)
vsb.style = "bgHead"
hsb = Scrollbar(
dlg, formats=formats, orient="horizontal",
command=canvas.xview, hideable=True)
hsb.style = "bgHead"
canvas.config(xscrollcommand=hsb.set)
canvas.config(yscrollcommand=vsb.set)
window = tk.Frame(canvas)
window.style = "bg"
lab1 = tk.Label(window, text="HOVER")
lab2 = tk.Label(window, text="HOVER")
lab3 = tk.Label(window, text="HOVER")
tabs = TabBook(
window, formats, minx=.40, miny=.30, dialog=dlg, selected="Moe",
tabs=(("Moe", "M"), ("Larry", "L"), ("Curly", "C")))
tabs.style = "bg"
lab1.style = "label"
bind_hoveree(lab1, toplevel=family_tree, hovercolor="bg")
lab2.style = "label"
bind_hoveree(lab2, toplevel=family_tree, hovercolor="highlight_bg")
lab3.style = "label"
bind_hoveree(lab3, toplevel=family_tree, hovercolor="head_bg")
combo = Combobox(
window, width=36,
style=f"{family_tree}.TCombobox",
values=(2, 3, 242, 289, 303, 314))
combo.delete(0, "end")
combo.insert(0, "select a new color scheme ID")
color_butt = tk.Button(window, text="RECOLORIZE")
color_butt.style = "button"
color_butt.config(
command=lambda treebard=treebard, all_trees=all_trees,
family_tree=family_tree, color_butt=color_butt:
recolorize(treebard, all_trees, family_tree, combo))
# children of dlg
dlg.columnconfigure(0, weight=1)
dlg.rowconfigure(0, weight=1)
canvas.grid(column=0, row=0, sticky="news")
vsb.grid(column=1, row=0, sticky="ns")
hsb.grid(column=0, row=1, sticky="ew")
# children of canvas
canvas.create_window(0, 0, anchor="nw", window=window)
# children of window
lab1.grid(column=0, row=0)
tabs.grid(column=1, row=0, rowspan=3)
lab2.grid(column=0, row=1)
lab3.grid(column=0, row=2)
combo.grid(column=0, row=3, columnspan=2)
color_butt.grid(column=1, row=3, sticky="e")
configall(dlg, master_tree=family_tree)
resize_scrolled_content(dlg, canvas, window)
treebard = tk.Tk()
user_tree_title = "Treebard GPS"
treebard.title(user_tree_title)
treebard.geometry("400x400+500+100")
treebard.style = "bg"
all_trees = make_family_trees_dict(treebard)
existing_trees = [
values["user_tree_title"] for values in all_trees.values()]
tree_selector = Combobox(
treebard,
style=f"{treebard}.TCombobox", # Do this every combobox.
values=existing_trees)
tree_maker = tk.Button(
treebard,
text="OPEN A TREE",
command=open_tree)
tree_maker.style = "button"
tree_selector.grid()
tree_maker.grid()
treebard.formats = all_trees[0]["formats"]
Style().theme_use("alt") # Do this once.
# Do this every toplevel window*********
config_combobox(treebard)
configall(treebard)
# **************************************
treebard.mainloop()