Фольклор Tkinter

Рассел Оуэн (Russell Owen).

Tkinter - это интерфейс Python к графическим инструментальным средствам Tk. Настоящий документ содержит информацию о Tkinter, которая показалась мне труднодоступной или неожиданной.

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

Переменные Tk

Переменные Tk (StringVar, IntVar, BooleanVar и DoubleVar) имеют несколько применений, в том числе автоматическое обновление свойств графических элементов управления (изменение переменной Tk вызывает обновление элемента управления) и выполнение функций обратного вызова при изменении значения переменной Tk. Автоматическое обновление элементов управления реализуется очевидным образом: при создании или настройке элемента управления переменная Tk определяется в качестве его опции (обычно именуемой "textvariable"). Здесь же описываются отслеживания (traces).

Для отслеживания меняющегося значения применяют метод trace_variable. Подробности:

Пример

import Tkinter
# еще до создания переменных Tk должно быть создано корневое окно
root = Tkinter.Tk()

# создается строковая переменная Tk
myVar = Tkinter.StringVar()

# определяется функция обратного вызова, описывающая свои аргументы на входе
# и изменяющая значение переменной
def callbackFunc(name, index, mode):
  print "callback called with name=%r, index=%r, mode=%r" % (name, index, mode)
  varValue = root.getvar(name)
  print "    and variable value = %r" % varValue
  # изменяет значение, чтобы показать, что это может быть сделано
  root.setvar(name, varValue + " modified by %r callback" % (mode,))

# создается отслеживание для записи и чтения;
# возвращаемые имена сохраняются для последующего удаления отслеживания
wCallbackName = myVar.trace_variable('w', callbackFunc)
rCallbackname = myVar.trace_variable('r', callbackFunc)

# задает значение, запуская обратный вызов записи
myVar.set("first value")

# получает значение, запуская обратный вызов считывания, и затем выводит значение на экран;
# при этом в выражении для печати нового получения значения не происходит,
# потому что тогда выход с выражения для печати и после обратного вызова
# перемешается, создавая путаницу
varValue = myVar.get() # запуск обратного вызова считывания
print "after first set, myVar =", varValue

# снова происходит задание и получение значения для демонстрации того, 
# что отслеживающие обратные вызовы еще существуют
myVar.set("second value")
varValue = myVar.get() # запуск обратного вызова получения значения
print "after second set, myVar =", varValue

# удаляем обратный вызов при записи и еще раз проводим задание и получение значения
myVar.trace_vdelete('w', wCallbackName)
myVar.set("third value")
varValue = myVar.get() # запуск обратного вызова считывания
print "after third set, myVar =", varValue
root.mainloop()

Выход:
callback called with name='PY_VAR0', index='', mode='w'
    and variable value = 'first value'
callback called with name='PY_VAR0', index='', mode='r'
    and variable value = "first value modified by 'w' callback"
after first set, myVar = first value modified by 'w' callback modified by 'r' callback
callback called with name='PY_VAR0', index='', mode='w'
    and variable value = 'second value'
callback called with name='PY_VAR0', index='', mode='r'
    and variable value = "second value modified by 'w' callback"
after second set, myVar = second value modified by 'w' callback modified by 'r' callback
callback called with name='PY_VAR0', index='', mode='r'
    and variable value = 'third value'
after third set, myVar = third value modified by 'r' callback

Предпочтения в приложениях Tk

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

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

Цвета

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

Шрифты

Если в качестве шрифта для элемента управления задать некоторый именованный шрифт (в терминах Tk; в Tkinter эквивалентом будет объект Font), то шрифт элемента управления будет автоматически обновляться при изменении этого именованного шрифта!

Чтобы все работало автоматически, определите базу данных опций с помощью именованных шрифтов еще до создания каких-либо элементов управления. Тогда элементы управления будут создаваться с автоматически обновляемыми шрифтами (если только вы явно не присвоите шрифтовой опции какое-нибудь другое значение). В приводимом ниже примере сначала считываются установки шрифтов, принятые по умолчанию (путем создания временных элементов управления), а затем определяется база данных опций. В моей системе элементы управления Entry (ввод) и Text (текст) используют один шрифт, а все остальные элементы управления - другой (по умолчанию), так что я разрешаю применение только этих двух категорий шрифтов. Этот код будет вполне хорош, если желательно сохранить соответствие стандартной базе данных опций Tk. К другим средствам получения начальных шрифтовых значений у объектов Font обращаются при необходимости хранить предпочтения в каком-либо другом виде.

import Tkinter
import tkFont # нужно для использования объектов Font (шрифт)!

root = Tkinter.Tk()

# Создание именованных шрифтов, считывание текущих значений по умолчанию
# (итак, если пользователи используют файлы с базой данных опций, их предпочтения будут учтены).
mainFontDescr = Tkinter.Button()["font"] # то же самое для элементов Label, Checkbutton, Menu...
entryFontDescr = Tkinter.Entry()["font"] # то же самое для элемента Text
mainFont = tkFont.Font(font=mainFontDescr)
entryFont = tkFont.Font(font=entryFontDescr)
# Формирование базы данных опций; убедитесь, что сначала задаются опции более общего характера.
root.option_add("*Font", mainFont)
root.option_add("*Entry*Font", entryFont)
root.option_add("*Text*Font", entryFont)

# Это черновой демонстрационный пример автоматического обновления шрифта
Tkinter.Label(root, text="Test Label").pack()
fontList = tkFont.families()
entryVar = Tkinter.StringVar()
entryVar.set(mainFont.cget("family"))
def setMainFont(varName, *args):
	mainFont.configure(family = root.getvar(varName))
entryVar.trace_variable("w", setMainFont)
mainMenu = Tkinter.OptionMenu(root, entryVar, *fontList)
mainMenu.pack()

Предупреждение: в базе данных опций хранится только имя именованного шрифта (а не сам объект Font), и я не нашел способа, как с его помощью изменить этот шрифт! В частности, я не смог воссоздать объект Font по имени именованного шрифта. Вызов nametowidget не работает, команда Font(name=имя-существующего-шрифта, font=имя-существующего-именованного-шрифта) выдает ошибку, утверждая, что этот шрифт уже существует; Font(font=имя-существующего-именованного-шрифта) работает, но, конечно же, это новый именованный шрифт, так что изменение получившегося шрифта не будет иметь никаких последствий. Возможно это как-то можно сделать, пока же могу посоветовать держать созданные объекты Font под рукой, чтобы было что изменять!

Как скрыть элементы управления

Чтобы спрятать элемент управления достаточно, чтобы о нем забыл диспетчер компоновки. В случае упаковщика (packer) поможет команда widget.pack_forget(), после которой, правда, теряется вся информация о местоположении элемента управления. Это может послужить источником головной боли, если спрятанный элемент потребуется снова выводить на экран. Для диспетчера grid (сетка) существует лучший метод - widget.grid_remove(), который сохраняет параметры сетки для данного элемента управления, так что его всегда можно снова показать, просто воспользовавшись widget.grid().

Проблема: фрейм-контейнер будет сжиматься, по мере удаления из него элементов управления за исключением последнего. Но при удалении последнего оставшегося элемента фрейм сжиматься не будет (так происходит, по крайней мере, на Mac при работе с MacPython 2.0). Это не очень хорошо, если необходимо иметь возможность удалять все элементы управления из фрейма, так чтобы он сжимался до нуля. Самый простой выход - включить в фрейм-контейнер пустой (то есть крошечный) фрейм и никогда его не удалять. Тогда при удалении последнего видимого элемента управления фрейм-контейнер будет сокращаться почти до нуля (до размера пустого фрейма).

Исправления


Перевод на русский язык:
Ф.С.ЗАНЬКО



Примечание переводчика

Настоящий перевод можно свободно распространять и использовать, но нельзя продавать. При этом текст перевода должен оставаться в неизменном виде.

О замеченных ошибках, неточностях, опечатках просьба сообщать по электронному адресу:
zanko_philipp@mail.ru

Интернет-адрес оригинального документа:
http://www.astro.washington.edu/users/rowen/ROTKFolklore.html

Интернет-адрес перевода:
http://www.russianlutheran.org/python/python.html