Tcl/Tk bind requires global variable? - tcl

I have a Tcl script controlling an automated tester. So far it was a console program that took user input at the command prompt. A colleague wrote a Tk GUI that can be launched by the script. I have never used Tk myself, so I don't understand a lot of the syntax.
At the beginning of the test the script must get a unit serial number from the operator. This is the function that my colleague wrote to do that:
proc GetSerialNumber {} {
global gUserInterfaceBarCode
DisplayMessage "Enter serial number:"
.c.serialnumberbox configure -state normal
focus .c.serialnumberbox
bind .c.serialnumberbox <Return> { set gUserInterfaceBarCode [.c.serialnumberbox get] }
tkwait variable gUserInterfaceBarCode
#grid forget .c.serialnumberbox
.c.serialnumberbox configure -state disabled
}
DisplayMessage is a procedure that simply updates a text label on the GUI.
I don't like the fact that there is a global variable gUserInterfaceBarCode that is used to hold the serial number. Is there any way to use a local variable instead and have the procedure return that value?
If I understand correctly, if the line tkwait variable gUserInterfaceBarCode is taken out this function will not block until that variable changes. Is this the best way to capture user input from a GUI element?

Fundamentally you need to have a variable to wait on. Unfortunately, the code for the <Return> is executed in a different context (the global context) than the code inside your proc, and there's no way for that code to address the local variables in your proc.
However, it doesn't have to a global variable, per se -- it just needs to be globally addressable, by which I mean you could use a namespace variable instead, if that makes you feel better about it:
namespace eval GetSerialNumber {
proc GetSerialNumber {} {
DisplayMessage "Enter serial number:"
.c.serialnumberbox configure -state normal
focus .c.serialnumberbox
bind .c.serialnumberbox <Return> { set ::GetSerialNumber::result [.c.serialnumberbox get] }
tkwait variable ::GetSerialNumber::result
.c.serialnumberbox configure -state disabled
return $::GetSerialNumber::result
}
}
set serialNum [GetSerialNumber::GetSerialNumber]
Another alternative would be to explicitly delete gUserInterfaceBarCode before returning:
tkwait variable ::gUserInterfaceBarCode
set result $::gUserInterfaceBarCode
unset ::gUserInterfaceBarCode
return $result
For what it's worth, the Tk core implementation uses the namespace approach for its own built-in dialog implementations, like the "Open File" dialog box.

Related

How to keep display up to date to monitor options' values of a tk widget

I'm developing a tk window to monitor the value of certain options of a widget.
I know I can monitor the latest value of a scalar variable (please allow me to use the term 'scalar' in perl) simply by specifying the variable name as the value of label's -textvariable switch. However, when it comes to monitoring widget options, if I use this form, say .button1 cget -bg to refer to the background option of a button, I don't know how to update the display of this option's value automatically.
label .label1 -textvariable ____ # <-- what should I put here?
or should I use another command?
To track when a widget option is changed, the easiest way is probably to rename the widget command and put your own proc in its place. That proc just forwards the calls to the original command and then updates the label as necessary:
button .button1
rename .button1 __.button1
proc .button1 {method args} {
set rc [__.button1 $method {*}$args]
if {$method eq "configure"} {
.label1 configure -text [__.button1 cget -bg]
}
return $rc
}
You may need to add some cleanup code to delete your .button1 proc when the widget is destroyed (bind to the event).
This can also be made prettier when errors occur. But this is the basic method.
When the -textvariable option is set to something other than the empty string, it contains the name of a global variable that the label will display instead of the value of the -text option. To update the displayed value, you just write to the global variable; Tk's built-in machinery handles the rest.
In theory, you could use a variable trace to emulate the -textvariable option using the -text option:
# Emulating: label .lbl -textvariable foo
label .lbl
trace add variable foo write {apply {{name1 name2 op} {
# We ignore name2 and op
upvar 1 $name1 var
.lbl configure -text $var
}}}
However, this is a pain to keep on typing; much more convenient to use the short form!
(Yes, Tk widgets use traces to implement things like -textvariable, but they're the C API version of traces so you can't see them from Tcl code. There's a lot of complexity beneath the surface.)
If you want to watch something that is part of a composite value (as opposed to a simple variable or array element) then the easiest method is to use a trace.
trace add variable foo write {apply {args {
global foo displayed_foo_element
set displayed_foo_element [lindex $foo 1]
}}}
label .lbl -textvariable displayed_foo_element
Like this, every time the container of the thing that you care about (foo in the above code) is updated, the trace ensures that the displayed_foo_element variable is updated to follow, and the label can just watch that.
You can use much more complex ways of preparing the value for displayed_foo_element if you want. Perhaps like this:
set displayed_foo_element "foo\[1\] = '[lindex $foo 1]'"
Also, instead of writing to an intermediate variable you can instead update the -text option on the widget directly. This option is particularly useful if you also want to adjust other features of the widget at the same time:
if {[lindex $foo 0] >= $CRITICAL_VALUE} {
set color red
} else {
set color black
}
.lbl configure -text "foo\[1\] = '[lindex $foo 1]'" -foreground $color

Triggering an error on unsetting a traced variable

I'm trying to create some read-only variables to use with code evaluated in a safe interp. Using trace, I can generate an error on attempts to set them, but not when using unset:
% set foo bar
bar
% trace add variable foo {unset write} {apply {{var _ op} { error "$var $op trace triggered" }}}
% set foo bar
can't set "foo": foo write trace triggered
% unset foo
%
Indeed, I eventually noticed the documentation even says in passing:
Any errors in unset traces are ignored.
Playing around with different return codes, including custom numbers, they all seem to be ignored. It doesn't trigger an interp bgerror handler either. Is there any other way to raise an error for an attempt to unset a particular variable?
There really isn't. The key problem is that there are times when Tcl is going to unset a variable when that variable really is going to be deleted because its containing structure (a namespace, stack frame or object, and ultimately an interpreter) is also being deleted. The variable is doomed at that point and user code cannot prevent it (except by the horrible approach of never returning from the trace, of course, which infinitely postpones the death and puts everything in a weird state; don't do that). There's simply nowhere to resurrect the variable to. Command deletion traces have the same issue; they too can be firing because their storage is vanishing. (TclOO destructors are a bit more protected against this; they try to not lose errors — there's even pitching them into interp bgerror as a last resort — but still can in some edge cases.)
What's more, there's currently nothing in the API to allow an error message to bubble out of the process of deleting a namespace or call frame. I think that would be fixable (it would require changing some public APIs) but for good reasons I think the deletion would still have to happen, especially for stack frames. Additionally, I'm not sure what should happen when you delete a namespace containing two unset-traced variables whose traces both report errors. What should the error be? I really don't know. (I know that the end result has to be that the namespace is still gone, FWIW, but the details matter and I have no idea what they should be.)
I'm trying to create some read-only variables to use with code evaluated
Schelte and Donal have already offered timely and in-depth feedback. So what comes is meant as a humble addition. Now that one knows that there variables traces are executed after the fact, the below is how I use to mimick read-only (or rather keep-re_setting-to-a-one-time-value) variables using traces (note: as Donal explains, this does not extend to proc-local variables).
The below implementation allows for the following:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
Inspired by variable, but only for one variable-value pair.
proc ::readOnly {var val} {
uplevel [list variable $var $val]
if {![string match "::*" $var]} {
set var [uplevel [list namespace which -variable $var]]
}
# only proceed iff namespace is not under deletion!
if {[namespace exists [namespace qualifiers $var]]} {
set readOnlyHandler {{var val _ _ op} {
if {[namespace exists [namespace qualifiers $var]]} {
if {$op eq "unset"} {
::readOnly $var $val
} else {
set $var $val
}
# optional: use stderr as err-signalling channel?
puts stderr [list $var is read-only]
}
}}
set handlerScript [list apply $readOnlyHandler $var $val]
set traces [trace info variable $var]
set varTrace [list {write unset} $handlerScript]
if {![llength $traces] || $varTrace ni $traces} {
trace add variable $var {*}$varTrace
}
}
}
Some notes:
This is meant to work only for global or otherwise namespaced variables, not for proc-local ones;
It wraps around variable;
[namespace exists ...]: These guards protect from operations when a given parent namespace is currently under deletion (namespace delete ::ns1, or child interp deletion);
In the unset case, the handler script re-adds the trace on the well re-created variable (otherwise, any subsequent write would not be caught anymore.);
[trace info variable ...]: Helps avoid adding redundant traces;
[namespace which -variable]: Makes sure to work on a fully-qualified variable name;
Some final remarks:
Ooo, maybe I can substitute the normal unset for a custom version and
do the checking in it instead of relying on trace
Certainly one option, but it does not give you coverage of the various (indirect) paths of unsetting a variable.
[...] in a safe interp.
You may want to interp alias between a variable in your safe interp to the above readOnly in the parent interp?

Different methods to access value of an entry field in Tk

I am using following simple code to change label text:
#! /usr/bin/wish8.6
label .a_lab -text "Enter text: "
entry .ent -textvariable tt
button .a_button -text "Change" -command changer
pack .a_lab -fill both -expand 1
pack .ent -fill both -expand 1
pack .a_button -fill both -expand 1
proc changer {} {
.a_lab config -text $::tt ;# How can I access 'tt' using pathname '.ent'?
}
wm geometry . 300x200+300+300
Are there any other methods to access the value of 'tt' apart from '$::tt'?
You want .ent get.
The configure and cget subcommands to a widget are used to access a widget's own traits. The text content in an entry widget isn't intrinsic and shouldn't be accessed that way, but widgets often have a specific subcommand for any reasonable task one would want it to perform.
Note also that you can set both the label and the entry to use the same content variable, which gives you instant and automatic updates.
ETA: updating the label with processed content from the entry
Some widgets signal changes through a virtual event (listbox generates a <<ListboxSelect>> event, for instance). The entry widget doesn’t. To setup update triggers for the entry widget, you can:
bind the <Return> event to the entry widget: bind .ent <Return> +mycallback. This lets the Enter key trigger the update. The + can be omitted as there is no standard action for this event.
bind the <Key> event to the Entry class*: bind Entry <Key> +mycallback: any key will trigger an update, including editing keys. Note that if the event is bound to the widget, it fires before the keystroke edits the content of the entry. If you bind it to Entry but omit the +, the callback will be run instead of the usual action to edit the entry.
add a watching trace to the variable: trace add variable tt write {apply {args mycallback}}, or
hijack the validation mechanism: .ent config -validate key -validatecommand {.a_lab config -text [string toupper %P];expr 1}
The mycallback callback can be either
proc mycallback {} {
.a_lab config -text [string toupper [.ent get]]
}
or
proc mycallback {} {
.a_lab config -text [string toupper $::tt]
}
If you set the parameter list of the callback to args, you don't need to wrap it in apply when tracing. If you use the validation mechanism, read the docs so you know how it works (you should always do that, but it's really easy to get it wrong in confusing ways in this case).
Documentation:
apply,
bind,
entry,
trace
*) i.e. X Window class, not OOP class.

Is it possible to create a GUI that return a value with Tcl?

I'm creating a small piece of GUI that is a must complete for the progression of the flow. What I want is to create a proc that creates a GUI and returns 1 or 0 when the GUI is closed and then the flow continues, like this:
first part of the code
...
...
if {![open_gui]} {
return
}
second part of the code
...
...
The GUI is simple 3 entries with a save and cancel buttons, if the save button is pressed, then some values should be stored to the data model and the function to return 1, if the cancel button is pressed, or the GUI is closed by closing the window then nothing should happen and the proc to return 0.
Is this possible?
Right now what I did is to break the code into two peaces, (code_part_1 and code_part_2) I run the first part, then open the GUI and the save button calls the second part, while the cancel just closes the GUI:
code_part_1
open_gui_split
And the function open_gui_split is:
proc open_gui_split {} {
# ...
set save_b [button $win.save_b -text save -command [list code_part_2]
# ...
}
* - All the code presented is only a representation of the architecture and not the real code.
It is entirely possible to create commands that run a Tk GUI, waiting for response from a user and returning that value. The key to doing that is the tkwait command:
proc popUpButton {w} {
toplevel $w
pack [button $w.b -text "push me" -command [list destroy $w]]
# This waits in the event loop until $w is destroyed...
tkwait window $w
return "button was pushed"
}
puts "about to pop up the button"
puts ">>[popUpButton]<<"
puts "popped up the button"
tkwait comes in three varieties:
tkwait window $w waits for the window $w to be destroyed.
tkwait visibility $w waits for the window $w to become visible (but doesn't work on platforms other than Unix/X11).
tkwait variable $varname waits for variable $varname to be set; it's just like plain Tcl vwait (and in fact vwait was originally tkwait variable before the integration of the event loop into Tcl).
Be aware that re-entering the event loop increases the stack depth and can cause your code to get very confused if you are not careful. You will probably want to use focus and grab to ensure that users only interact with the popped up dialog.
Finally, to see a more complete example of how this all works, look at the source to tk_dialog (that's exactly the version from Tk 8.4.19, direct from our repository) which is just plain old Tcl code and does the sort of thing you're after. It's a much more completely worked example than I want to write, showing off things like how to get a value to return that's based on user input.

Tk Get the window path currently in focus

How do i get the current window in focus using Tcl/Tk.
I tried using the focus command but it returns and empty string.
I have mutiple windows in the same wish session. Each window has
the same set of buttons but different data. I need to find out the
path to the window from which the button was pressed.
The focus command with no arguments returns the current Tk widget with the focus or an empty result if no Tk widget has focus. You can test this by starting Tk and packing some windows, then use after 2000 {puts [focus]} and click in a window within the 2 seconds.
However! What you want to achive sounds better done by binding the button command and passing itself to the command procedure:
pack [button .b -text Click -command [list Click .b]]
proc Click {widget args} {puts [list $widget $args [focus]]}
If you add an entry widget in there you will find the focus does not necessarily equal the button widget when you click it. That requires tabbing to the button first.
While looking for something else, I came across this, and while it's a bit old.. In Tcl, the there is a better way to do this. For
one thing, you cannot be sure the "active window" is the window you want when the script runs. The way to know which window/button was pressed is to embed it in the script code. When you attach code to Tk window/widget with the "-command" parameter, you can use something like "[list mycommand uniqueid]", and the Tcl interpreter will append any documented arguments before executing it. An excellent example of this is making a TCP socket server in Tcl. The command is "socket -server <procname> <port>", where the <procname> gets called whenever a client tries to connect to the <port>. But it does not have to be a simple procname, it can be any Tcl command that can be eval'd: "socket -server [list myserver foo bar] 12344" will create a listening socket on port 12345. When a client tries to connect to the server, Tcl will call (technically, it'll run it through "eval") "myserver foo bar <chan> <addr> <port>", where chan, addr and port are parameters added to the command by the socket command. Those are documented in the Tcl socket command pages.
The important part here is to understand that Tcl calls "myserver" with the arguments you gave it then appends the additional arguments to the end. You can then listen on another socket with "socket -server myserver foo baz 12346", which would listen on port 12346, and when a client connects, it will call/eval "myserver foo baz <chan> <addr> <port>". You can then write a single "myserver" proc as "proc myserver {argFoo argBar argChan argAddr argPort} {if {$argBar eq "bar"} {puts "do somthing"} else if {$argBar eq "baz"} {puts "do something else"} else {puts "Whatcha' talkin' about, foo'!?"}}".
For a Tk button example:
button .pressme -text "Press Me!" -command [list cmdYouPressed [pid]]
proc cmdYouPressed {argPID args} {
puts "You pressed the button for $argPID!"
}
If you run this snippet in multiple different Tcl/Tk interps, you can tell which one got pressed by the process ID (thats what the [pid] was for) to know which button got pressed in which running script. Of course, you can create your own unique identifiers instead of using [pid], as I did with the foo bar/baz socket example.
Doing it this way eliminates many problems that simply trying to get the active window has, such as "what happens if the window is NOT active when the command runs, such as if you have some kind of automation/remote control going and it's responding to a background window?" It also makes it so you do not have to try to determine the window/resource id when you have multiple version of the script running--each already has it's own system-unique process ID.
(Sorry if I'm not being clear or being too terse.. My keyboard is not working correctly, which is why I was searching for something else, to help me "autocorrect" the errors..)