Переменная Основы
Как уже указывали другие, запутанная запись в $ или не в $ может быть упрощена следующим правилом.
var - это ссылка на саму переменную, а не на ее значение
$ var возвращает значение, содержащееся в переменной
Это может стать немного более запутанным, когда вы начинаете думать обо всем в Tcl как о строке (на самом деле это не так, но достаточно близко), так что вы можете сохранить имя одной переменной в другой и восстановить ее значение по ссылке.
% set foo "hi"
hi
% set bar "foo"
foo
% set $foo
can't read "hi": no such variable
% set $bar
hi
Здесь обозначение set $foo
вычисляется на шаге - сначала $foo
возвращает его значение hi
, а затем выражение set
(при запуске без третьего аргумента) пытается вернуть значение, содержащееся в переменной hi
. Это не удается. Обозначение set $bar
принимает те же шаги, но на этот раз set
может работать со значением bar
, которое составляет foo
, и, таким образом, возвращает значение foo
, которое составляет hi
. (ссылка на API "set")
инициализация
Одной из проблем, с которыми вы столкнулись в этом скрипте, является инициализация. В Tcl переменные не существуют, пока им не присвоено значение. Именно поэтому попытка set $foo
выше не сработала, потому что не было переменной hi
.
В верхней части вашего скрипта вы пытаетесь объявить переменную с помощью
global colorimetric
, который не работает, потому что вы уже работаете в глобальном масштабе. Глобальный «не имеет эффекта, если не выполняется в контексте тела процесса». (ссылка на «глобальный» API) Для инициализации переменной вам действительно нужно использовать команду set
. Вот почему ни одна из ваших попыток печати colorimetric
в proc finish
не сработала.
Область
Другая проблема, с которой вы сталкиваетесь в этом скрипте, - это область действия, особенно при смешивании глобальной и процедурной / локальной области. Вы правы, если бы вы правильно инициализировали colorimetric
, а затем код
puts $::colorimetric ;# print the value of the global variable colorimetric
сработало бы. Еще один способ добиться этого с помощью
global colorimetric ;# reference a global variable into the local scope
puts $colorimetric ;# print the value of colorimetric in the local scope
Мое решение
Я бы хотел представить свое решение. Я признаю, что я переместил много кода, и я кратко объясню, какие изменения я реализовал, чтобы сделать его более кратким.
#!/usr/bin/env wish
# --- default configuration --- #
array set CONF {
colorimetric "-c"
spectral ""
cfilename "/path/to/defaultCI.txt"
sfilename ""
x 0
y 0
gretagnum "/dev/ttyS0"
filter "-d65"
baud "B9600"
}
# --- build the interface --- #
wm title . "Gretag"
ttk::frame .f -borderwidth 5 -relief sunken -padding "5 10"
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
grid .f
ttk::label .f.dataLabel -text "Data Type: "
foreach {dtname dttag dtfile} {
colorimetric "-c" cfilename
spectral "-s" sfilename
} {
lappend mygrid [
ttk::checkbutton .f.$dtname -text [string totitle $dtname] \
-variable CONF($dtname) -onvalue $dttag -offvalue "" \
-command [list getFilename $dtname $dttag $dtfile ]
]
}
grid .f.dataLabel {*}$mygrid -sticky w ; set mygrid { }
ttk::label .f.gretagNumLabel -text "Gretag #: "
for {set tty 0} {$tty < 5} {incr tty} {
lappend mygrid [
ttk::radiobutton .f.gretagRadio$tty -text [expr $tty + 1] \
-variable CONF(gretagnum) -value "/dev/ttyS$tty"
]
}
grid .f.gretagNumLabel {*}$mygrid -sticky w ; set mygrid { }
ttk::label .f.sampleSize -text "Sample Size: "
ttk::label .f.samplex -text "X"
ttk::label .f.sampley -text "Y"
ttk::entry .f.x -textvariable CONF(x) -width 5
ttk::entry .f.y -textvariable CONF(x) -width 5
grid .f.sampleSize .f.samplex .f.x .f.sampley .f.y
ttk::label .f.filterLabel -text "Filter Type: "
foreach {ftname ftval} {
D50 "-d50"
D65 "-d65"
Unfiltered "-U"
Polarized "-P"
} {
lappend mygrid [
ttk::radiobutton .f.filterRadio$ftname -text $ftname \
-variable CONF(filter) -value $ftval
]
}
grid .f.filterLabel {*}$mygrid -sticky w ; set mygrid { }
ttk::label .f.baudLabel -text "Baud Rate: "
foreach {baud} {
4800 9600 19200 38400 57600
} {
lappend mygrid [
ttk::radiobutton .f.baudRadio$baud -text $baud \
-variable CONF(baud) -value "B$baud"
]
}
grid .f.baudLabel {*}$mygrid -sticky w ; set mygrid { }
ttk::button .f.submitBtn -text "Submit" -command submit
grid .f.submitBtn -columnspan 6 -sticky we
foreach w [winfo children .f] {
grid configure $w -padx 5 -pady 5
}
focus .f.colorimetric
bind . <Return> submit
# --- callbacks --- #
proc getFilename {type tag file} {
global CONF
if {$CONF($type) eq $tag} {
set CONF($file) [tk_getOpenFile]
if {$CONF($file) eq ""} { .f.$type invoke }
} else {
set CONF($file) ""
}
}
proc submit { } {
global CONF
exec ./gretag $CONF(colorimetric) $CONF(cfilename) \
$CONF(spectral) $CONF(sfilename) $CONF(gretagnum) \
$CONF(x) $CONF(y) $CONF(filter) $CONF(baud)
}
Обсуждение изменений
1. Первыми изменениями, которые я сделал, было использование параметров -text
на ttk::checkbutton
и ttk::radiobutton
. Конечно, использование дополнительной метки для них позволяет разместить текст перед кнопкой, но это нестандартно и требует больше кода.
ttk::label .f.colorimetricLabel -text "Colorimetric"
ttk::checkbutton .f.colorimetric -onvalue "-c" -offvalue "" -command getFilename1
становится
ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1
2. Затем я использовал сходства между этими двумя кнопками, чтобы абстрагировать творение в foreach. (Я делаю это все время в своем коде Tcl для работы.) Это создает гораздо более простой код для чтения и позволяет добавлять / удалять / менять имена и теги для виджетов. В результате получается немного больше, но гораздо более гибкий код.
ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1
ttk::checkbutton .f.colorimetric -text "Spectral" -onvalue "-s" -offvalue "" -command getFilename2
становится
foreach {dtname dttag dtcommand} {
colorimetric "-c" getFilename1
spectral "-s" getFilename2
} {
ttk::checkbutton .f.$dtname -text [string totitle $dtname] -onvalue $dttag -offvalue "" -command $dtcommand
}
3. Следующим изменением было объединение ваших getFilename1
и getFilename2
в одну getFilename
процедуру. Мы можем передать аргументы в эту функцию, чтобы определить, кто ее вызывает. Я использую команду list
для генерации вызова для этой новой функции. (ссылка на API "list")
Я также начал комбинировать ваши grid
команды в самом коде виджета. Здесь mygrid
хранит список того, что необходимо привязать к сетке для каждой строки в GUI, а затем оценивается в конце каждого раздела для распространения GUI на лету. (ссылка на API "grid")
Предыдущий код получает свою окончательную версию и становится,
foreach {dtname dttag dtfile} {
colorimetric "-c" cfilename
spectral "-s" sfilename
} {
lappend mygrid [
ttk::checkbutton .f.$dtname -text [string totitle $dtname] -variable CONF($dtname) -onvalue $dttag -offvalue "" -command [list getFilename $dtname $dttag $dtfile ]
]
}
Затем можно оценить команду grid
и очистить переменную mygrid
после каждого использования!
4. Если вы обращали внимание, я также добавил опцию -variable
к вашему ttk::checkbutton
и в этот момент начал сохранять состояние кнопки в глобальной переменной CONF
. Это большое изменение.
Tk любит загрязнять ваше глобальное пространство имен, и это хорошая практика, чтобы дать отпор. Я обычно помещаю все свое состояние конфигурации в глобальный массив и устанавливаю это в верхней части кода, чтобы любой мог войти и изменить состояние моего кода по умолчанию, не углубляясь в него и не делая вызовы поиска / замены или что-то подобное тот. Просто хорошая практика, поэтому, где бы у вас ни была переменная, я переместил ее в CONF
и переместил CONF
вверх.
array set CONF {
colorimetric "-c"
spectral ""
cfilename "/path/to/defaultCI.txt"
sfilename ""
x 0
y 0
gretagnum "/dev/ttyS0"
filter "-d65"
baud "B9600"
}
5. Далее я распространил эти изменения по всему вашему коду. Почти все создание виджета было подвержено этим изменениям. Что касается создания виджетов, это иногда делало независимые разделы кода больше. Но это также позволило мне удалить весь ваш раздел сетки, объединяя этот код с кодом виджета, как я уже обсуждал, значительно уменьшая размер и беспорядок вашего кода за счет дополнительной сложности.
6. Последние изменения, которые я внес в ваш код функции. У вас есть пара незначительных ошибок в коде getFilename1
и getFilename2
. Первая ошибка заключалась в том, что вы хотите позвонить tk_getOpenFile
, потому что, насколько я понимаю, вы только захватываете существующее имя файла, чтобы передать его gretag
. (ссылка на API tk_getOpenFile) Если вы используете tk_getOpenFile
, диалоговое окно убедится, что файл существует.
Вторая ошибка в getFilename1
заключается в том, что в диалоговом окне есть кнопка Cancel
, и если пользователь нажимает эту кнопку отмены, диалоговое окно возвращает пустую строку. В этом случае вы должны отменить проверку ttk::checkbutton
и сбросить переменную CONF(colorimetric)
. Или, более правильно, вы должны установить CONF(colorimetric)
для кнопки -offvalue
. Вы можете сделать оба этих действия одновременно, отправив событие нажатия на текущую кнопку.
proc getFilename1 {} {
set filename1 [tk_getSaveFile]
}
становится
proc getFilename {type tag file} {
global CONF
if {$CONF($type) eq $tag} {
set CONF($file) [tk_getOpenFile]
if {$CONF($file) eq ""} { .f.$type invoke }
} else {
set CONF($file) ""
}
}
В вашей функции finish
я переименовал функцию в submit
(извините) и переписал ее, чтобы использовать новую переменную CONF
.
Ответ
Конечно, большая часть этого была ненужной. Я хотел дать вам некоторый интересный код Tcl / Tk, чтобы подумать и в то же время решить вашу проблему. На данный момент у нас должен быть словарный запас, чтобы ответить на ваш вопрос.
Причина, по которой ваши переменные не распечатывались, была из-за инициализации и области видимости. Вы всегда должны использовать -variable
или -textvariable
для виджетов, на которые вам нужно сослаться позже. Я обычно инициализирую свои переменные, на которые ссылаются, перед созданием содержащих их виджетов. Так что в верхней части вашего файла, если вы сделали,
set colorimetric "-c"
ttk::checkbutton .f.colorimetric -variable colorimetric [...]
Тогда вы могли бы сделать, в функции finish
,
puts $::colorimetric