Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<h2>Variable Basics</h2> <p>As others have pointed out, the confusing <strong>to $ or not to $</strong> notation can be simplified by the following rule.</p> <blockquote> <p><strong>var</strong> is a reference to the variable itself, not its value</p> <p><strong>$var</strong> yields the value held in the variable</p> </blockquote> <p>It can become a little more confusing when you start to think of everything in Tcl as a string (it's really not, but close enough), so you can store the name of one variable in another and restore its value by reference.</p> <pre><code>% set foo "hi" hi % set bar "foo" foo % set $foo can't read "hi": no such variable % set $bar hi </code></pre> <p>Here the notation <code>set $foo</code> is evaluated in step - first <code>$foo</code> yields its value <code>hi</code> and then the <code>set</code> expression (when run with no third argument) attempts to return the value held in the variable <code>hi</code>. This fails. The notation <code>set $bar</code> takes the same steps but this time <code>set</code> can operate on the value of <code>bar</code>, which is <code>foo</code>, and thus returns the value of <code>foo</code> which is <code>hi</code>. <a href="http://tcl.tk/man/tcl8.5/TclCmd/set.htm" rel="nofollow noreferrer">(link to "set" API)</a></p> <h2>Initialization</h2> <p>One problem you have in this script is initialization. In Tcl variables don't exist until they're assigned a value. That's clearly why trying to <code>set $foo</code> above didn't work, because there was no variable <code>hi</code>. </p> <p>At the top of your script you attempt to declare a variable with,</p> <pre><code>global colorimetric </code></pre> <p>which doesn't work, because you are already operating in global scope. Global "has no effect unless executed in the context of a proc body." <a href="http://tcl.tk/man/tcl8.5/TclCmd/global.htm" rel="nofollow noreferrer">(link to "global" API)</a> You actually have to use a <code>set</code> command to initialize the variable. This is why none of your attempts to print <code>colorimetric</code> in <code>proc finish</code> worked.</p> <h2>Scope</h2> <p>The other problem you have in this script is scope, particularly with mixing global and procedural/local scope. You're correct that, had you initialized <code>colorimetric</code> correctly then the code,</p> <pre><code>puts $::colorimetric ;# print the value of the global variable colorimetric </code></pre> <p>would have worked. Another way to achieve this is with,</p> <pre><code>global colorimetric ;# reference a global variable into the local scope puts $colorimetric ;# print the value of colorimetric in the local scope </code></pre> <h2>My Solution</h2> <p>I'd like to present my solution. I admit that I've moved a lot of code around, and I will go into a short explanation of what changes I implemented to make it more concise.</p> <pre><code>#!/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 &lt; 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 . &lt;Return&gt; 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) } </code></pre> <h2>Discussion of Changes</h2> <p><strong>1.</strong> The first changes I made were to use the <code>-text</code> options on the <code>ttk::checkbutton</code> and <code>ttk::radiobutton</code>. Granted, using an extra label for these allows you to place the text before the button, but doing so is non-standard and requires more code.</p> <pre><code>ttk::label .f.colorimetricLabel -text "Colorimetric" ttk::checkbutton .f.colorimetric -onvalue "-c" -offvalue "" -command getFilename1 </code></pre> <p>becomes</p> <pre><code>ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1 </code></pre> <hr> <p><strong>2.</strong> Next I used the similarities between these two checkbuttons to abstract the creation into a foreach. (I do this all the time in my Tcl code for work.) This generates much easier code to read and allows you to add/remove/swap names and tags for the widgets. It results in slightly more but much more versitile code.</p> <pre><code>ttk::checkbutton .f.colorimetric -text "Colorimetric" -onvalue "-c" -offvalue "" -command getFilename1 ttk::checkbutton .f.colorimetric -text "Spectral" -onvalue "-s" -offvalue "" -command getFilename2 </code></pre> <p>becomes</p> <pre><code>foreach {dtname dttag dtcommand} { colorimetric "-c" getFilename1 spectral "-s" getFilename2 } { ttk::checkbutton .f.$dtname -text [string totitle $dtname] -onvalue $dttag -offvalue "" -command $dtcommand } </code></pre> <hr> <p><strong>3.</strong> The next change was to merge your <code>getFilename1</code> and <code>getFilename2</code> into a single <code>getFilename</code> procedure. We can pass arguments into this function to determine who is calling it. I use the <code>list</code> command to generate the call for this new function. <a href="http://tcl.tk/man/tcl8.5/TclCmd/list.htm" rel="nofollow noreferrer">(link to "list" API)</a></p> <p>I also started to combine your <code>grid</code> commands into the widget code itself. Here <code>mygrid</code> keeps a list of what needs to be gridded per line in the GUI and then is evaluated at the end of each section to propagate the GUI on the fly. <a href="http://tcl.tk/man/tcl8.5/TkCmd/grid.htm" rel="nofollow noreferrer">(link to "grid" API)</a></p> <p>The previous code gets its final revision and becomes,</p> <pre><code>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 ] ] } </code></pre> <p>Then the <code>grid</code> command can be evaluated and the <code>mygrid</code> variable cleared after every use!</p> <hr> <p><strong>4.</strong> If you've been paying attention I also added a <code>-variable</code> option to your <code>ttk::checkbutton</code> and at this point started storing the button state in a global variable <code>CONF</code>. This is a big change.</p> <p>Tk loves to pollute your global namespace and it's good practice to fight back. I usually put all of my configuration state in a global array, and set that right up top of the code so that anyone can come in and change the default state of my code, without digging into it or doing search/replace calls or anything like that. Just good practice, so wherever you had a variable I moved it into <code>CONF</code> and moved <code>CONF</code> up top.</p> <pre><code>array set CONF { colorimetric "-c" spectral "" cfilename "/path/to/defaultCI.txt" sfilename "" x 0 y 0 gretagnum "/dev/ttyS0" filter "-d65" baud "B9600" } </code></pre> <hr> <p><strong>5.</strong> Next I propagated these changes throughout your code. Almost all of the widget creation was susceptible to these revisions. With respect to the widget creation this sometimes made independent code sections larger. But it also allowed me to remove your entire grid section, merging this code up into the widget code as I've discussed, greatly decreasing the size and clutter of your code at the added expense of complexity.</p> <hr> <p><strong>6.</strong> The final changes I made were to your function code. You have a couple of minor bugs in your <code>getFilename1</code> and <code>getFilename2</code> code. The first bug was that you want to call <code>tk_getOpenFile</code> because I gather you are only grabbing an existing file name to pass it to <code>gretag</code>. <a href="http://tcl.tk/man/tcl8.5/TkCmd/getOpenFile.htm" rel="nofollow noreferrer">(link to 'tk_getOpenFile' API)</a> If you use <code>tk_getOpenFile</code> the dialog will make sure the file exists.</p> <p>The second bug in <code>getFilename1</code> is that the dialog has a <code>Cancel</code> button, and if the user clicks this cancel button the dialog returns an empty string. In this case you have to un-check the <code>ttk::checkbutton</code> and you have to unset the <code>CONF(colorimetric)</code> variable. Or more correctly you have to set <code>CONF(colorimetric)</code> to the button's <code>-offvalue</code>. You can do both of these at once by sending a click event to the current button.</p> <pre><code>proc getFilename1 {} { set filename1 [tk_getSaveFile] } </code></pre> <p>becomes</p> <pre><code>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) "" } } </code></pre> <p>In your <code>finish</code> function I renamed the function to <code>submit</code> (sorry) and rewrote it to make use of the new <code>CONF</code> variable.</p> <h2>The Answer</h2> <p>Of course most of this was unnecessary. I wanted to give you some interesting Tcl/Tk code to think about while at the same time solving your problem. At this point we should have the vocabulary to answer your question, though.</p> <p>The reason your variables weren't printing out was because of initialization and scope. You should always use a <code>-variable</code> or <code>-textvariable</code> on widgets that you need to reference later. I generally initialize my referenced variables before building their containing widgets. So at the top of your file if you had done,</p> <pre><code>set colorimetric "-c" ttk::checkbutton .f.colorimetric -variable colorimetric [...] </code></pre> <p>Then you would have been able to do, in the <code>finish</code> function,</p> <pre><code>puts $::colorimetric </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload