I wish to retrieve the font object currently assigned to an arbitrary tkinter.ttk.Label
widget.
(To be more exact, I want to obtain the exact configuration attributes of the widget’s current tkinter.font.Font
instance — family
, size
, weight
, slant
, underline
, and overstrike
— in order to programmatically define modified variants of the same base font.)
This is easy to do with a plain, unthemed tkinter.Label
:
import tkinter.font
# Create root object
root = tkinter.Tk()
# Create label widget
label = tkinter.Label( root, text="Hello world!" )
label.pack()
# Get font name (key) from the widget
font_name = label['font']
print( " font_name:", repr(font_name) )
# Get Font instance and print attributes
font = tkinter.font.nametofont( font_name )
for key, value in font.config().items():
print( f"{key:>10}: {repr(value)}" )
# Display the widget
root.mainloop()
The above program prints out the font details as follows:
font_name: 'TkDefaultFont'
family: 'sans-serif'
size: 10
weight: 'normal'
slant: 'roman'
underline: 0
overstrike: 0
However, if I change it to use a tkinter.ttk.Label
instead of a tkinter.Label
...
import tkinter.font
import tkinter.ttk
[...]
# Create label widget
label = tkinter.ttk.Label( root, text="Hello world!" )
label.pack()
...it no longer works. This is because the expression label['font']
now returns an empty string instead of a valid Tkinter font name. The empty string gets assigned to font_name
and then used in a nametofont
function call, resulting to:
font_name: ''
Traceback (most recent call last):
File "fonttest.py", line 14, in <module>
font = tkinter.font.nametofont( font_name )
File "/usr/lib/python3.10/tkinter/font.py", line 23, in nametofont
return Font(name=name, exists=True, root=root)
File "/usr/lib/python3.10/tkinter/font.py", line 87, in __init__
raise tkinter._tkinter.TclError(
_tkinter.TclError: named font font1 does not already exist
If I change the program yet further and specify a named ttk
theme by adding the following lines after the creation of the root
object but before creating the ttk.Label
widget...
# Set up a ttk theme
style = tkinter.ttk.Style()
style.theme_use( 'breeze' )
...I get a somewhat different result, but still no valid font name:
font_name: <font object: 'Helvetica 10'>
Traceback (most recent call last):
File "fonttest.py", line 20, in <module>
font = tkinter.font.nametofont( font_name )
File "/usr/lib/python3.10/tkinter/font.py", line 23, in nametofont
return Font(name=name, exists=True, root=root)
File "/usr/lib/python3.10/tkinter/font.py", line 87, in __init__
raise tkinter._tkinter.TclError(
_tkinter.TclError: named font Helvetica 10 does not already exist
Note how label['font']
no longer returned a string — empty or otherwise — but a “font object”.
Adding some more debug prints...
print( " font_name:", repr(font_name) )
print( "font_name.__class__:", font_name.__class__ )
print( "font_name.__str__():", repr(font_name.__str__()) )
print( "dir( font_name ):", dir( font_name ) )
print( "tkinter.font.names():", tkinter.font.names() )
...gives us this output:
font_name: <font object: 'Helvetica 10'>
font_name.__class__: <class '_tkinter.Tcl_Obj'>
font_name.__str__(): 'Helvetica 10'
dir( font_name ): ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'string', 'typename']
tkinter.font.names(): ('TkCaptionFont', 'TkSmallCaptionFont', 'TkTooltipFont', 'TkFixedFont', 'TkHeadingFont', 'TkMenuFont', 'TkIconFont', 'TkTextFont', 'TkDefaultFont')
It is not really a “font name”, and not an instance of tkinter.font.Font
either, but an instance of the class _tkinter.Tcl_Obj
. The dir
output lists a member called typename
so I added this line, too...
print( "font_name.typename:", repr(font_name.typename) )
...which yields:
font_name.typename: 'font'
To summarize: sometimes the themed ttk
widgets return an empty string for their font, some other times they return a “font object”. The “font object”, if present, is not a tkinter.font.Font
instance but a _tkinter.Tcl_Obj
instance — apparently a Python wrapper for an internal TCL font
object whose properties are inaccessible to Python code?
Since this curious font
object is convertible to a string (Helvetica 10
), it could technically be used as a Tkinter “font name” in a font lookup call. Alas, the string that the object holds or converts into is still not a registered “font name” (that tkinter.font.names()
ortkinter.font.nametofont( font_name )
would recognize) so the attributes of the underlying font remain inaccessible.
So, my question is: what is the correct way to programmatically obtain the current tkinter.font.Font
instance (or the equivalent attribute details — family
, size
, weight
, slant
, underline
, overstrike
) of the font assigned to a tkinter.ttk.Label
widget both when a theme has been explicitly set and when it has not been set?
My test environment in the above was Python 3.10.12 that comes standard with Kubuntu (Ubuntu) 22.04 LTS. The packages python3-tk
(version 3.10.8-1~22.04) and python3-ttkthemes
(version 3.2.2+git20220101+07e6509cc6bf-1) were manually installed from the standard Ubuntu repositories.
Edit: I initially assumed thettkthemes
package would just passively register more TTK themes for the tkinter.ttk
“theme engine”.
Not so: the ttkthemes
package actually provides custom ThemedTk
and ThemedStyle
classes (derivatives of Tk
and Style
), which its friendly manual instructs to use in place of the standard classes, for loading the bundled themes.
This means the explanation given above about loading the Breeze theme using tkinter.ttk.Style().theme_use('breeze')
is not exactly correct, even though it technically works on Ubuntu; ttkthemes.ThemedStyle().theme_use('breeze')
should be used instead. Thanks to pippo1980 for making me realize this. This does not invalidate the original question but may make fully answering it somewhat more specific to the ttkthemes
package than I originally thought.
Nordine Lotfi’s answer almost resolves the question.
However, even while the described method works with all of the “bult-in” tkinter.ttk
themes, some of the additional ttkthemes
themes still escape it.
I made another small test program to illustrate this remaining issue. It tries to resolve a tkinter.ttk.Label
widget “font name” in each available theme:
import tkinter.font
import tkinter.ttk
try:
from ttkthemes import ThemedTk, ThemedStyle
root = ThemedTk()
style = ThemedStyle()
print( "Using ttkthemes.ThemedTk and ttkthemes.ThemedStyle." )
except ImportError:
print( "Using standard tkinter.Tk and tkinter.ttk.Style." )
root = tkinter.Tk()
style = tkinter.ttk.Style()
label = tkinter.ttk.Label( root, text="Hello world!" )
label.pack()
label_style = label.winfo_class()
def print_status( status, theme_name, font_name ):
print( f"[{status:^6}] {repr(theme_name):>12}: {repr(font_name)}" )
for theme_name in style.theme_names():
style.theme_use( theme_name )
# font_name = label['font']
font_name = style.lookup( label_style, 'font' )
try:
font = tkinter.font.nametofont( font_name )
print_status( "OK", theme_name, font_name )
except tkinter._tkinter.TclError as e:
print_status( "FAIL", theme_name, font_name )
#root.mainloop()
When I only have the modules tkinter
and tkinter.ttk
available in the Python environment, this program produces the following output on an Ubuntu system:
Using standard tkinter.Tk and tkinter.ttk.Style.
[ OK ] 'clam': 'TkDefaultFont'
[ OK ] 'alt': 'TkDefaultFont'
[ OK ] 'default': 'TkDefaultFont'
[ OK ] 'classic': 'TkDefaultFont'
If I install the ttkthemes
package (pip install ttkthemes
or apt install python3-ttkthemes
) and run the program again, I can no longer resolve all the “font names” (FAIL
indicates an exception happened when trying to look up the resolved font name with nametofont
):
Using ttkthemes.ThemedTk and ttkthemes.ThemedStyle.
[ OK ] 'ubuntu': 'TkDefaultFont'
[ FAIL ] 'breeze': 'Helvetica 10'
[ OK ] 'scidpurple': 'TkDefaultFont'
[ OK ] 'black': 'TkDefaultFont'
[ FAIL ] 'smog': ''
[ OK ] 'aquativo': 'TkDefaultFont'
[ OK ] 'arc': 'TkDefaultFont'
[ OK ] 'scidgrey': 'TkDefaultFont'
[ OK ] 'clearlooks': 'TkDefaultFont'
[ OK ] 'scidblue': 'TkDefaultFont'
[ OK ] 'yaru': 'TkDefaultFont'
[ OK ] 'kroc': 'TkDefaultFont'
[ OK ] 'scidmint': 'TkDefaultFont'
[ FAIL ] 'itft1': ''
[ OK ] 'scidsand': 'TkDefaultFont'
[ OK ] 'classic': 'TkDefaultFont'
[ OK ] 'alt': 'TkDefaultFont'
[ OK ] 'equilux': 'TkDefaultFont'
[ OK ] 'radiance': 'TkDefaultFont'
[ OK ] 'scidpink': 'TkDefaultFont'
[ OK ] 'winxpblue': 'TkDefaultFont'
[ FAIL ] 'blue': ''
[ OK ] 'default': 'TkDefaultFont'
[ OK ] 'scidgreen': 'TkDefaultFont'
[ OK ] 'clam': 'TkDefaultFont'
[ OK ] 'plastik': 'TkDefaultFont'
[ OK ] 'adapta': 'TkDefaultFont'
[ OK ] 'elegance': 'TkDefaultFont'
[ OK ] 'keramik': 'TkDefaultFont'
(Is there some silent fallback mechanism going on in the style engine which handles these invalid TTK font names? Are these “outlier” themes incorrectly defined?)
not sure my code is how it should be done, but googling around SO I figured out that there are ways to solve your question using
tkinter.font.Font
toghether withtkinter.ttk.Style
class (tkinter.ttk.Style) , see code below :