How to keep display up to date to monitor options' values of a tk widget - 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

Related

How to use loop index variable inside command that will be invoked later

Let's consider the following code:
package require Tk
proc test {} {
foreach n {
1 2 3 4 5 6 7 8 9
} {
pack [button ._$n -text $n -command {puts $n}]
}
}
test
When one of the buttons invoked, "n" is unknown.
I found a away to address this by changing {puts $n} to "puts $n", but not sure this is a correct approach.
The callback for the button command is executed in the global scope, and there's no n variable there.
If you add global n command into that proc, then the error message won't appear, but each button will print the same value of n.
As you intent is to associate the value of n for each button, you need to pick a different quoting mechanism: braces prevent variable expansion. See https://www.tcl-lang.org/man/tcl8.6/TclCmd/Tcl.htm -- Shawn's comment gave you the answer.
You have to bind the current value of $n to the callback at the time you set it; your code as it stands uses whatever happens to be in the global n variable at the time that the callback is invoked (i.e., it is either the same for all the buttons or an error).
The list command is designed to be perfect for doing that binding; it generates a list, yes, but it also guarantees to generate a substitution-free command that has the words that are its arguments. That is, the script/command call:
eval [list $a $b $c]
is guaranteed to be the same as:
$a $b $c
for any values at all. This is an exceptionally useful property for almost any kind of code generation since it lets you make a script — that can be a call to a procedure for anything complicated — and pass any values over, entirely safely. In your case, this means that you should change change:
pack [button ._$n -text $n -command {puts $n}]
to:
pack [button ._$n -text $n -command [list puts $n]]
Thanks for your answers!
I ended up with the following possible ways:
"puts $n" - good for simple cases, but a bit non-intuitive
[list puts $n] - good, especially for lists
[subst {puts $n}] - good, straightforward substitution

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.

TCL/TK How do I pass multiple arguments to a button call back function?

I have a code like this
proc press2 {v sbit} {
puts $v
puts $sbit
}
:
:
button .t.ok2 -text "OKI" -command "press2 $v $sbit"
with this I get the error wrong # args: should be "press2 v sbit"
if I change it to button .t.ok2 -text "OKI" -command {press2 $v $sbit} I get can't read "v": no such variable and finally I tried button .t.ok2 -text "OKI" -command [press2 $v $sbit] which doesn't give any errors but doesn't work also. Just asking is there any good documentation available for TCL/TK ? The usual man pages and googling isnt helping me much. I am doing much by trial and error.
Passing multiple arguments to a procedure is easy, but the "correct" way depends on what you want:
Early Binding: If you want to pass the current values later (e.g. if you create the widgets in a loop) you need to use list:
button .t.ok2 -text OKI -command [list press2 $v $sbit]
list creates a command that is free from any further substitution*.
Late Binding: If you want pass the value when this command is executed, simply brace it with {}
button .t.ok2 -text OKI -command {press2 $v $sbit}
The variables v and sbit are subsituted when the button is pressed. You only have access to global variables (or variables in a namespace, but not local variables).
* Tk's bind replaces % and a following character with something special. This is done using string substitution, not Tcl substitution, so list does not guard against this.

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.

Tcl/Tk bind requires global variable?

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.