Использование словарей в tcl

от автора

Приветствую, Хабра! %username, ты наверняка слышал про такой язык как tcl и его графический тулкит tk. Язык отличается особой гибкостью (на мой скромный взгляд). Пока нет много времени написать об «облачной» разработке, решил написать мини-статью/мини-урок про одну из возможностей замечательного языка tcl — словарях ( причем, их я не могу вспомнить по книге «Практическое программирование на Tcl/TK», быть может просто читал старую версию). Наиболее полное описание данного типа данных, %username, ты можешь найти по ссылкe.

Немного теории — словарь в tcl это такой тип данных, которые позволяет хранить сложные упорядоченные значения по типу «ключ-значение», причем с возможностью ветвить само дерево. Иными словами — на один ключ (к примеру ID записи) может приходиться не одно значение, а еще несколько ключей со значениями. Выглядит в синтаксисе tcl это примерно так:

dict set programSettings 0 title "myProgram" dict set programSettings 0 min-width 800 dict set programSettings 0 min-height 600 dict set programSettings 0 resizable-x 1 dict set programSettings 0 resizable-y 1 

Что делает данный код? Все просто — он создает словарь programSettings, который имеет атрибуты title, min-width, min-height, resizable-x, resizable-y. Причем, за счет ID элемента в словаре мы можем натирать настройки для других root-окон программ (такое может потребоваться, если вы планируете делать многооконный
интерфейс).

Доступ к значению из словаря можно получить по средствам выполнения следующей команды:

dict get <имя-словаря> <атрибуты> 

Давайте «нарисуем» окно Tk с помощью нашего словаря. Обычный код, выглядел бы таким образом:

wm title . "myProgram" wm resizable . 1 1 wm minsize . 800 600 

Наш код будет иметь следующий вид:

wm title . [dict get $programSettings 0 "title"] wm resizable . [dict get $programSettings 0 "resizable-x"] [dict get $programSettings 0 "resizable-y"] wm minsize . [dict get $programSettings 0 "min-width"] [dict get $programSettings 0 "min-height"] 

Что нам это даст? По своей сущности, словари (как, впрочем и все данные в tcl) — мутабельны. Следовательно могут быть переопределены. Таким образом, мы получаем окно, которое при переопределении значения, полученного из словаря, меняется. При этом перезапуск программы не требуется. Иными словами — настройки вашего GUI будут подхватываться динамически. Но, все это можно легко реализовать через список. Тогда зачем нам использовать словарь. Ну, во первых — использование словаря сделает программу более читаемой, ее данные — более систематизированными (по сути вы держите некое подобие таблицы базы данных в коде). А с другой… сейчас покажу.

Предположим, нам необходимо сделать обычное верхнее меню. Как виджет — оно имеет свою имя, а так же опцию tearOff. Для хранения настроек создадим еще один
словарь:

dict set menu-settings 0 name mainMenuBar dict set menu-settings 0 tearOff 1 

Данные о типе выпадающего меню из меню-бара добавим сразу:

option add *tearOff [dict get $menu-settings 0 "tearOff"] 

Так же, предположим, мы хотим дать пользователю возможность включать и отключать верхнее меню:

dict set user-settings 0 menu-bar true  

А вот с отрисовкой давайте немного повременим. Описывать весь GUI вручную не имеет особого смысла, ведь у нас в арсенале очень мощный инструмент — итеративный перебор словаря по ключам и параметрам. Для меню заглавий меню создаем словарь:

dict set menu-bar-items 0 name file dict set menu-bar-items 0 text_item "File" dict set menu-bar-items 1 name help dict set menu-bar-items 1 text_item "Help" 

Теперь создадим словарь, в котором будет храниться меня первого уровня (параметр parent будет обозначать id заголовка меню к которому привязан первый уровень):

dict set menu-bar-items_first-level 0 parent 0 dict set menu-bar-items_first-level 0 label "Open" dict set menu-bar-items_first-level 0 command NONE dict set menu-bar-items_first-level 1 parent 0 dict set menu-bar-items_first-level 1 label "Close" dict set menu-bar-items_first-level 1 command NONE dict set menu-bar-items_first-level 2 parent 0 dict set menu-bar-items_first-level 2 label "Exit" dict set menu-bar-items_first-level 2 command { exit; } dict set menu-bar-items_first-level 3 parent 1 dict set menu-bar-items_first-level 3 label "About" dict set menu-bar-items_first-level 3 command NONE dict set menu-bar-items_first-level 4 parent 1  dict set menu-bar-items_first-level 4 label "Help" dict set menu-bar-items_first-level 4 command NONE 

Стандартный синтаксис меню разбирать не буду, его можно посмотреть в любом туториале. Лучше перейдем сразу к алгоритму «рисования» меню:

# Создаем процедуру, которая будет "собирать" меню из словарей proc assemble-my-menu {} { # Так, как функция оперирует только локальными значениями переменных, то нам необходимо ввести в ее область видимости все нужные нам словари     global user-settings     global menu-settings     global menu-bar-items     global menu-bar-items_first-level # Проверяем, не выключил ли пользователь вывод верхнего меню     if {[dict get $user-settings 0 "menuBar"]==true} { # , если да, то: # создаем собственно саму строку верхнего меню (обратите внимание, путь до виджета так же является "динамическим" и формируется из элемента словаря         menu .[dict get $menu-settings 0 "name"] # применяем настройки и отрисовываем строку на верхней части окна         . config -menu .[dict get $menu-settings 0 "name"] # Собственно начинается перебор. Берем словарь с заголовками меню и перебираем его         dict for {id info} $menu-bar-items { # info - вобрал в себя ввесь массив ключей и значений. Так, как с id-шником нам собственно пока делать нечего особо, по средствам ключевого слова with формируем из данных следующий код:             dict with info { # Создаем непосредственно меню нулевого уровня по ключу name словаря menu-bar-items и формирую путь до виджета из menu-settings:                 menu .[dict get $menu-settings 0 "name"].$name # Собственно добавляем заголовок                 .[dict get $menuBarSettings 0 "name"] add cascade -label $text_item -menu .[dict get $menuBarSettings 0 "name"].$name # Врубаем новый итеративный цикл на подобии указанного выше, но целевым будет словарь menu-bar-items_first-level                 dict for {iter data} $menu-bar-items_firs-level {                     dict with data { # Проверяем какому родительскому элементу принадлежит пункт, если истина - прорисовываем:                         if {$id==$parent} { # А вот тут - формируем меню второго уровня                             .[dict get $menu-settings 0 "name"].$name add command -label $label -command $command                         }                     }                 }             }         }      } } 

Полный код нашей программы:

dict set programSettings 0 title "myProgram" dict set programSettings 0 min-width 800 dict set programSettings 0 min-height 600 dict set programSettings 0 resizable-x 1 dict set programSettings 0 resizable-y 1  dict set menu-settings 0 name mainMenuBar dict set menu-settings 0 tearOff 1  dict set user-settings 0 menu-bar true   dict set menu-bar-items 0 name file dict set menu-bar-items 0 text_item "File" dict set menu-bar-items 1 name help dict set menu-bar-items 1 text_item "Help"  dict set menu-bar-items_first-level 0 parent 0 dict set menu-bar-items_first-level 0 label "Open" dict set menu-bar-items_first-level 0 command NONE dict set menu-bar-items_first-level 1 parent 0 dict set menu-bar-items_first-level 1 label "Close" dict set menu-bar-items_first-level 1 command NONE dict set menu-bar-items_first-level 2 parent 0 dict set menu-bar-items_first-level 2 label "Exit" dict set menu-bar-items_first-level 2 command { exit; } dict set menu-bar-items_first-level 3 parent 1 dict set menu-bar-items_first-level 3 label "About" dict set menu-bar-items_first-level 3 command NONE dict set menu-bar-items_first-level 4 parent 1  dict set menu-bar-items_first-level 4 label "Help" dict set menu-bar-items_first-level 4 command NONE  proc assemble-my-menu {} {     global user-settings     global menu-settings     global menu-bar-items     global menu-bar-items_first-level     if {[dict get $user-settings 0 "menuBar"]==true} {         menu .[dict get $menu-settings 0 "name"]         . config -menu .[dict get $menu-settings 0 "name"]         dict for {id info} $menu-bar-items {             dict with info {                 menu .[dict get $menu-settings 0 "name"].$name                 .[dict get $menuBarSettings 0 "name"] add cascade -label $text_item -menu .[dict get $menuBarSettings 0 "name"].$name                 dict for {iter data} $menu-bar-items_firs-level {                     dict with data {                         if {$id==$parent} {                             .[dict get $menu-settings 0 "name"].$name add command -label $label -command $command                         }                     }                 }             }         }      } }  wm title . [dict get $programSettings 0 "title"] wm resizable . [dict get $programSettings 0 "resizable-x"] [dict get $programSettings 0 "resizable-y"] wm minsize . [dict get $programSettings 0 "min-width"] [dict get $programSettings 0 "min-height"] option add *tearOff [dict get $menu-settings 0 "tearOff"] assemble-my-menu 

Надеюсь, урок/статья оказалась полезной. Приятного погружения в наркоманский под tcl/tk!

ссылка на оригинал статьи http://habrahabr.ru/post/174243/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *