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.
Related
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
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.
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.
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..)
I'm using tk widget Combobox and whenever I select any item in it it gives
invalid command name .top47.not48.fpage2.sw.sf.frame.cf2.frame.c.shell.listb
my code looks like this:-
ComboBox $mainframe.cf2.frame.c -textvariable variable1 \
-values Corners -modifycmd "new_values"
This is the main combobox that controls all the values of other combobox'es present in it which don't give any error like this.
This is very likely due to an error in the "new_values" function you have omitted. As a sample to show this:
package require BWidget
proc modify {} {.xyzzy something}
ComboBox .c -textvariable v -values Corners -modifycmd modify
pack .c
Now when you run this and select an entry from the dropdown you get 'Error: invalid command name ".xyzzy"'. The window you are trying to address is evidently digging into the internal implementation of this BWidgets class as winfo children .c shows me that .c.shell.listb exists. However, this is unsafe - the implementation may change from one version to the next and you don't control when the dropdown is created and destroyed. Check for the existence of your target window using winfo exists $combo.shell.listb as a minimum. You may want to ensure a compatible version of the BWidgets package too by using package require -exact BWidget 1.M.N