What is stored in this Tcl variable?
As recommended in this question , I wrote a little gui to take the options for the C command line program and pass them to the specified C program, which is already configured to handle them. It displays the way I wanted it to.
However, I would like to check if the values ββstored in the variables are correct. Getting the values ββto print gives me a lot of grief (I can't test in vivo directly due to some hardware issue). What am I missing?
- Converting a variable name to "$" gives me "$ variableName", not the value of the variable.
- Adding these variables to the array and calling is
array get arr
supposed to print the index and value of the array; I am getting variable names. - I tried the pathName parameter
cget
, but apparently-value
it is not an option, and leaving the option away does not give me a list of valid parameters.
Here's all the code with various things that didn't work (from option # 1, which is the easiest way, and the others are just my attempts at workarounds). They all produce line-by-line errors: "cannot read" :: ": no such variable" or "" cannot read "colorimetric": no such variable ".
#!/opt/ActiveTcl-8.5/bin/wish8.5
wm title . "Gretag"
ttk::frame .f -borderwidth 5 -relief sunken -padding "5 10"
# next line part of the "puts" tests at the bottom
global colorimetric
ttk::label .f.dataLabel -text "Data Type"
ttk::label .f.colorimetricLabel -text "Colorimetric"
ttk::checkbutton .f.colorimetric -onvalue "-c" -offvalue "" -command getFilename1
ttk::label .f.spectralLabel -text "Spectral"
ttk::checkbutton .f.spectral -onvalue "-s" -offvalue "" -command getFilename2
ttk::label .f.gretagNumLabel -text "Gretag #"
ttk::label .f.gretagLabel0 -text "1"
ttk::radiobutton .f.gretagRadio0 -variable gretagNum -value "/dev/ttyS0"
ttk::label .f.gretagLabel1 -text "2"
ttk::radiobutton .f.gretagRadio1 -variable gretagNum -value "/dev/ttyS1"
ttk::label .f.gretagLabel2 -text "3"
ttk::radiobutton .f.gretagRadio2 -variable gretagNum -value "/dev/ttyS2"
ttk::label .f.gretagLabel3 -text "4"
ttk::radiobutton .f.gretagRadio3 -variable gretagNum -value "/dev/ttyS3"
ttk::label .f.gretagLabel4 -text "5"
ttk::radiobutton .f.gretagRadio4 -variable gretagNum -value "/dev/ttyS4"
ttk::label .f.sampleSize -text "Sample Size"
ttk::label .f.samplex -text "X"
ttk::label .f.sampley -text "Y"
ttk::entry .f.x -textvariable x -width 5
ttk::entry .f.y -textvariable y -width 5
ttk::label .f.filterLabel -text "Filter Type"
ttk::label .f.filterLabel0 -text "D50"
ttk::radiobutton .f.filterRadio0 -variable filter -value "-d50"
ttk::label .f.filterLabel1 -text "D65"
ttk::radiobutton .f.filterRadio1 -variable filter -value "-d65"
ttk::label .f.filterLabel2 -text "Unfiltered"
ttk::radiobutton .f.filterRadio2 -variable filter -value "-U"
ttk::label .f.filterLabel3 -text "Polarized"
ttk::radiobutton .f.filterRadio3 -variable filter -value "-p"
ttk::label .f.baudLabel -text "Baud Rate"
ttk::label .f.baudLabel0 -text "4800"
ttk::radiobutton .f.baudRadio0 -variable baud -value "B4800"
ttk::label .f.baudLabel1 -text "9600"
ttk::radiobutton .f.baudRadio1 -variable baud -value "B9600"
ttk::label .f.baudLabel2 -text "19200"
ttk::radiobutton .f.baudRadio2 -variable baud -value "B19200"
ttk::label .f.baudLabel3 -text "38400"
ttk::radiobutton .f.baudRadio3 -variable baud -value "B38400"
ttk::label .f.baudLabel4 -text "57600"
ttk::radiobutton .f.baudRadio4 -variable baud -value "B57600"
ttk::button .f.submitBtn -text "Submit" -command finish
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
grid .f -column 0 -row 0 -columnspan 11 -rowspan 5
grid .f.dataLabel -column 0 -row 0 -sticky we
grid .f.colorimetricLabel -column 1 -row 0 -sticky e
grid .f.colorimetric -column 2 -row 0 -sticky w
grid .f.spectralLabel -column 3 -row 0 -sticky e
grid .f.spectral -column 4 -row 0 -sticky w
grid .f.gretagNumLabel -column 0 -row 1 -sticky we
grid .f.gretagLabel0 -column 1 -row 1 -sticky e
grid .f.gretagRadio0 -column 2 -row 1 -sticky w
grid .f.gretagLabel1 -column 3 -row 1 -sticky e
grid .f.gretagRadio1 -column 4 -row 1 -sticky w
grid .f.gretagLabel2 -column 5 -row 1 -sticky e
grid .f.gretagRadio2 -column 6 -row 1 -sticky w
grid .f.gretagLabel3 -column 7 -row 1 -sticky e
grid .f.gretagRadio3 -column 8 -row 1 -sticky w
grid .f.gretagLabel4 -column 9 -row 1 -sticky e
grid .f.gretagRadio4 -column 10 -row 1 -sticky w
grid .f.sampleSize -column 0 -row 2 -sticky we
grid .f.samplex -column 1 -row 2 -sticky e
grid .f.x -column 2 -row 2 -sticky w
grid .f.sampley -column 3 -row 2 -sticky e
grid .f.y -column 4 -row 2 -sticky w
grid .f.filterLabel -column 0 -row 3 -sticky we
grid .f.filterLabel0 -column 1 -row 3 -sticky e
grid .f.filterRadio0 -column 2 -row 3 -sticky w
grid .f.filterLabel1 -column 3 -row 3 -sticky e
grid .f.filterRadio1 -column 4 -row 3 -sticky w
grid .f.filterLabel2 -column 5 -row 3 -sticky e
grid .f.filterRadio2 -column 6 -row 3 -sticky w
grid .f.filterLabel3 -column 7 -row 3 -sticky e
grid .f.filterRadio3 -column 8 -row 3 -sticky w
grid .f.baudLabel -column 0 -row 4 -sticky we
grid .f.baudLabel0 -column 1 -row 4 -sticky e
grid .f.baudRadio0 -column 2 -row 4 -sticky w
grid .f.baudLabel1 -column 3 -row 4 -sticky e
grid .f.baudRadio1 -column 4 -row 4 -sticky w
grid .f.baudLabel2 -column 5 -row 4 -sticky e
grid .f.baudRadio2 -column 6 -row 4 -sticky w
grid .f.baudLabel3 -column 7 -row 4 -sticky e
grid .f.baudRadio3 -column 8 -row 4 -sticky w
grid .f.baudLabel4 -column 9 -row 4 -sticky e
grid .f.baudRadio4 -column 10 -row 4 -sticky w
grid .f.submitBtn -column 1 -row 5 -columnspan 7 -sticky we
foreach w [winfo children .f] {grid configure $w -padx 5 -pady 5}
focus .f.colorimetric
.f.colorimetric state selected
.f.filterRadio1 state selected
.f.baudRadio1 state selected
bind . <Return> {finish}
proc getFilename1 {} {
set filename1 [tk_getSaveFile]
}
proc getFilename2 {} {
set filename2 [tk_getSaveFile]
}
proc finish {} {
.f.x insert 0 "-x"
.f.y insert 0 "-y"
# Pick one
# puts $colorimetric
# puts colorimetric
# puts "$colorimetric"
# puts $::colorimetric
# puts .f.colorimetric
# puts $.f.colorimetric
# puts $::.f.colorimetric
# puts "$::colorimetric"
exec ./gretag .f.colorimetric filename1 .f.spectral filename2 .f.gretagNum .f.x .f.y .f.filter .f.baud
}
Edit: I have posted all the code, not just a part, and on the next line are the various syntaxes from option # 1 that I tried to see the values ββof the variables before they are passed to the next program, None of them work and I don't understand why or how to fix it. I hope another set of eyes will catch what's wrong.
source to share
Basics of variables
As others have pointed out, the confusing notation before $ or not $ can be simplified with the following rule.
var is a reference to the variable itself, not its value
$ var gives the value held in the variable
It can get a little more confusing when you start thinking of everything in Tcl as a string (it really isn't, but close enough), so you can store the name of one variable in another and restore its value by reference.
% set foo "hi"
hi
% set bar "foo"
foo
% set $foo
can't read "hi": no such variable
% set $bar
hi
Here, the notation set $foo
is evaluated in a step - first $foo
it gives its value hi
, and then the expression set
(when run without a third argument) tries to return the value held in the variable hi
. It fails. Notation set $bar
follows the same steps, but this time it set
can operate on a value bar
that is equal foo
, and thus returns a value foo
that is equal hi
. (link to API "set")
Initialization
One of the problems with this script is initialization. In Tcl variables does not exist until a value is assigned. It's clear why trying set $foo
above didn't work because there was no variable hi
.
At the top of your script, you are trying to declare a variable with
global colorimetric
which doesn't work because you are already working in the global field. The global "has no effect unless it is executed in the context of the process body." (link to "global" API) You really need to use the command set
to initialize the variable. That's why none of your print attempts colorimetric
to proc finish
not work.
Scale
Another problem you run into in this script is scoping, specifically when mixing global and procedural / local scopes. You are correct if you initialized correctly colorimetric
then the code
puts $::colorimetric ;# print the value of the global variable colorimetric
would work. Another way to achieve this is with
global colorimetric ;# reference a global variable into the local scope
puts $colorimetric ;# print the value of colorimetric in the local scope
My decision
I would like to present my solution. I admit that I have moved a lot of code and I will give a short explanation of what changes I have implemented to make it more concise.
#!/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)
}
Discussion of changes
1. The first changes I made were to use options -text
in ttk::checkbutton
and ttk::radiobutton
. Of course, with an extra label for them, you can put text in front of the button, but this is non-standard and requires more code.
ttk::label .f.colorimetricLabel -text "Colorimetric"
ttk::checkbutton .f.colorimetric -onvalue "-c" -offvalue "" -command getFilename1
becomes
ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1
2. Next, I used the similarities between the two buttons to abstract the creation in foreach. (I do this all the time in my Tcl code for the job.) This creates much easier code to read and allows you to add / remove / replace names and tags for widgets. This results in a slightly more, but much more stylish code.
ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1
ttk::checkbutton .f.colorimetric -text "Spectral" -onvalue "-s" -offvalue "" -command getFilename2
becomes
foreach {dtname dttag dtcommand} {
colorimetric "-c" getFilename1
spectral "-s" getFilename2
} {
ttk::checkbutton .f.$dtname -text [string totitle $dtname] -onvalue $dttag -offvalue "" -command $dtcommand
}
3. The next change was to combine yours getFilename1
and getFilename2
into one procedure getFilename
. We can pass arguments to this function to determine who is calling it. I am using a command list
to generate a call for this new function. (link to list API)
I also started to combine your commands grid
in the widget code itself. It mygrid
stores a list of what to bind to each line in the GUI and then evaluates at the end of each section for GUI distribution on the fly. (grid API link)
The previous code gets the final version and becomes,
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 ]
]
}
Then the command grid
can be evaluated and the variable mygrid
is cleared after each use!
4. If you noticed, I also added a parameter -variable
to yours, ttk::checkbutton
and at that moment started storing the state of the button in a global variable CONF
. This is a big change.
Tk loves to pollute your global namespace and it's good practice to fight back. I usually add all of my configuration state to a global array and set that at the top of the code so that everyone can go in and change the default state of my code without breaking into it or making find / replace calls or something. Just good practice, so wherever you had a variable, I moved it in CONF
and moved it CONF
up.
array set CONF {
colorimetric "-c"
spectral ""
cfilename "/path/to/defaultCI.txt"
sfilename ""
x 0
y 0
gretagnum "/dev/ttyS0"
filter "-d65"
baud "B9600"
}
5. I then propagated these changes throughout your code. Almost all widget creations have been susceptible to these changes. When it comes to creating a widget, this sometimes made the independent sections of code larger. But it also allowed me to remove the entire grid section by bundling that code into the widget code, as I said, significantly reducing the size and clutter of your code at the cost of additional complexity.
6. The final changes I made were related to your function code. You have a few minor bugs in your code getFilename1
and getFilename2
. The first error was what you want to call tk_getOpenFile
because I am only collecting a capture of the existing filename to pass to gretag
. (link to the 'tk_getOpenFile' API) If you use tk_getOpenFile
, the dialog will ensure that the file exists.
The second error getFilename1
is that the dialog has a button Cancel
, and if the user clicks the cancel button, the dialog returns an empty string. In this case, you need to uncheck the box ttk::checkbutton
and you need to disable the variable CONF(colorimetric)
. Or rather, you need to set CONF(colorimetric)
to a button -offvalue
. You can do this at the same time by sending a click to the current button.
proc getFilename1 {} {
set filename1 [tk_getSaveFile]
}
becomes
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) ""
}
}
In your function, finish
I renamed the function to submit
(sorry) and rewrote it to use the new variable CONF
.
Answer
Of course most of this is unnecessary. I wanted to give you some interesting Tcl / Tk code to think about and at the same time solve your problem. At this point, we should have a dictionary to answer your question.
The reason your variables weren't printed is due to initialization and scope. You should always use -variable
or -textvariable
in widgets that you will need to reference later. I usually initialize my reference variables before creating their containing widgets. So, at the top of your file, if you did,
set colorimetric "-c"
ttk::checkbutton .f.colorimetric -variable colorimetric [...]
Then you could do in the function finish
puts $::colorimetric
source to share
You have not specified any variable in the colorimetric button. Add -sizable colorimeter to control button and then in finish you can use: puts $ :: colorimetric
Also, first set :: colorimetric to select the default. Which is easier than trying to mess with the state of the widget.
I see that the colorimetric values ββcan have the values ββ"and" -c, so I am assuming that you will use that value in the exec line. Remember that [do something yada $ :: colorimetric something] will probably not work You probably need {*} $ :: colorimetric in exec to make the argument disappear if empty.
source to share
Here's a bit of tcl snippet - run from tclsh or wish
[nigel@rose ~]$ wish
% set foo /dev/ttys0
/dev/ttys0
% puts $foo
/dev/ttys0
% puts "$foo"
/dev/ttys0
% puts [$foo]
invalid command name "/dev/ttys0"
% puts ($foo)
(/dev/ttys0)
% puts {$foo}
$foo
%
Quoting in Tcl:
-
""
(double quotes): evaluate substitutions ($ variable) -
{}
{Escaping brackets): treat the entire string as a literal without substitution -
[]
(Square brackets): Execute string as a wildcard command
Alternatively, you can open diagnostics in a dialog:
% set mb [tk_messageBox -message "Port is $foo" -type ok -icon info]
ok
%
source to share
I'm not sure what you want to do, if you end up printing the variable name and not the contents of the variable, use the set command as a function:
set content "hello"
set blah value
if you do:
puts $blah
.. you get the content of the blah variable, which is the content.
To get the contents of a variable through blah use the following:
puts [set $blah]
source to share