5

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?

Collin
  • 11,977
  • 2
  • 46
  • 60

1 Answers1

7

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

Kevin
  • 74,910
  • 12
  • 133
  • 166
  • It makes sense, and does fix the example (and the larger program that spurred this question in the first place), if a bit unsatisfying. Is this a bug then? It could be missing documentation, but I could be filling out missing docs bugs for tkinter from now until eternity. – Collin Sep 04 '14 at 17:50
  • 3
    I think it's in the gray area between "bug" and "surprising interaction of complex systems". I would love it if this kind of manual reference keeping wasn't needed, but for all I know the devs have an excellent reason for having it this way. – Kevin Sep 04 '14 at 18:04
  • Just ran into this quirk today - still the case with Python 3.7. It does get garbage collected, and in the `__del__` method of `Font`, there's a call to Tk to delete it from its internal font inventory as well. – Supra621 Mar 21 '20 at 15:07