26 March 2013

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

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

Немного теории — словарь в tcl это такой тип данных, которые позволяет хранить сложные упорядоченные значения по типу «ключ-значение», причем с возможностью ветвить само дерево. Иными словами — на один ключ (к примеру ID записи) может приходиться не одно значение, а еще несколько ключей со значениями. Выглядит в синтаксисе tcl это примерно так:
dict set nonUserSettings 0 title "myProgram"
dict set nonUserSettings 0 minWidth 800
dict set nonUserSettings 0 minHeight 600
dict set nonUserSettings 0 resizableX 1
dict set nonUserSettings 0 resizableY 1

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

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

Давайте «нарисуем» окно Tk с помощью нашего словаря. Обычный код, выглядел бы таким образом:
wm title . "myProgram"
wm resizable . 1 1
wm minsize . 800 600

Наш код будет иметь следующий вид:
wm title . [dict get $nonUserSettings 0 "title"]
wm resizable . [dict get $nonUserSettings 0 "resizableX"] [dict get $nonUserSettings 0 "resizableY"]
wm minsize . [dict get $nonUserSettings 0 "minWidth"] [dict get $nonUserSettings 0 "minHeight"]

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

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

Данные о типе выпадающего меню из меню-бара добавим сразу:
option add *tearOff [dict get $menu-settings 0 "tearOff"]

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

А вот с отрисовкой давайте немного повременим. Описывать весь GUI вручную не имеет особого смысла, ведь у нас в арсенале очень мощный инструмент — итеративный перебор словаря по ключам и параметрам. Для меню заглавий меню создаем словарь:
dict set menuBarItems_ZeroLevel 0 name connectionItem
dict set menuBarItems_ZeroLevel 0 text_item "Connection"
dict set menuBarItems_ZeroLevel 1 name helpItem
dict set menuBarItems_ZeroLevel 1 text_item "Help"

Теперь создадим словарь, в котором будет храниться меня первого уровня (параметр parent будет обозначать id заголовка меню к которому привязан первый уровень):
dict set menuBarItems_FirstLevel 0 parent 0
dict set menuBarItems_FirstLevel 0 label "Open"
dict set menuBarItems_FirstLevel 0 command NONE
dict set menuBarItems_FirstLevel 1 parent 0
dict set menuBarItems_FirstLevel 1 label "Editt"
dict set menuBarItems_FirstLevel 1 command NONE
dict set menuBarItems_FirstLevel 2 parent 0
dict set menuBarItems_FirstLevel 2 label "Exit"
dict set menuBarItems_FirstLevel 2 command { exit; }
dict set menuBarItems_FirstLevel 3 parent 1
dict set menuBarItems_FirstLevel 3 label "About"
dict set menuBarItems_FirstLevel 3 command NONE
dict set menuBarItems_FirstLevel 4 parent 1 
dict set menuBarItems_FirstLevel 4 label "Help"
dict set menuBarItems_FirstLevel 4 command NONE

Стандартный синтаксис меню разбирать не буду, его можно посмотреть в любом туториале. Лучше перейдем сразу к алгоритму «рисования» меню:
# Создаем процедуру, которая будет "собирать" меню из словарей
proc assembleMyMenu {} {
# Так, как функция оперирует только локальными значениями переменных, то нам необходимо ввести в ее область видимости все нужные нам словари
    global userSettings
    global menuBarSettings
    global menuBarItems_ZeroLevel
    global menuBarItems_FirstLevel
# Проверяем, не выключил ли пользователь вывод верхнего меню
    if {[dict get $userSettings 0 "menuBar"]==true} {
# , если да, то:
# создаем собственно саму строку верхнего меню (обратите внимание, путь до виджета так же является "динамическим" и формируется из элемента словаря
        menu .[dict get $menuBarSettings 0 "name"]
# применяем настройки и отрисовываем строку на верхней части окна
        . config -menu .[dict get $menuBarSettings 0 "name"]
# Собственно начинается перебор. Берем словарь с заголовками меню и перебираем его
        dict for {id info} $menuBarItems_ZeroLevel {
# info - вобрал в себя ввесь массив ключей и значений. Так, как с id-шником нам собственно пока делать нечего особо, по средствам ключевого слова with формируем из данных следующий код:
            dict with info {
# Создаем непосредственно меню нулевого уровня по ключу name словаря menu-bar-items и формирую путь до виджета из menu-settings:
             menu .[dict get $menuBarSettings 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} $menuBarItems_FirstLevel {
                    dict with data {
# Проверяем какому родительскому элементу принадлежит пункт, если истина - прорисовываем:
                        if {$id==$parent} {
# А вот тут - формируем меню второго уровня
                            .[dict get $menuBarSettings 0 "name"].$name add command -label $label -command $command
                        }
                    }
                }
            }
        } 
    }
}

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


#Require main packages for work
package require Tk
# Data for non user settings
dict set nonUserSettings 0 title "myPrograms"
dict set nonUserSettings 0 minWidth 800
dict set nonUserSettings 0 minHeight 600
dict set nonUserSettings 0 resizableX 1
dict set nonUserSettings 0 resizableY 1
# Data for user settings
dict set userSettings 0 menuBar true 
# Data for biuld menubar
dict set menuBarSettings 0 name mainMenuBar
dict set menuBarSettings 0 tearOff 1
# Data for 0-level menu
dict set menuBarItems_ZeroLevel 0 name connectionItem
dict set menuBarItems_ZeroLevel 0 text_item "Connection"
dict set menuBarItems_ZeroLevel 1 name helpItem
dict set menuBarItems_ZeroLevel 1 text_item "Help"
# Data for 1-level menu
dict set menuBarItems_FirstLevel 0 parent 0
dict set menuBarItems_FirstLevel 0 label "LogIn"
dict set menuBarItems_FirstLevel 0 command NONE
dict set menuBarItems_FirstLevel 1 parent 0
dict set menuBarItems_FirstLevel 1 label "LogOut"
dict set menuBarItems_FirstLevel 1 command NONE
dict set menuBarItems_FirstLevel 2 parent 0
dict set menuBarItems_FirstLevel 2 label "Exit"
dict set menuBarItems_FirstLevel 2 command { exit; }
dict set menuBarItems_FirstLevel 3 parent 1
dict set menuBarItems_FirstLevel 3 label "About"
dict set menuBarItems_FirstLevel 3 command NONE
dict set menuBarItems_FirstLevel 4 parent 1 
dict set menuBarItems_FirstLevel 4 label "Help"
dict set menuBarItems_FirstLevel 4 command NONE
# Procedure for builing menu bar
proc assembleMyMenu {} {
    global userSettings
    global menuBarSettings
    global menuBarItems_ZeroLevel
    global menuBarItems_FirstLevel
    if {[dict get $userSettings 0 "menuBar"]==true} {
        menu .[dict get $menuBarSettings 0 "name"]
        . config -menu .[dict get $menuBarSettings 0 "name"]
        dict for {id info} $menuBarItems_ZeroLevel {
            dict with info {
                menu .[dict get $menuBarSettings 0 "name"].$name
                .[dict get $menuBarSettings 0 "name"] add cascade -label $text_item -menu .[dict get $menuBarSettings 0 "name"].$name
                dict for {iter data} $menuBarItems_FirstLevel {
                    dict with data {
                        if {$id==$parent} {
                            .[dict get $menuBarSettings 0 "name"].$name add command -label $label -command $command
                        }
                    }
                }
            }
        } 
    }
}
# Main programm logic

# Window Manager main settings
wm title . [dict get $nonUserSettings 0 "title"]
wm resizable . [dict get $nonUserSettings 0 "resizableX"] [dict get $nonUserSettings 0 "resizableY"]
wm minsize . [dict get $nonUserSettings 0 "minWidth"] [dict get $nonUserSettings 0 "minHeight"]
option add *tearOff [dict get $menuBarSettings 0 "tearOff"]
assembleMyMenu


Надеюсь, урок/статья оказалась полезной. Приятного погружения в наркоманский код tcl/tk!
Tags:tcltkизвращениясловариэзотерика
Hubs: Abnormal programming
+6
4.8k 12
Comments 10
Top of the last 24 hours