Different methods to access value of an entry field in Tk - tcl

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.

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

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.

TCL Entry box read to variable

I have a single entry box called 'entrySerial' where it will have the user input the serial of the device. This serial should be placed into a variable so it can then be sent into a different procedure. How do I take what the user has entered and place it into the variable? This will occur only after the user presses a button -- I don't want the input being read as the user types it.
The entry widget has a -textvariable option which takes the name of a global variable which will hold the text value of the widget. This is live so it always holds the current value as the user types however you can use the -validatecommand option to run a command on certain conditions as described for the -validate option. One of these is a key press event (where you could check for Enter) and another useful one is to do the validation on loss of focus. eg:
set value "test"
entry .e -textvariable ::value -validate focusout -validatecommand {puts $::value; return 1}
pack .e -side left
pack [button .b -text Ok] -side right
That should print the entry value when you loose focus either by clicking another control or application or by using Tab to switch to another control.

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.