Древняя, новая, будущая

8 сентябрь, 2004 - 23:00Андрей Зубинский
Прежде чем мы перейдем непосредственно к Tk, уделим немного внимания "немодным новостям" из "тикль-мира". За время, отделяющее эту часть статьи от ее предшественницы, произошло несколько существенных изменений. Так, версия системы Tcl/Tk на момент написания статьи достигла 8.4.7, и, если вы пользуетесь более старой системой, желательно обновить ее (или с основного ресурса www.tcl.tk -- для пользователей Unix-совместимых ОС, или с сайта компании ActiveState). Также обновилась версия замечательной интерактивной среды отладки Tcl/Tk-программ RamDebugger, однозначно заслуживающей установки на компьютер "тиклолюба".

Ну а теперь все готово к тому, чтобы запустить руки в еще один ящик с "тикль-инструментами"...

Древняя, новая, будущая
Две строки кода -- завершенное GUI-приложение. Характерная особенность Tcl/Tk -- освобождение программиста от массы деталей, неизбежных при программировании GUI на компилируемых языках высокого уровня
Древняя, новая, будущая
Количество строк в "приложении" не изменилось, но теперь можно использовать штатный для данной платформы селектор цвета
Древняя, новая, будущая
Одна-единственная "кнопка" Tk способна "рассказать о себе" практически все. Рефлексивность Tk -- одно из серьезных достоинств этой библиотеки виджетов
Древняя, новая, будущая
Древняя, новая, будущая
Никаких команд перерисовки окон, ничего лишнего. Все те же две строки кода... Достаточно только изменить значение ресурса Tk -- и цвет кнопки меняется
Итак, Tk, он же -- Tool Kit, по-русски -- "набор инструментов". Лаконично, но непонятно. По сути, Tk представляет собой комплект из мощной библиотеки виджетов (сейчас мы уточним этот термин), имеющей самостоятельную ценность и допускающей применение при разработке программ на компилируемых языках (например, C, C++, Ada), и команд-инструментов Tcl, позволяющих создавать на этом языке интерактивные программы с графическим пользовательским интерфейсом. Термин "виджет" ничего таинственного не обозначает -- это окно, обладающее собственным характерным видом на экране и поведением. Хорошо знакомые по GUI разных операционных систем и прикладных программ элементы интерфейса -- кнопки, меню, диалоги, полосы прокрутки и т. п. -- все это виджеты, т. е. самостоятельные окна. Естественно, для создания завершенного приложения только виджетов недостаточно -- им необходимо придать некоторый порядок. За упорядочение виджетов в Tk отвечают два фундаментальных для практически всех GUI-подсистем механизма -- иерархии виджетов и геометрического менеджера. Первый механизм обеспечивает формирование композитных виджетов из виджетов-примитивов (это исключительно важная функция, и для ее поддержки в Tk присутствует специальный виджет -- контейнер-фрейм, предназначенный исключительно для удобства образования композитных элементов пользовательского интерфейса), второй отвечает за их взаимное расположение на экране. Последнее важное на этом уровне ознакомления сведение о Tk касается, естественно, архитектуры данной подсистемы -- она относится к классу "управляемых событиями". Это означает, что в исполнении Tcl-приложения с Tk GUI инициатива принадлежит пользователю -- именно он "генерирует" события (изменяет координаты мыши, нажимает на кнопки и т. д.), на которые реагирует программа. Tcl-инструменты Tk устроены таким образом, чтобы решать типовые задачи можно было буквально за считаные минуты -- так, для большинства событий в них предусмотрены обработчики по умолчанию, а в случае надобности программист может одной командой "связать" (bind) виджет, событие и "реагирующий" на него код. Более того, причинно-следственный механизм "связывания" (причина -- событие, инициированное пользователем, следствие -- соответствующие действия программы) допускает формирование собственных иерархий. Это так называемые "классы связываний" (в терминах Tk -- bindtags), отвечающие за поведение целой иерархии виджетов. И наконец, последнее необходимое высокоуровневое архитектурное понятие -- "фокус". В Tk в любой момент времени "в фокусе" системы находится один виджет -- это означает, что сообщения о событиях (например, о нажатиях клавиш) направляются системой этому виджету, т. е. инициируют его реакции на события. Tk располагает двумя основными моделями "фокусировки" виджетов -- инициируемой пользователем с помощью указателя мыши и при-нудительной, инициируемой программистом (что означает: программист может задавать находящийся в "фокусе" виджет, изменять "фокусировку" и даже перехватывать "фокусную информацию").

Теперь, без промежуточных отступлений, перейдем к конкретике связки Tcl--Tk. Вопросы использования библиотеки виджетов Tk из компилируемых программ оставляются читателю для самостоятельного ознакомления (естественно, в случае необходимости, так как связка Tcl--Tk обеспечивает все, что нужно для написания как "программ-однодневок", так и сложных программных комплексов с исключительно развитым GUI). Начнем с фундаментального -- с Tcl-синтаксиса описания иерархий виджетов в Tk. Благо, правила эти хорошо вписываются в синтаксические модели описания иерархий во многих языках программирования: корневое окно вашей программы обозначается символом "точка", затем следует имя виджета, являющегося первым непосредственным "потомком" корневого окна, затем, опять через "точку", следует "вложенный" в него "подвиджет" и т. д. Например, если мы хотим написать типовую первую Tk-программу (конечно же, "Здравствуй, мир!") и в ней использовать только один виджет-кнопку (назовем ее hellow), то ее полное иерархическое имя будет ".hellow" (что означает -- виджет с именем hellow, являющийся потомком корневого окна). Собственно, время для первой программы наступило -- ее текст мы разберем по косточкам:

button .hellow -text Say
Hello -command puts stdout
"Здравствуй, мир!"
pack .hellow -padx 50 -pady 10


Первое слово button -- это, естественно, команда-инструмент Tcl (маленькое уточнение относительно "первое слово" -- пользователи Windows вполне могут ограничиться таким синтаксисом, для работающих в Unix-подобных ОС в начале примера надо добавить "бэнг-строку" с указанием пути к интерпретатору wish). Эта команда (button) более чем детально "разжевана" в сопроводительной документации -- так, в chm-файле, поставляемом в комплекте ActiveState Tcl, ее описание находится в подразделе Tk Manual -- Widgets. Команда принимает в качестве параметров полное иерархическое имя создаваемого ею виджета "кнопка" и строку дополнительных опций. Каждая опция описывается подстрокой, начинающейся с символа "тире", за которым без пробела следует имя опции. В приведенном выше примере полное иерархическое имя ".hellow" указывает, что мы фактически создаем главное окно нашей "программы" и помещаем в него виджет-кнопку с именем ".hellow". Опция -text Say Hello не является специфической для виджетов-кнопок (button) -- это одна из многих опций, которыми "оснащено" большинство виджетов Tk (последнее замечание не случайно -- когда вы будете самостоятельно осваивать виджеты Tk, не забывайте, что в документации такие общие опции вынесены в отдельный раздел). Она специфицирует строку текста, отображаемого внутри виджета (к слову, во многих виджетах расположением отображаемого текста можно дополнительно управлять -- например, его выравниванием). В данном случае строка "Say Hello" будет написана в отображаемой кнопке. Вторая опция специфична для виджета-кнопки и позволяет удобно ассоциировать определяемые программистом действия с одним событием -- отпусканием кнопки мыши # 1 (в ПК -- это по умолчанию левая кнопка) после нажатия ею на виджет-кнопку. Здесь ассоциированное действие настолько примитивно, что для него не требуется написания процедуры или функции -- оно помещается в одну строчку и делает именно то, что ожидается от такой канонической программы, -- выводит в стандартный поток вывода фразу "Здравствуй, мир!". Итак, это была пока всего одна команда Tcl -- button (синтаксические принципы языка и механизм формирования команд описаны в предыдущих статьях данного цикла). Вторая строка -- новая команда, ее имя -- pack. Эта команда -- Tcl-интерфейс к одному из встроенных геометрических менеджеров Tk, который размещает виджеты "потомков" относительно границ их "предков". В данном случае виджет с именем hellow располагается в корневом окне программы, а специфика размещения указывается опциями (правила задания опций мы уже оговорили): -padx 50 специфицирует, что измеряемый по горизонтали экрана отступ кнопки от левой и правой границ корневого окна программы должен быть 50 пикселов, -pady 10 аналогично устанавливает отступы от верхней и нижней границы корневого окна в 10 пикселов. Вот, собственно, и все. Остальное, например реакции на стандартные GUI-события (касающиеся, в первую очередь, поведения корневого окна программы), мы даже и не вспоминаем -- Tk здесь полностью берет все на себя, а наша программа готова к исполнению. И, естественно, она выполняется именно так, как и ожидалось.

А теперь давайте немного модифицируем нашу первую программу, изменив в ней только реакцию на событие:

button .hellow -text
Say Hello -command
clipboard clear ;
clipboard append
"Здравствуй, мир!"
pack .hellow -padx 50 -pady 10


В данном примере вместо идеологически правильного в неинтерактивных скриптах приема -- вывода в стандартный поток -- мы применили не менее идеологически правильный для интерактивных программ прием вывода... в буфер копирования. В нем используется команда clipboard (полное описание в документации -- в разделе Tk Manual -- Inter-Client Communication), причем синтаксис ее настолько прост, что ни в каких дополнительных пояснениях не нуждается -- сначала буфер копирования очищается, затем к его содержимому добавляется строка "Здравствуй, мир!". Можно попробовать самостоятельно "ликвидировать" в этом примере очистку буфера копирования и, понажимав несколько раз на кнопку Say Hello, убедиться в том, что команда clipboard с опцией append именно добавляет что-то к содержимому буфера, а не полностью переписывает его. Этот пример не случаен -- он показывает один из путей создания простейших, но очень полезных Tcl/Tk-программ. А именно, GUI-утилиток буквально из нескольких десятков строк кода, позволяющих удобно интерактивно задавать... опции для хороших, но перегруженных излишествами программ командной строки (в конце концов, удерживать в голове три-четыре десятка опций командной строки нечасто используемой программы нерационально, а каждый раз "подглядывать" в руководство -- неудобно).

Если бы в перечне виджетов Tk были только кнопки, естественно, этот инструментальный набор не -заслуживал бы нашего внимания. Кроме кнопок, Tk располагает уже упомянутыми ранее фреймами (команда frame), мощным примитивом, создающим окно со структурированной графикой (команда canvas), очень функциональными так называемыми радиокнопками (checkbutton, кнопки с фиксацией состояния -- в отличие от обычных радиокнопки не только "помнят" свое состояние, но и характеризуются двусторонним ассоциированием со значениями указанных глобальных переменных Tcl -- как изменение пользователем состояния радиокнопки автоматически приводит к изменению значения переменной, так и изменение ее значения автоматически отображается изменением состояния кнопки), списками выбора, меню, полосами прокрутки, ключевым компонентом построения редакторов текста (команда text), а также несколькими мощными специфическими примитивами GUI. Кроме базовых виджетов, в состав Tk входят также готовые общеупотребительные "мегавиджеты" -- например, стандартный диалог селектора цвета tk_chooseColor (попробуйте в предыдущем примере изменить реакцию кнопки hellow, например так: -command tk_choose-Color -title "Выберите цвет"). Кроме этого, развивавшаяся десятилетиями и продолжающая развиваться система обросла множеством мощных расширений -- библиотек "мегавиджетов" (это принятый в мире тикль термин, обозначающий композитные повторно используемые конструкции для создания GUI-приложений), так что зачастую, прежде чем пытаться написать какой-то высокоуровневый элемент GUI, стоит поискать подходящую, уже отлаженную его реализацию. Перечень заслуживающих внимания библиотек начинают уже ставшие де-факто стандартными разработки BLT, BWidgets, Tix. Что же касается геометрических менеджеров, то уже обсужденная команда pack -- далеко не единственная в системе. Кроме нее, можно использовать геометрический менеджер, обеспечивающий "абсолютное расположение на резиновом листе" (команда place), -- размещенные ею виджеты-потомки могут автоматически изменять размеры при изменении размеров окна предка. Если требуемое расположение характеризуется регулярностью, можно воспользоваться сеточным геометрическим менеджером (его еще называют "табличным", команда grid). И наконец, можно динамически изменять иерархию виджетов (в терминах GUI-систем часто оперируют понятием "стековый порядок", stacking order) командами lower и rise.

Теперь, когда вы вкратце знакомы с двумя основными принципами построения GUI -- образованием иерархий из виджетов и размещением виджетов (пусть это знакомство проводилось на очень простом примере -- фундаментальные принципы от этого не меняются, и более сложные конструкции будут отличаться разве что объемом практически типового кода), можно перейти к более сложным вещам. Начнем мы с... объектно-ориентированных свойств виджетов Tk. Несмотря на то что Tcl/Tk реализована на далеком от объектной ориентации языке C, Tk можно назвать образцовой объектно-ориентированной подсистемой. Каждый виджет в ней -- независимо от его типа, инкапсулирует массу информации, отве-чающей за его вид и поведение. И для доступа к этой информации ис-пользуется весьма специфическая конструкция вида "полное_иерархическое_имя_виджета config необязательные_опции". Давайте немного модифицируем наш пример и приведем его к такому виду:

button .hellow -text
Параметры виджета button
-command clipboard clear ;
clipboard append [.hellow config]
pack .hellow -padx 50 -pady 10


В общем, модификации не так уж и страшны -- мы изменили надпись на кнопке (теперь она -- "Параметры виджета button") и реакцию на отпускание пользователем нажатой кнопки GUI hellow. Результат мы заносим в предварительно очищенный буфер копирования, а вот этим самым результатом является перечень (точнее -- список) всех параметров виджета button. Это один из вариантов применения указанной выше конструкции "...config ...", при котором мы можем наблюдать так называемое свойство рефлективности Tk (термин "рефлективность", применяемый к программным и, в общем, к вычислительным системам, означает способность системы хранить полную информацию о самой себе и использовать эту информацию для -изменения поведения). Теперь произведем еще одну модификацию, выбрав из полученного перечня, например, параметр -fg (или -foreground):

button .hellow -text
Параметры виджета button
-command clipboard clear ;
clipboard append
[.hellow config -fg]
pack .hellow -padx 50 -pady 10


Здесь изменения минимальны -- мы только использовали уточняющий параметр, о котором хотим получить исчерпывающую информацию. Запускаем эту программу, нажимаем на кнопку "Параметры виджета button" и вставляем из буфера копирования результат: -foreground foreground Foreground SystemButtonText SystemButtonText. В этом случае виджет .hellow рассказал нам о себе следующее: у него была запрошена информация о параметре "цвет выводимых объектов" (-foreground); данному параметру соответствует ресурс системы с именем foreground, относящийся к классу Foreground, для которого значение по умолчанию равно значению переменной SystemButtonText и текущее значение также равно SystemButtonText (пользователи Unix-подобных систем в качестве значений могут получить в результате выполнения этой программы принятые в X Window описатели цвета в формате "#шестнадцатеричное_число"). Понятие ресурсов -- еще одно фундаментальное в Tk. Оно обозначает иерархическую информационную базу параметров виджетов, допускающую как модификацию одного параметра выбранного виджета, так и одновременную -- всех виджетов иерархии или множества параметров одного класса для виджетов разных иерархий. Мы же ограничимся последней модификацией нашего примера для демонстрации возможности интерактивной модификации одного из ресурсов Tk -- например цвета фона кнопки hellow:

button .hellow -text
Параметры виджета button
-command .hellow config
-bg [tk_chooseColor
-title "Цвет фона"]
pack .hellow -padx 50
-pady 10


Здесь мы использовали только уже известные по этому краткому обзору конструкции, с единственной оговоркой -- диалог tk_chooseColor возвращает в вызывающую Tcl-программу выбранный цвет. Теперь, нажимая на кнопку GUI "Параметры виджета button", мы сначала активируем диалог выбора цвета (почему сначала -- в предыдущих статьях цикла), затем результат выбора пользователем с помощью конструкции типа ".hellow config -bg цвет" присваивается значению ресурса background виджета .hellow и... кнопка автоматически меняет цвет своего фона -- не нужны ее "перерисовки вручную" и прочие присущие GUI-программам "извращения".