Reputation: 1295
I am without knowledge of tck
/tk
. I have done carefully search on the internet but haven't found a good solution.
For example, I created a LabelFrame
using:
import tkinter as tk
from tkinter import ttk
newBT = ttk.LabelFrame(width=100, height=100)
Then I need to set the frame style. There is foreground for tk.LabelFrame
. However, I didn't find such style option for ttk.LabelFrame
on NMT and tck/tk reference. Then I have to guess, like following
s = ttk.Style()
s.configure('TLabelframe', foreground='red')
But this doesn't work, the right thing is:
s.configure('TLabelframe.Label', foreground='red')
So, my question is, how can I find out all the style options a ttk widget has. Is there some function like:
s.getAllOptions('TLabelframe')
and then the output is something like:
['background', 'foreground', 'padding', 'border', ...]
Upvotes: 31
Views: 18680
Reputation: 1192
Using similar logic to find all style option but with regular expressions would result in even fewer lines of code.
Fewer lines doesn't mean faster execution. This code takes a bit more time to complete the execution. This approach is ideal if you don't care about the performance in the code.
import tkinter.ttk as ttk
import re
s = ttk.Style(None)
def findStyleOptions(class_name):
str_layout = str(s.layout(class_name))
matches = re.findall("[(]'(.*?)',", str_layout)
for match in matches:
print(s.element_options(match))
# Tests
for class_name in ("TLabel", "TButton", "TEntry", "TFrame"):
a=findStyleOptions(class_name)
Explanation:
('element-name.element',
, regular expression pattern [(]'(.*?)',
is written to parse out the element name.re.findall
is used to find all occurrence of the above pattern from the s.layout()
result.s.element_options()
is used to find all the options for each elements.Suggested links:
If you don't want to use Regular Expression for performance reasons then you can use this code block:
import tkinter.ttk as ttk
s = ttk.Style(None)
def findStyleOptions(class_name):
group_stack =[]
blocks = []
result = []
inner_layout = ""
rev_layout = reversed(str(s.layout(class_name)))
for char in rev_layout:
if char in (')', ']', '}'):
group_stack.append(char)
continue
if char in ('(', '[', '{'):
blocks.append(f"{char}{inner_layout[::-1]}{group_stack.pop()}")
inner_layout = ""
continue
inner_layout += char
for vals in reversed(blocks):
if vals == '[]':
continue
elif '(' in vals:
vals = vals.replace('(', "").replace(')', "").replace("'", "").replace(', ', "")
result.append(vals)
for element in result:
print(s.element_options(element))
for class_name in ("TLabel", "TButton", "TEntry", "TFrame"):
a=findStyleOptions(class_name)
Explanation:
blocks
list.blocks
list to convert the list into top to bottom element hierarchy.s.element_options()
is used to find all the options for each elements.Reason to add this code along with the code of other answers is this code is more easy to understand and avoids multiple nesting of code blocks.
Upvotes: 0
Reputation: 121
Building on SunBear's script:
import tkinter as tk
import tkinter.ttk as ttk
def iter_layout(layout, tab_amnt=0, elements=[]):
"""Recursively prints the layout children."""
el_tabs = ' '*tab_amnt
val_tabs = ' '*(tab_amnt + 1)
for element, child in layout:
elements.append(element)
print(el_tabs+ '\'{}\': {}'.format(element, '{'))
for key, value in child.items():
if type(value) == str:
print(val_tabs + '\'{}\' : \'{}\','.format(key, value))
else:
print(val_tabs + '\'{}\' : [('.format(key))
iter_layout(value, tab_amnt=tab_amnt+3)
print(val_tabs + ')]')
print(el_tabs + '{}{}'.format('} // ', element))
return elements
def stylename_elements_options(stylename, widget):
"""Function to expose the options of every element associated to a widget stylename."""
try:
# Get widget elements
style = ttk.Style()
layout = style.layout(stylename)
config = widget.configure()
print('{:*^50}\n'.format(f'Style = {stylename}'))
print('{:*^50}'.format('Config'))
for key, value in config.items():
print('{:<15}{:^10}{}'.format(key, '=>', value))
print('\n{:*^50}'.format('Layout'))
elements = iter_layout(layout)
# Get options of widget elements
print('\n{:*^50}'.format('element options'))
for element in elements:
print('{0:30} options: {1}'.format(
element, style.element_options(element)))
except tk.TclError:
print('_tkinter.TclError: "{0}" in function'
'widget_elements_options({0}) is not a regonised stylename.'
.format(stylename))
widget = ttk.Button(None)
class_ = widget.winfo_class()
stylename_elements_options(class_, widget)
Prints the config options as well as the layout tree.
Upvotes: 12
Reputation: 8297
I found your question interesting as I had asked myself the same question but have not found time to address it until now. I have written a function called stylename_elements_options(stylename)
to do just this. Sharing it here. Hope it can benefit you (although it is 6 months late) and any tkinter users asking the same question.
Script:
import tkinter as tk
import tkinter.ttk as ttk
def stylename_elements_options(stylename):
'''Function to expose the options of every element associated to a widget
stylename.'''
try:
# Get widget elements
style = ttk.Style()
layout = str(style.layout(stylename))
print('Stylename = {}'.format(stylename))
print('Layout = {}'.format(layout))
elements=[]
for n, x in enumerate(layout):
if x=='(':
element=""
for y in layout[n+2:]:
if y != ',':
element=element+str(y)
else:
elements.append(element[:-1])
break
print('\nElement(s) = {}\n'.format(elements))
# Get options of widget elements
for element in elements:
print('{0:30} options: {1}'.format(
element, style.element_options(element)))
except tk.TclError:
print('_tkinter.TclError: "{0}" in function'
'widget_elements_options({0}) is not a regonised stylename.'
.format(stylename))
stylename_elements_options('my.Vertical.TScrollbar')
Upvotes: 22
Reputation: 371
The issue is that if you really want to control a style in detail you need to use the layout. So first identify the widget class using:
>>b=ttk.Button(None)
>>b.winfo_class()
'TButton
Then use the command
>>> s.layout('TButton')
[("Button.border", {"children": [("Button.focus", {"children":
[("Button.spacing",
{"children": [("Button.label", {"sticky": "nswe"})], "sticky": "nswe"})],
"sticky": "nswe"})], "sticky": "nswe", "border": "1"})]
Finally change what you want:
s.layout("MYButton.TButton",[("Button.border", {"children":
[("Button.focus", {"children": [("Button.spacing", {"children":
[("Button.label", {"sticky": "nswe"})], "sticky": "nswe"})], "sticky":
"nswe"})], "sticky": "we", "border": "1"})]
This made the trick for me and finally provides me a way to control my ttk widget!!!
Luca
Upvotes: 14