Nan Zhou
Nan Zhou

Reputation: 1295

How to know all style options of a ttk widget?

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

Answers (4)

Faraaz Kurawle
Faraaz Kurawle

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:

  1. Since element names are in format ('element-name.element', , regular expression pattern [(]'(.*?)', is written to parse out the element name.
  2. re.findall is used to find all occurrence of the above pattern from the s.layout() result.
  3. s.element_options() is used to find all the options for each elements.

Suggested links:

  1. Learn & Play with Regex's

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:

  1. Above code use's Parenthesis Validation Algorithm to parse out each block of brackets along with its interior data.
  2. Since we are reading the string from left to right, our string becomes for example "hello" to "olleh" so we hade to reverse it again and add it to the blocks list.
  3. We reversed the blocks list to convert the list into top to bottom element hierarchy.
  4. 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

Thomas
Thomas

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

Sun Bear
Sun Bear

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

LucaG
LucaG

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

Related Questions