Reputation:
I want to change the different configuration colors of different widgets altogether to a same color when the user clicks on the entry widgets.
I've created a function "change_color(color)" where all my widgets are getting configured to the color passed as an argument.
The problem is the code has a lot of widgets and I've to manually add every widget to the function to keep them updated. I can't use a list as some widgets options are different, for eg: changing foreground of entry widget, background of labels, and much more. Please let me know if there is a better approach of doing this.
Here is a small example of my program. My main code is very long and is not suitable to post here.
import tkinter as tk
def change_color(color):
"Change color of widgets."
window.config(bg=color)
user_label.config(bg=color)
pass_label.config(bg=color)
user_entry.config(highlightbackground=color)
pass_entry.config(highlightbackground=color)
user_entry.config(fg=color, insertbackground=color)
pass_entry.config(fg=color, insertbackground=color)
window = tk.Tk()
# username
user_label = tk.Label(window, text='Username')
user_entry = tk.Entry(window, bg='black')
# password
pass_label = tk.Label(window, text='Password')
pass_entry = tk.Entry(window, bg='black')
user_label.grid(row=0, column=0)
user_entry.grid(row=0, column=1)
pass_label.grid(row=1, column=0)
pass_entry.grid(row=1, column=1)
# changes color
user_entry.bind("<1>", lambda _: change_color("#99c9ff"))
pass_entry.bind("<1>", lambda _: change_color("#ffaf99"))
window.mainloop()
I hope you can get an idea from this example. If something is not clear please ask me from the comment section.
Upvotes: 2
Views: 1925
Reputation: 33
Changing color for many widgets simultaneously, regardless of their place in the app hierarchy, is the exact problem I've solved. I was adding an ability for my app to change its color theme in pure tkinter. Consider an example application below, and the brief explanation that follows.
the app in action
main.py
import tkinter as tk
import themed_widgets as th # where magic happens
root = tk.Tk()
root.geometry("200x200")
frame1 = th.Frame(root, pady=10, borderwidth=6, relief=tk.SUNKEN)
frame1.pack(fill=tk.X)
# btn will NOT support recoloring,
# since it was made with 'tk', not 'th'
btn = tk.Button(frame1, text="change theme",
command=lambda: th.change_theme(root))
btn.pack()
frame2 = th.Frame(root, pady=10, borderwidth=12, relief=tk.SUNKEN)
frame2.pack(fill=tk.BOTH, expand=True)
label = th.Label(frame2, text="random label")
label.pack(fill=tk.X, expand=True)
tk.Radiobutton()
rb_var = tk.IntVar(value=0)
for i in range(3):
rb = th.Radiobutton(frame2, text=f"radiobutton {i}",
variable=rb_var, value=i, indicatoron=False)
rb.pack()
root.mainloop()
See how it's enough to replace tk
with th
in the existing app where you wish to support recoloring. In order for it to work, all the tk
widgets you want to recolor must have their counterparts in the themed_widgets.py
file.
themed_widgets.py
import tkinter as tk
active = 0
THEMES = [
# colors # themes
# primary , secondary #
["#0000FF", "#00FF00"], # 1st
["#00FF00", "#FF0000"], # 2nd
["#FF0000", "#0000FF"], # 3rd
]
def primary(): return THEMES[active][0]
def secondary(): return THEMES[active][1]
def Frame(*args, **kwargs):
fr = tk.Frame(*args, **kwargs)
fr.recolor = lambda: fr.configure(bg=secondary())
fr.recolor()
return fr
def Label(*args, **kwargs):
lbl = tk.Label(*args, **kwargs)
lbl.recolor = lambda: lbl.configure(bg=secondary(), fg=primary())
lbl.recolor()
return lbl
def Radiobutton(*args, **kwargs):
rb = tk.Radiobutton(*args, **kwargs)
# notice how '.configure' differs drastically between widgets
# especially here, with 'selectcolor' exclusive to a Radiobutton
rb.recolor = lambda: rb.configure(bg=secondary(), selectcolor=primary())
rb.recolor()
return rb
def change_theme(container):
"""
Recursively iterate over all children of a given container.
Typically call with 'root' as an argument.
"""
global active
active += 1
if active > len(THEMES) - 1:
active = 0
do_recolor(container)
def do_recolor(container):
for child in container.winfo_children():
try:
child.recolor()
except AttributeError as e:
pass
if child.winfo_children():
do_recolor(child)
See how for each widget a function with its name is created. There, a recoloring lambda, specific to the widget's needs, is assigned to the recolor
attribute of the tk
widget. With this approach, you don't need to check widget's class to decide what to do in .configure
. This allows for a more simple recursive recoloring, as can be seen in do_recolor
.
Recoloring relies on a list of constants, which ensures a uniform look for your app, and makes expanding template colors and adding themes that much easier.
Set themes for individual widgets as opposed to whole widget classes
Don't want a widget instance to participate in recoloring? Simply create it with tk
instead of th
. What if, however, you have two themed widgets of the same class, and they need to be recolored differently?
The solution as it is can be tweaked to solve this case. Consider a more elegant solution. With closures, custom theme colors to cycle through can be used for individual themed widgets. Here is an example for labels.
Add this to themed_widgets.py
def Unique_Label(theme_colors):
l = len(theme_colors)
def primary(): return theme_colors[active % l][0]
def secondary(): return theme_colors[active % l][1]
def label(*args, **kwargs):
lbl = tk.Label(*args, **kwargs)
lbl.recolor = lambda: lbl.configure(bg=secondary(), fg=primary())
lbl.recolor()
return lbl
return label
Add this below the lbl
in main.py
ulabel_theme = [['lightblue', 'darkgreen'],['darkblue', 'lightgreen']]
ulabel = th.Unique_Label(ulabel_theme)(frame2, text="unique label")
ulabel.pack(fill=tk.X, expand=True)
And it works!
Upvotes: 0
Reputation: 1217
Here is one approach to doing it.
import tkinter as tk
def change_color(color):
"Change color of widgets."
for wdg in window.children():
wdg = window.nametowidget(wdg)
if isinstance(wdg, tk.Label):
wdg.config(bg=color)
elif isinstance(wdg, tk.Entry):
wdg.config(fg=color, insertbackground=color, highlightbackground=color)
window = tk.Tk()
# username
user_label = tk.Label(window, text='Username')
user_entry = tk.Entry(window, bg='black')
# password
pass_label = tk.Label(window, text='Password')
pass_entry = tk.Entry(window, bg='black')
user_label.grid(row=0, column=0)
user_entry.grid(row=0, column=1)
pass_label.grid(row=1, column=0)
pass_entry.grid(row=1, column=1)
# changes color
user_entry.bind("<1>", lambda _: change_color("#99c9ff"))
pass_entry.bind("<1>", lambda _: change_color("#ffaf99"))
window.mainloop()
Upvotes: 0
Reputation: 46669
You can go through all widgets recursively using winfo_children()
:
def change_color(color, container=None):
if container is None:
container = window # set to root window
container.config(bg=color)
for child in container.winfo_children():
if child.winfo_children():
# child has children, go through its children
change_color(color, child)
elif type(child) is tk.Label:
child.config(bg=color)
elif type(child) is tk.Entry:
child.config(highlightbackground=color)
child.config(fg=color, insertbackground=color)
# check for other widget types ...
Upvotes: 2