Введение в проблему масштабирования шрифтов в Tkinter

Знакома ситуация: вы вылизываете интерфейс на Python, настраиваете идеальный кастомный шрифт в Tkinter, но стоит пользователю стереть последний символ в поле ввода — и всё оформление мгновенно откатывается к унылому дефолту из девяностых? Это ломает визуальный ритм приложения в самый неподходящий момент.

Библиотека Tkinter остается стандартным инструментом для создания GUI на Python благодаря своей доступности и отсутствию внешних зависимостей. Для работы с многострочным текстом разработчики часто выбирают виджет ScrolledText, объединяющий текстовое поле Text и полосу прокрутки Scrollbar.

Однако на практике девелоперы сталкиваются с неочевидным багом: при полном удалении всего текста в ScrolledText размер шрифта внезапно сбрасывается к дефолтному системному значению. Это портит UX и ломает визуальное оформление приложения.

В этой статье мы разберем архитектурные причины этого сбоя, напишем воспроизводимый пример и предложим надежные варианты исправления с использованием конфигурации тегов.

Анатомия виджета ScrolledText и скрытая магия тегов

Представьте, что вы пишите клиент для заметок, где пользователь волен полностью очистить холст, но при этом стиль блокнота должен сохраняться. Почему же стандартный подход дает сбой? Давайте разберемся под капотом.

Виджет ScrolledText из модуля tkinter.scrolledtext — это не атомарный элемент, а Frame, содержащий внутри себя стандартный Text и Scrollbar.

Когда вы задаете шрифт через config(font=('Arial', 14)), вы меняете базовый стиль виджета. Но в модели Tkinter форматирование привязано к символам и диапазонам через теги (tags). Полная очистка поля (от '1.0' до 'end') стирает не только текст, но и все сопутствующие теги, из-за чего интерфейс откатывается к дефолтным настройкам TCL/Tk.

Воспроизведение бага: минимальный рабочий пример

Для демонстрации проблемы напишем простой скрипт на Python, который создает текстовое поле с кастомным размером шрифта:

import tkinter as tk
from tkinter.scrolledtext import ScrolledText

root = tk.Tk()
root.geometry("400x300")

st = ScrolledText(root, font=("Courier", 14))
st.pack(expand=True, fill="both")

st.insert("1.0", "Удалите весь этот текст, чтобы увидеть баг со шрифтом.")

root.mainloop()

Если выделить весь текст (Ctrl+A) и нажать Backspace, а затем начать вводить новый текст, он отобразится стандартным системным шрифтом (обычно Times New Roman мелкого размера). Баг воспроизведен.

Способы решения проблемы

Отыскав корень зла, перейдем к практической магии — заставим интерфейс держать удар при любых действиях пользователя.

Способ 1: Использование конфигурации тега по умолчанию

Вместо применения шрифта ко всему виджету напрямую, лучше настроить дефолтный тег или использовать свойство sel и конфигурацию самого текстового пространства через тег tcl:

import tkinter as tk
from tkinter.scrolledtext import ScrolledText

def on_key_release(event):
    # Привязываем конфигурацию шрифта к первому тегу по умолчанию
    st.tag_configure("all_text", font=("Courier", 14))

root = tk.Tk()
st = ScrolledText(root)
st.pack(expand=True, fill="both")

# Применяем тег ко всему содержимому при изменении
st.config(font=("Courier", 14))

Способ 2: Перехват событий удаления и переустановка шрифта

Самый прагматичный подход в production-коде — принудительно восстанавливать параметры шрифта, если длина текста стала нулевой:

import tkinter as tk
from tkinter.scrolledtext import ScrolledText

class FixedScrolledText(ScrolledText):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.default_font = ("Arial", 12)
        self.config(font=self.default_font)
        
        # Следим за изменениями
        self.bind('<KeyRelease>', self.check_empty)

    def check_empty(self, event):
        content = self.get('1.0', 'end-1c')
        if not content:
            self.config(font=self.default_font)

root = tk.Tk()
st = FixedScrolledText(root)
st.pack(expand=True, fill="both")
st.insert('1.0', 'Текст для теста...')
root.mainloop()

Заключение

Баг со сбросом шрифта в Tkinter ScrolledText — классический пример того, как особенности движка TCL/Tk могут неожиданно испортить пользовательский опыт. Теперь у вас в арсенале есть пара надежных способов защитить свои десктопные приложения от подобных сюрпризов. Попробуйте внедрить класс-наследник в свой текущий проект на Python и забудьте о проблеме сбрасывающегося форматирования раз и навсегда!