Search code examples
pythonpython-2.7tkinterttk

Why can't I define (and save) Tkinter fonts inside a function?


Defining a font inside a function and in the main body of the script seems to behave differently, and I can't seem to figure out how it's supposed to work.

For example, the Label in this example ends up being in a larger font, as expected:

from Tkinter import *
from ttk import *

import tkFont

root = Tk()

default = tkFont.Font(root=root, name="TkTextFont", exists=True)
large = default.copy()
large.config(size=36)

style = Style(root)
style.configure("Large.TLabel", font=large)

root.title("Font Test")

main_frame = Frame(root)
Label(main_frame, text="Large Font", style="Large.TLabel").pack()

main_frame.pack()
root.mainloop()

Screenshot of working version

However, if I try to define styles inside a function, it seems like the font gets deleted or garbage collected and is not available by the time the widget needs to use it:

from Tkinter import *
from ttk import *

import tkFont

def define_styles(root):
    default = tkFont.Font(root=root, name="TkTextFont", exists=True)

    large = default.copy()
    large.config(size=36)

    style = Style(root)
    style.configure("Large.TLabel", font=large)


root = Tk()

root.title("Font Test")

define_styles(root)

main_frame = Frame(root)
Label(main_frame, text="Large Font", style="Large.TLabel").grid(row=0, column=0)

main_frame.pack()
root.mainloop()

Screenshot of non-working version

Printing out tkFont.names() in the first version just before the main_frame.pack() lists the custom font as font<id>, but printing the same in the second version does not list the custom font outside the define_styles function. Do I have to do something special to save them?

Why can't I put that code in a function? Am I fundamentally misunderstanding something about how Fonts are supposed to be used? tkFont seems to have some kind of font registry, why aren't mine sticking around?


Solution

  • I have no evidence to back this up, but I believe that your large Font object is being garbage collected by Python once define_styles ends. This is because no pure Python objects have any references to it, even though the underlying Tcl implementation is still using it. This is a problem that afflicts Tkinter's PhotoImage class, as well.

    The workaround is to keep the object alive by making a long-lived reference to it. Just assign it to any old attribute on the root object, for example.

    def define_styles(root):
        default = tkFont.Font(root=root, name="TkTextFont", exists=True)
    
        large = default.copy()
        large.config(size=36)
    
        style = Style(root)
        style.configure("Large.TLabel", font=large)
        root.myfont = large
    

    Result:

    enter image description here