How to change the dynamic appearance (i.e. color) of tkinter ttk.Scrollbar? - python

I have found questions and answers on changing the static colour of a ttk.Scrollbar. However, I have not yet found one on changing its dynamic appearance, which is my question.
My scripts have exposed the elements of a Vertical.TScrollbar to be:
clam theme:
Stylename = Vertical.TScrollbar
Layout = [('Vertical.Scrollbar.trough', {'sticky': 'ns', 'children': [('Vertical.Scrollbar.uparrow', {'side': 'top', 'sticky': ''}), ('Vertical.Scrollbar.downarrow', {'side': 'bottom', 'sticky': ''}), ('Vertical.Scrollbar.thumb', {'sticky': 'nswe'})]})]
Element(s) = ['Vertical.Scrollbar.trough', 'Vertical.Scrollbar.uparrow', 'Vertical.Scrollbar.downarrow', 'Vertical.Scrollbar.thumb']
Vertical.Scrollbar.trough options: ('orient', 'background', 'bordercolor', 'troughcolor', 'lightcolor', 'darkcolor', 'arrowcolor', 'arrowsize', 'gripcount', 'sliderlength')
Vertical.Scrollbar.uparrow options: ('orient', 'background', 'bordercolor', 'troughcolor', 'lightcolor', 'darkcolor', 'arrowcolor', 'arrowsize', 'gripcount', 'sliderlength')
Vertical.Scrollbar.downarrow options: ('orient', 'background', 'bordercolor', 'troughcolor', 'lightcolor', 'darkcolor', 'arrowcolor', 'arrowsize', 'gripcount', 'sliderlength')
Vertical.Scrollbar.thumb options: ('orient', 'background', 'bordercolor', 'troughcolor', 'lightcolor', 'darkcolor', 'arrowcolor', 'arrowsize', 'gripcount', 'sliderlength')
default theme:
Stylename = Vertical.TScrollbar
Layout = [('Vertical.Scrollbar.trough', {'sticky': 'ns', 'children': [('Vertical.Scrollbar.uparrow', {'side': 'top', 'sticky': ''}), ('Vertical.Scrollbar.downarrow', {'side': 'bottom', 'sticky': ''}), ('Vertical.Scrollbar.thumb', {'sticky': 'nswe'})]})]
Element(s) = ['Vertical.Scrollbar.trough', 'Vertical.Scrollbar.uparrow', 'Vertical.Scrollbar.downarrow', 'Vertical.Scrollbar.thumb']
Vertical.Scrollbar.trough options: ('borderwidth', 'troughcolor', 'troughrelief')
Vertical.Scrollbar.uparrow options: ('background', 'relief', 'borderwidth', 'arrowcolor', 'arrowsize')
Vertical.Scrollbar.downarrow options: ('background', 'relief', 'borderwidth', 'arrowcolor', 'arrowsize')
Vertical.Scrollbar.thumb options: ('orient', 'width', 'relief', 'background', 'borderwidth')
I tried changing the dynamic color of the its thumb using:
ss.map("Vertical.TScrollbar", background=[('pressed', "yellow"), ('active', "yellow")])
or
ss.map("Vertical.TScrollbar", thumb=[('pressed', "yellow"), ('active', "yellow")])
but these syntaxes don't work.
I want the scrollbar thumb to change to yellow color whenever the mouse pointer goes over or presses the ttk.Scrollbar. How can this objective be achieved? Thanks.
Sample script(adapted from #Aivar):
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
style.theme_use('clam')
# style.theme_use('default')
# list the options of the style
# (Argument should be an element of TScrollbar, eg. "thumb", "trough", ...)
print(style.element_options("Horizontal.TScrollbar.thumb"))
# configure the style
style.configure("Horizontal.TScrollbar", gripcount=0,
background="Green", darkcolor="DarkGreen", lightcolor="LightGreen",
troughcolor="gray", bordercolor="blue", arrowcolor="white")
# Syntax A - don't work
style.map("Vertical.TScrollbar.thumb",
background=[('pressed', "yellow"), ('active', "yellow")],
bordercolor=[('pressed', "yellow"), ('active', "yellow")],
troughcolor=[('pressed', "yellow"), ('active', "yellow")],
lightcolor=[('pressed', "yellow"), ('active', "yellow")],
darkcolor=[('pressed', "yellow"), ('active', "yellow")],
)
# Syntax B - don't work either
##style.map("Vertical.TScrollbar",
## background=[('pressed', "yellow"), ('active', "yellow")],
## bordercolor=[('pressed', "yellow"), ('active', "yellow")],
## troughcolor=[('pressed', "yellow"), ('active', "yellow")],
## lightcolor=[('pressed', "yellow"), ('active', "yellow")],
## darkcolor=[('pressed', "yellow"), ('active', "yellow")],
## )
hs = ttk.Scrollbar(root, orient="horizontal")
hs.place(x=5, y=5, width=150)
hs.set(0.2,0.3)
root.mainloop()
These are useful documentation [1, 2], on dynamic styling and ttk.Scrollbar that I have found. The tcl documentation did mention that the dynamic states of a ttk.Scrollbar are: active, disabled. I have tried removing the pressed state in my test script so as to only mention active state but this amendment did not work.

I am so happy to share that the answer to my question is
style.map("TScrollbar", background=[('active', "yellow")],)
or specifically for a Horizontal scrollbar it is
style.map("Horizontal.TScrollbar", background=[('active', "yellow")],)
Found it after many different permutations of try and error and after realising my typo. The script used a Horizontal ttk.Scrollbar but my styling mentioned Vertical.TScrollbar. :)
Works on these themes 'clam', 'default', 'classic', 'alt'.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style()
# style.theme_use('clam')
# style.theme_use('default')
# style.theme_use('classic')
style.theme_use('alt')
# list the options of the style
# (Argument should be an element of TScrollbar, eg. "thumb", "trough", ...)
print(style.element_options("Horizontal.TScrollbar.thumb"))
# configure the style
style.configure("Horizontal.TScrollbar", gripcount=0,
background="Green", darkcolor="DarkGreen", lightcolor="LightGreen",
troughcolor="gray", bordercolor="blue", arrowcolor="white")
# Syntax - works
style.map("TScrollbar",
background=[('active', "yellow")],
bordercolor=[('active', "cyan")],
troughcolor=[('active', "orange")],
lightcolor=[('active', "red")],
darkcolor=[('active', "pink")],
arrowcolor=[('active', "red")],
)
print(f"{style.map('TScrollbar')=}")
hs = ttk.Scrollbar(root, orient="horizontal")
hs.place(x=5, y=5, width=150)
hs.set(0.2,0.3)
root.mainloop()

Related

How to customize Tkinter.ttk Progressbar with label text and custom bar color

I'm trying to create a custom progress bar widget that has some text (Label) above it, and I have to change the background (Progressbar) bar color in some situations, but I'm struggling to understand Tkinter's documentation as for the Style and Themes...
I'm trying to combine j_4321 and clan + martineau solutions for this.
I've created a Style layout like this:
progressbar_style = Style(self)
progressbar_style.layout(
'pb1.text.Horizontal.TProgressbar',
[
(
'Horizontal.Progressbar.trough', # main proressbar definition
{'children':
[
('Horizontal.Progressbar.pbar', # main progressbar
{
'side': 'left',
'sticky': 'ns',
}
)
],
'sticky': 'nswe'
}
),
(
# transparent label text over progressbar
'Horizontal.Progressbar.label', {'sticky': 'nswe'}
)
]
)
And using this allows me to change the overlayed Label text color
# foreground argument determines the text color
progressbar_style.configure('pb1.text.Horizontal.TProgressbar', text='lorem ipsum', anchor='center', foreground='red', background='yellow')
But unlike seen in the clan + martineau's solution, the background argument doesn't seem to change the progress bar color. And even troughcolor hasn't worked for me for this. How can I specify the widget part I want to configure (as in the previous configure method)?
Customized: You have to use the style definition and than assign to your widget:
import tkinter as tk
from tkinter import ttk
import time
def increment(*args):
for i in range(100):
p1["value"] = i+1
root.update()
time.sleep(0.1)
pb_style.configure('text.Horizontal.TProgressbar',
text=f"{p1['value']} %")
root = tk.Tk()
root.geometry('300x50')
# define the style
pb_style = ttk.Style()
print(pb_style.theme_names())
# ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
pb_style.theme_use("default")
pb_style.layout('text.Horizontal.TProgressbar',
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': 'nswe'})])
pb_style.configure('text.Horizontal.TProgressbar', text='0 %', anchor='center', foreground='red', background='yellow' )
#pb_style.configure("yellow.Vertical.TProgressbar", troughcolor="gray", background="yellow")
p1 = ttk.Progressbar(root, length=200, cursor='spider',
mode="determinate",
orient=tk.HORIZONTAL,
style='text.Horizontal.TProgressbar'
)
p1.grid(row=1,column=1)
btn = ttk.Button(root,text="Start",command=increment)
btn.grid(row=1,column=0)
root.mainloop()
Output:
With a small change to the style layout I was able to modify the text, text color and the color of the bar itself, but I couldn't keep my system default theme on the progress bar, which made me change all other progress bars I had in my application.
First of all here's how they look like under different themes:
vista (default on my system - Windows 10):
clam:
xpnative:
Without calling the configure method to edit the style, the progress bar keeps the system default, and after it seems to change to xpnative, unless changed with another theme command, but I couldn't get it back to the default vista one (winnative, alt, default, classic all changed to xpnative after configuring).
The clam and xpnative themes are the only ones that currently work for me, where I can acess and modify the text itself and progress bar and text colors.
Here's how the style layout I'm using looks like:
# this is inside a Frame class
progressbar_style = Style(self)
# creating a colored progress bar (i'm currently using the clam theme)
progressbar_style.element_create('color.pbar', 'from', 'clam')
# new style layout
progressbar_style.layout(
'pb1',
[
(
# main proressbar definition
'Horizontal.Progressbar.trough',
{
'sticky': 'nswe',
'children':
[
(
# colored progressbar
'Horizontal.Progressbar.color.pbar',
{
'side': 'left',
'sticky': 'ns'
}
)
],
}
),
(
# transparent label text over progressbar
'Horizontal.Progressbar.label', {'sticky': 'nswe'}
)
]
)
Here's the progress bar widget, taking the new created style as an argument
audio_level_indicator = Progressbar(
master = self # frame where the progressbar is used
variable = self.audio_level, # DoubleVar control variable
mode = 'determinate',
maximum = 20,
style = 'pb1' # new style just modified
)
And here's the configure command to modify its attributes
progressbar_style.configure(
'pb1', # style argument
text = 'Sample text', # progress bar text - for the overlayed label
background='blue', # progress bar color
foreground='red' # text color
)
output bar:
But it's unfortunate that I couldn't use the vista theme for this, I had to change the rest of my application to match the new theme.

How to change the colour of a ttk.Checkbutton when it is !disabled and selected?

How to change the colour of the indicator of ttk.Checkbutton when it is !disabled and selected? The following picture shows it is blueish in colour.
Typing the following:
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
s = ttk.Style()
print(f"{s.map('TCheckbutton', 'indicatorcolor')=}")
returns:
s.map('TCheckbutton', 'indicatorcolor')=[('pressed', '#ececec'), ('!disabled', 'alternate', '#9fbdd8'), ('disabled', 'alternate', '#c0c0c0'), ('!disabled', 'selected', '#4a6984'), ('disabled', 'selected', '#a3a3a3')]
I tried to change ('!disabled', 'selected', '#4a6984') to red color by using
s.map('TCheckbutton.indicatorcolor',
background=[('pressed', '#ececec'),
('!disabled', 'alternate', 'red'),
('disabled', 'alternate', '#c0c0c0'),
# ('!disabled', 'selected', '#4a6984'), # original
('!disabled', 'selected', 'red'),
('disabled', 'selected', '#a3a3a3')]
)
and replacing the word background with foreground and even removing the word entirely but these methods failed to work. I also tried the below syntax but to no avail.
ss.configure("TCheckbutton.indicator.", background="red", foreground="red",
indicatorcolor="red")
For me under Windows 11 and the theme alt the indicatorcolor corresponds with the forground option and the box of the indicator is colored by the indicatorcolor. So it might be confusing. But I think the code below should show how it is done
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
style = ttk.Style()
common_fg = 'white'
common_bg = 'black'
sel_bg = 'red'
style.theme_use('alt')
style.configure('TCheckbutton',
focuscolor = '',
font=['arial','10','italic'])
style.map('TCheckbutton',
foreground = [('disabled', common_fg),
('pressed', common_fg),
('active', 'blue')],
## background = [('disabled', common_bg),
## ('pressed', '!focus', common_bg),
## ('active', common_bg)],
indicatorcolor=[('selected', 'red'),
('pressed', 'pink')]
)
c = ttk.Checkbutton(root, text='test')
c.pack()
root.mainloop()

How to justify text in a Button in Python ttk

I'm trying to justify (align) text in a button, let's say to the left.
I've found some useful code here, based on that I've created code below to check how the label behaves.
import tkinter as tk
import tkinter.ttk as ttk
app = tk.Tk()
style = ttk.Style()
style.layout(
'Left1.TButton',[
('Button.button', None),
('Button.focus', {'children': [
('Button.padding', {'children': [
('Button.label', {'side' : 'left'}
)]}
)]}
)]
)
print("TButton.label->justify: ", style.lookup('TButton.label', 'justify'))
print("TButton.label->side: ", style.lookup('TButton.label', 'side'))
style.configure('TButton.label', justify='left')
style.configure('TButton.label', side='left')
print("TButton.label->justify: ", style.lookup('TButton.label', 'justify'))
print("TButton.label->side: ", style.lookup('TButton.label', 'side'))
ttk.Button(text="TButton", width=100, style="TButton").pack()
ttk.Button(text="Lef1.TButton", width=100, style="Left1.TButton").pack()
print("TButton.label options:\n", style.element_options('TButton.label'))
Problem is that there's no effect on the label. It stays in the center of the button.
What is even more interesting is last line.
Output is:
TButton.label options:
('compound', 'space', 'text', 'font', 'foreground', 'underline', 'width', 'anchor', 'justify', 'wraplength', 'embossed', 'image', 'stipple', 'background')
But if I try to set 'anchor' I'm getting exception. I know this feature is absent in ttk but why is it showing up here?
I've just been using the following style and it's working - this applies the anchor='center' configuration to all of my buttons, but you can create your own style for each wherein you just change 'TButton' to '{insert your style name}.TButton' (more on that https://www.pythontutorial.net/tkinter/ttk-style/ - underrated resource to learn ttk styling).
Anyways, here's the code:
style = ttk.Style()
style.theme_create( "button-center", parent="alt", settings={
"TButton": {"configure": {"anchor": "center"}}} )
# Now just create the button itself
style.theme_use("button-center")
btn = ttk.Button({the_frame_it_belongs_to})
btn.configure(text='BUTTON', width='15')
btn.grid(column='0', row='0')
style: tkinter.ttk.Style = tkinter.ttk.Style()
# Justify to the left [('Button.label', {'sticky': 'w'})]
style.layout("TButton", [('Button.button', {'sticky': 'nswe', 'children': [('Button.focus', {'sticky': 'nswe', 'children': [('Button.padding', {'sticky': 'nswe', 'children': [('Button.label',
{'sticky': 'w'})]})]})]})])

Progressbar with Percentage Label?

How can I put a label in the middle of a progressbar that shows the percentage?
The problem is that python doesn't support transparency for label backgrounds, so I don't know how I can solve that.
This is possible using a ttk.Style. The idea is to modify the layout of the Horizontal.TProgressbar style (do the same with Vertical.TProgressbar for a vertical progressbar) to add a label inside the bar:
Usual Horizontal.TProgressbar layout:
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'})]
With an additional label:
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': 'nswe'})]
Then, the text of the label can be changed with style.configure.
Here is the code:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
style = ttk.Style(root)
# add label in the layout
style.layout('text.Horizontal.TProgressbar',
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': 'nswe'})])
# set initial text
style.configure('text.Horizontal.TProgressbar', text='0 %', anchor='center')
# create progressbar
variable = tk.DoubleVar(root)
pbar = ttk.Progressbar(root, style='text.Horizontal.TProgressbar', variable=variable)
pbar.pack()
def increment():
pbar.step() # increment progressbar
style.configure('text.Horizontal.TProgressbar',
text='{:g} %'.format(variable.get())) # update label
root.after(200, increment)
increment()
root.mainloop()
Styling
The font, color and position of the label can be changed using style.configure. For instance,
style.configure('text.Horizontal.TProgressbar', foreground="red",
font='Arial 20', anchor='w')
gives
Multiple progressbars
The text is set through the style therefore to have multiple progressbars with different labels, one needs to use a different style for each. However, there is no need to set the layout for each style: create the layout 'text.Horizontal.TProgressbar' like in the above code and then use substyles 'pb1.text.Horizontal.TProgressbar', 'pb2.text.Horizontal.TProgressbar', ... for each progressbar. Then the text of a single progressbar can be changed with
style.configure('pb1.text.Horizontal.TProgressbar', text=...)

How to get FLAT relief Entry widget in python ttk?

I am writing a GUI FTP client app in python ttk. I can't seem to get the text Entries the way I wanted them to.
The text Entries have the "SUNKEN" effect. I want to adjust the height and get the text entries to have a "FLAT" look, more like this program:
I got the buttons to look flat from here, I am struggling to get the Entries to look flat.
How do I get the Entries to look flat?
I don't want a tk solution, I know how to set relief FLAT in tk.
Trivia:
Appereance of ttk widgets heavily depends on theme in use and/or platform. On windows I can reproduce this depressive gray style with a classic theme, so let's assume that this theme in use. List of themes you can find here (note the 'clam' theme, wich is most similar to your wishes), just if you wonder.
Next stop, our layout or, in other words, a structure of widget's theme.
You can easily print this structure to see available options. Enough words, let's try a structure of an ttk.Entry widget.
>>> print(s.layout('TEntry'))
# formatted result, actual result is one-line string
[('Entry.highlight', {
'sticky': 'nswe',
'children': [(
'Entry.field', {
'sticky': 'nswe',
'children': [(
'Entry.padding', {
'sticky': 'nswe',
'children': [(
'Entry.textarea', {
'sticky': 'nswe'})]
})],
'border': '1'})]
})]
As you can see, noone of structure elements has a relief option, hence relief='flat' has no effect on our entry!
>>> print(s.element_options('Entry.field'))
('-bordercolor', '-lightcolor', '-darkcolor', '-fieldbackground')
Workaround:
However, we can edit the layout of desired widget, and replace one element with another, wich able to recognize a relief option (note a removed Entry.field, replaced with an Entry.border).
So try out this little snippet:
try:
import tkinter as tk
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
# root
root = tk.Tk()
# declare style variable
s = ttk.Style()
# assume that classic theme in use
s.theme_use('classic')
# configure relief
s.configure('SOExample.TEntry', relief='flat')
# lets try to change this structure
s.layout('SOExample.TEntry', [
('Entry.highlight', {
'sticky': 'nswe',
'children':
[('Entry.border', {
'border': '1',
'sticky': 'nswe',
'children':
[('Entry.padding', {
'sticky': 'nswe',
'children':
[('Entry.textarea',
{'sticky': 'nswe'})]
})]
})]
})])
# let's find some differences between this two
print(s.layout('SOExample.TEntry'))
print(s.layout('TEntry'))
# packing
frame1 = tk.Frame(root)
label1 = tk.Label(frame1, text='Flat Entry')
entry1 = ttk.Entry(frame1, style='SOExample.TEntry')
label1.pack()
entry1.pack()
frame1.pack(side='left')
frame2 = tk.Frame(root)
label2 = tk.Label(frame2, text='Default Entry')
entry2 = ttk.Entry(frame2)
label2.pack()
entry2.pack()
frame2.pack(side='right')
# mainloop
root.mainloop()
...and final appereance is:
Also, on my machine default theme is vista and there I can't even recognize an entry:
And also a structure of an Entry depends on theme, so with vista it's:
# formatted result, actual result is one-line string
[('Entry.field', {
'sticky': 'nswe',
'children': [(
'Entry.background', {
'sticky': 'nswe',
'children': [(
'Entry.padding', {
'sticky': 'nswe',
'children': [(
'Entry.textarea', {
'sticky': 'nswe'})]
})]
})]
})]
...and if we replace a field with border element in this structure, the appearence is very close to your demands (also try groove relief in this case):
Conclusion:
Unfortunately, this widget will lack options of field and border color, so another option is create own element for this purpose, but that is another question.
So, basicly I tryied to answer not "how to workaround this", but "why a relief has no effect on Entry", anyway as you can see, a flat relief for an Entry is achievable.

Categories