Краткий обзор Tkinter

Это краткая справочная информация к Tkinter. Ее следует рассматривать как дополнение, а не альтернативу основному справочному материалу. Глубина изложения варьируется от самого простого уровня до изощренных приемов. Документ содержит информацию, которую мне самому было трудно найти.

Элементарное приложение Tk

import Tkinter
root = Tkinter.Tk()
# опишите свой интерфейс, затем запустите приложение с помощью команды:
root.mainloop()

Стандартные элементы управления

Простые элементы управления: Toplevel, Frame, Button, Checkbutton, Entry, Label, Listbox, OptionMenu, Photoimage, Radiobutton, Scale.

Сложные элементы управления: Canvas, Text. Они могут содержать внутри себя помеченные тегами элементы, которые ведут себя подобно объектам.

Трудные для использования: Menu, Menubutton, Scrollbar.

Переменные Tk: StringVar, IntVar, DoubleVar, BooleanVar. Это своего рода контейнеры данных (контейнер - объект или приложение, содержащие другие объекты.- Прим. пер.). Они необходимы для некоторых элементов управления и взаимодействуют со многими другими, обеспечивая легкий доступ к их содержимому, а также могут инициировать обратные вызовы при изменении своих данных.

Дополнительные модули (не загружаемые командой "import Tkinter")

tkFont определяет класс Font для параметров шрифта и присвоения имен шрифтов. Если объект Font связать с шрифтом элемента управления, тогда изменение объекта Font будет автоматически приводить к соответствующим изменениям данного элемента управления.

FileDialog определяет FileDialog, LoadFileDialog, SaveFileDialog. Пример:

fdlg = FileDialog.LoadFileDialog(root, title="Выберите файл")
fname = fdlg.go() # необязательные аргументы:
dir_or_file=os.curdir, pattern="*", default="", key=None) if fname == None: # отмена пользователем

tkColorChooser определяет функцию askcolor(initialcolor), которая возвращает выбранный пользователем цвет.

tkSimpleDialog определяет askinteger(title, prompt, initialvalue, minvalue, maxvalue), askfloat и askstring.

Просмотрите исходный код файлов с расширением .py в каталоге .../Lib/lib-tk. Там есть и другие модули, а также примеры использования.

Диспетчеры компоновки

Упаковщик (диспетчер Pack)

pack(side="top/right/bottom/left", expand=0/1, anchor="n/nw/w...", fill="x/y/both")

Диспетчер сетки (Grid)

grid(row, column, rowspan=?, columnspan=?, sticky="news", ipadx=?, ipady=?, padx=?, pady=?)

columnconfigure(row, weight=?, minsize=?, pad=?)

columnconfigure(column, weight=?, minsize=?, pad=?

События и обратные вызовы (callbacks)

События

События описываются с помощью строк вида: "<modifiers-type-qualifier>" ("модификаторы-тип-определитель").

Типы событий:

Предупреждение: то, какие именно события видит тот или иной элемент управления, зависит от платформы.

Определители для KeyPress и KeyRelease - это символы клавиатуры (keysyms). Буквы и цифры используются как есть, но знаки пунктуации и все остальные клавиши требуют особых, чувствительных к регистру имен, как-то: comma (запятая), period (точка), dollar (доллар), asciicircum (^), numbersign (#), exclam (восклицательный знак), Return, Escape, BackSpace, Tab, Up (вверх), Down (вниз), Left (влево), Right (вправо)... В сомнительных случаях запустите интерактивный тест, наподобие примера, приведенного ниже.

Для того чтобы при наступлении некоторого события происходило обращение к обратному вызову (callback), создайте соответствующую привязку (bind):
widget.bind(event, callback)

Ниже приводится пример, в котором при нажатии любой кнопки клавиатуры выводится соответствующий символ клавиши:

        #!/usr/local/bin/Python
        """Отображает символ клавиши (keysym) 
        для каждого события KeyPress (нажатия клавиатуры)."""
        import Tkinter
	
        root = Tkinter.Tk()
        root.title("Регистратор символов клавиатуры")
	
        def reportEvent(event):
                print 'keysym=%s, keysym_num=%s' % (event.keysym, event.keysym_num)
	
        text  = Tkinter.Text(root, width=20, height=5, highlightthickness=2)
	
        text.bind('<KeyPress>', reportEvent)
	
        text.pack(expand=1, fill="both")
        text.focus_set()
        root.mainloop()

Обработчик протокола WM_DELETE_WINDOW

Обратный вызов (callback) события Destroy (уничтожить) запускается слишком поздно, чтобы с его помощью можно было очистить элемент управления или предотвратить уничтожение окна. Сделать что-либо из перечисленного удастся, если применить обработчик протокола WM_DELETE_WINDOW. (Но если удаление требуется при выходе из приложения, лучше воспользоваться стандартной библиотекой python "atexit"; это проще!). Этот протокол позволяет производить действия, отличные от поведения, принятого по умолчанию. Если вам действительно нужно уничтожить окно, сделайте это сами. Данный обратный вызов (callback) не получает никаких аргументов.

toplevel.protocol("WM_DELETE_WINDOW", callback)

Существуют два других протокола: WM_SAVE_YOURSELF и WM_TAKE_FOCUS. Не вполне ясно, для чего они нужны.

Команды элементов управления

Элементы управления, такие как Button (кнопка), имеют параметр "command" ("команда"). В данном случае обратный вызов (callback) не получает никаких аргументов. Это самый лучший способ запустить обратный вызов (callback) нажатием кнопки Button, поскольку не существует другого события, связанного с мышью, делающего ту же самую работу. Конечно жаль, что "командные" (относящиеся к параметру "command".- Прим. пер.) обратные вызовы (callbacks) не передают функции обратного вызова (callback function) никакой информации, но ситуацию легко исправить с помощью т.н. оболочки обратного вызова (Callback Shim).

Переменные слежения

Для того чтобы при изменении переменной Tk запускался обратный вызов (callback), можно воспользоваться методом trace_variable:
traceName = tkvar.trace_variable(mode, callback)

After: синхронизированные события и анимация

Чтобы запустить обратный вызов (callback) с определенной задержкой, например для анимации, воспользуйтесь методом after (после):
widget.after(timems, callback, arg1, arg2...)

Обработчики файлов/сокетов

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

Обработчики файлов

Обработчик файлов обращается к обратному вызову (callback), когда в файле либо сокете есть данные для чтения или записи. Обработчики файлов просты и хорошо интегрированы в Tkinter. К сожалению, они не работают под Windows (по крайней мере с версией Python 2.3.4 и Windows XP). Но если ваша программа будет запускаться только на unix и/или MacOS X, то из-за простоты применения предпочтение следует отдать именно им.

wdg.tk.createfilehandler(file_or_socket, mask, callback)
где wdg - произвольный элемент управления Tkinter (если под рукой ничего нет, создайте для этих целей новый фрейм (frame)).

Сокеты Tcl

Сокеты Tcl полностью кроссплатформенны, но пользоваться ими немного труднее, поскольку приходится самостоятельно писать tcl-код. Тем не менее, это совсем не сложно, а затраченные усилия с лихвой окупаются тем, что вы получаете полностью переносимый код. Как пример использования tcl-сокетов можно загрузить мой пакет RO package и просмотреть RO.Comm.TkSocket. См. также следующее сообщение Мэтью Синсера (Matthew Cincera), которое я взял в качестве отправной точки (попутно приношу благодарность Стефану Беланду (Stephane Beland), посоветовавшему мне эту ссылку).

Twisted Framework

Twisted Framework - это свободная кроссплатформенная сетевая библиотека, работающая с несколькими разными инструментариями GUI (фактически, для своей работы она и не нуждается в каком-либо GUI-инструментарии). У нее очень хорошая репутация. До сих пор мне не приходилось пользоваться этой библиотекой, но, скорее всего, когда-нибудь я на нее перейду.

Оболочки обратного вызова (каррированные функции)

Так получается, что мне часто бывает нужно передать в функцию обратного вызова (callback function) больше данных, чем это предусмотрено. К примеру, элемент управления Button (кнопка) не передает никаких аргументов через свой "командный" (имеется ввиду параметр command) обратный вызов (callback), но может оказаться эффективнее использовать одну функцию обратного вызова (callback function) для управления несколькими кнопками; в этом случае мне необходимо знать, какая именно кнопка была нажата.

Реализовать это возможно, определив функцию обратного вызова (callback function) непосредственно перед ее пересылкой в элемент управления и включив в нее всю дополнительную информацию, которая вам нужна. К сожалению, Python, как и большинство других языков, не поддерживает на достаточно хорошем уровне смешение раннего связывания (информация, известная при определении функции) и позднего связывания (информация, известная при вызове функции). Я считаю, что самым простым и ясным будет следующее решение:

Надеюсь, что приведенный далее пример прояснит сказанное.

Оболочка обратного вызова (callback shim), которой я пользуюсь, называется RO.Alg.GenericCallback. Она входит в мой пакет RO package. Упрощенная версия оболочки, не умеющая работать с аргументами в виде ключевых слов, описана в примере, приведенном ниже по тексту. Весь код оболочки основан на python-рецепте Скотта Дэвида Дэниэлса (Scott David Daniels), который называет этот прием "каррирование функции" ("currying a function"); последний термин, возможно, более распространен, чем "оболочка обратного вызова" ("callback shim").

        #!/usr/local/bin/Python
        """Пример, демонстрирующий применение оболочки обратного вызова (callback shim)"""
        import Tkinter
	
        def doButton(buttonName):
		"""Желаемый обратный вызов (callback). Мне потребуется оболочка
обратного вызова (callback shim), поскольку через параметр command элемента
Button (кнопка) обратные вызовы (callback) никаких аргументов получить не могут."""
print buttonName, "pressed" class SimpleCallback: """Создается оболочка обратного вызова (callback shim), основанная на коде,
предложенном Скоттом Дэвидом Дэниелсом (Scott David Daniels),
который может работать и с ключевыми словами-аргументами.""" def __init__(self, callback, *firstArgs): self.__callback = callback self.__firstArgs = firstArgs def __call__(self, *args): return self.__callback (*(self.__firstArgs + args)) root = Tkinter.Tk() buttonNames = ("Button 1", "Button 2", "Button 3") for name in buttonNames: callback = SimpleCallback(doButton, name) Tkinter.Button(root, text=name, command=callback).pack() root.mainloop()

[Примечание переводчика : В стандартной библиотеке Python 2.5+ появилась функция, реализующая каррирование. См. http://docs.python.org/library/functools.html#functools.partial. Но гораздо проще передавать аргументы в вызываемую функцию (callback) с помощью lambda. См. http://www.russianlutheran.org/python/lambda/python_lambda.html (добавлено по совету А.Иваненко).]

Индексирование

Элемент управления Text (текст)

Эти обозначения могут быть модифицированы путем добавления следующих строк:

Например, "1.0 lineend" означает ссылку на конец первой строки.

Элемент управления Entry (ввод)

Получение информации от элементов управления

Есть несколько путей, как получить информацию о настройке элемента управления:

Во всех случаях каждый параметр настройки возвращается в виде строки. Это может стать главным источником головной боли. К примеру, булевы значения будут "0" или "1" (оба из которых логически истинны в Python). Проблема становится еще хуже, когда речь заходит об извлечении обычных объектов Tkinter, таких как переменные Tk или графические элементы управления. Следующий пример иллюстрирует эту проблему (хотя и в такой простой ситуации, когда приходится иметь дело лишь с оригинальными объектами Tk); ниже обсуждаются ее возможные решения:

import Tkinter
root = Tkinter.Tk()
aVar = Tkinter.StringVar()
aLabel = Tkinter.Label(textvar = aVar)
aLabel["textvar"]
  'PY_VAR0'
root.setvar(aLabel["textvar"], "foo")
aLabel.getvar(aLabel["textvar"])
  'foo'
str(aLabel)
  '.8252000'
root.nametowidget(str(aLabel))
  <Tkinter.Label instance at 0x7dea60>
aLabel.master
  <Tkinter.Label instance at 0x7dea60>
root.master
  None
	

Выбор подхода зависит от того, что вы хотите извлечь:

Советы

Это перечень ошибок, часто встречающихся при программировании на python и Tkinter, а также способы их избежания.

Ресурсы

Автор документа - Рассел Оуэн (Russell Owen). Последнее изменение 2004-10-12 (переписан раздел Обработчик файлов/сокетов, кроме того удалена неработающая ссылка на FAQ - ответы на часто задаваемые вопросы). Настоящий документ можно свободно распространять и использовать, но нельзя продавать.


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



Исправления:

9 февраля 2012 г. - исправлены некоторые неточности перевода; внесены изменения в терминологию; по предложению Андрея Иваненко добавлено примечание о возможности использования lambda для реализации обратных вызовов (callbacks).



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

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

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


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

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