I cannot fix a strange behavior of spinbox. Specifically, I need to update GUI at changing the spinbox's value, by means of -command and update in it.
The code a bit simplified is like:
package require Tk
set sv 1
ttk::spinbox .sp -from 1 -to 9 -textvariable ::sv \
-command {
after 50 ;# some processing imitated
puts [incr ::tmp]:$::sv ;# changes shown in CLI - ok
update ;# changes shown in GUI - ???
}
pack .sp
The problem is when the spinbox's arrow (more "Up" than "Down", but I've not found any regularity in this) is clicked and then pressed 10-20 seconds, the spinbox goes in the infinite cycle of updating, as puts shows.
Of course, the reason is update in the -command code, but I cannot do without it.
Tried in Windows (Tk 8.6.8) and Linux (Tk 8.6.10), ttk::spinbox and spinbox, all revealing the freak.
Is any way to overcome this? Thanks a lot for any help.
In general, don't update the spinbox variable from within the -command callback, and in particular don't run update from within the -command callback. You probably shouldn't do that at all. That command allows processing of events (it runs a subsidiary event loop until the event queue is drained) and is exactly the source of your problems. (I also would suggest not doing update idletasks; that will trigger the reconfigure and redraw that is at the heart of the issue.)
Instead, just stop running the command callback. That returns control to the Tk widget, which will in turn return to the main event loop. You are also advised to not do substantial processing in the callback, and instead to schedule such processing to occur later. Exactly how you do this can be complex, and will definitely be application-specific. One way to move processing later is to just punt it to a procedure that runs in an after event, like this:
package require Tk
set sv 1
proc updateVar {varName} {
upvar "#0" $varName var
after 50; # Processing...
incr var; # Actually update the variable
}
ttk::spinbox .sp -from 1 -to 9 -textvariable ::sv \
-command {after 0 updateVar ::sv}
pack .sp
Note that this does not call update. More substantial postponements of code might involve threads or subprocesses. As I said, getting this right can be complex. It's particularly so when changes to the GUI layout while a mouse button is down cause the selected value to change which in turn causes changes to the GUI layout which …
I made this archive with video to demonstrate the strange behavior of spinbox when update is included in its -command option.
There are two tests in the archive:
test1.tcl presents a way how it should not be done. There are two issues:
the -command code isn't moved to a separate procedure
update is fired immediately from the -command code
The result is seen in test1-spx.mp4: when pressed a spinbox arrow 10-20 seconds, the spinbox goes into an infinite cycle of updating. This behavior is not regular, though well revealed when the focus is switched to another application.
test2.tcl presents a way how this freak can be overcome. The after idle is used to postpone the updating. You can use also after 0 for this.
In a "real test" test2_pave.tcl I use the following procedure for the -command:
proc fontszCheck {} {
lappend ::afters [after 0 {
foreach a $::afters {after cancel $a}
set ::afters [list]
::t::toolBut 4 -3
}]
}
Hopefully, this information would be useful at dealing with Tk spinbox.
Related
proc foo {} {
# do something
# sleep for 5sec
# do something
# sleep for 5sec
# do something
}
I want as soon as we enter foo, progressbar should appear and running horizontally,
along with that foo code should also be running,
and as everything in foo finishes, progressbar should show 100% horizontally and disappear .
How to do that?
The widget you're looking for is a ttk::progressbar (Tcl) or tkinter.ttk.Progressbar (Python). You'll need to decide whether to use it in determinate or indeterminate mode and stuff like that.
There's one key tricky thing about using a progress bar: you need to keep the event loop running while you're displaying it. This means you need to either put the work in a separate thread or call update periodically. Both options have their tricky aspects:
When using a separate worker thread, that thread must not touch the GUI at all. Instead, it has to send messages to the GUI thread asking it to do the update.
When using periodic update calls (once every quarter second or so is usually enough) you need to be very careful to not recursively enter full event loop processing (see the TkDocs page on the event loop for a discussion). This often involves doing things like disabling buttons while the processing is going on.
Were you instead after a progress bar in a terminal, those are comparatively simple because you don't have to manage an event loop; the terminal itself will do that for you.
If you have a good idea of the time it takes to do what you have to do you can use the after command to update the progress bar at regular intervals.
# pop up progress bar for a calibration that takes 20 seconds
toplevel .progress
label .progress.calib -text "Calibrating ..."
ttk::progressbar .progress.bar \
-variable ::progress \
-length 300
grid .progress.calib
grid .progress.bar
wm geometry .progress +180+180
#
# wait 20 seconds and update the progress every 0.5 s
#
set wait_time 20000
# use a variable to wait until the progressbar is complete
after 20100 "destroy .progress"
# create updates every 0.5 second
for {set i 500} {$i <= $wait_time} {incr i 500} {
after $i "set ::progress [expr $i*100/$wait_time]"
}
# do your stuff in less than 20 seconds
...
i have the following code ...
lassign [ chan pipe ] chan chanW
fileevent $chan readable [ list echo $chan ]
proc echo { chan } {
...
}
proc exec { var1 var2 } {
....
puts $chanW "Some output"
....
}
Now according to man fileevent will be executed when the programs idles
is it possible to forse fileevent to be executed before that. For instance is it possible to force the fileevent to be executed immediately after the channel becomes readable, to somehow give it priority .... without using threads :)
Tcl never executes an event handler at “unexpected” points; it only runs them at points where it is asked to do so explicitly or, in some configurations (such as inside wish) when it is doing nothing else. You can introduce an explicit wait for events via two commands:
update
vwait
The update command clears down the current event queue, but does not wait for incoming events (strictly, it does an OS-level wait of length zero). The vwait command will also allow true waiting to happen, waiting until a named Tcl global variable has been written to. (It uses a C-level variable trace to do this, BTW.) Doing either of these will let your code process events before returning. Note that there are a number of other wrappers around this functionality; the geturl command in the http package (in “synchronous” mode) and the tkwait command in the Tk package both do this.
The complication? It's very easy to make your code reenter itself by accident while running the event loop. This can easily end up with you making lots of nested event loop calls, running you out of stack space; don't do that. Instead, prepare for reentrancy issues (a global variable check is on of the easiest approaches to do that) so that you don't nest event loops.
Alternatively, if you're using Tcl 8.6 you can switch your code around to use coroutines. They let you stop the current procedure evaluation and return to the main event loop to wait for a future event before starting execution again: you end up with code that returns at the expected time, but which was suspended for a while first. If you want more information about this approach, please ask another separate question here.
Can a button in tcl could be linked to multiple command line arguments ?
I have a code which runs when a button is clicked, a progressbar code with time in seconds is also linked with it , and should start at same time when this button is pressed.
I put both procs as a command in button command argument using {} but it fails with Error.
Code Snippet
button .b -image $p -command {progressbar 300 run_structural_comparision}
proc progressbar {seconds} {
ttk::progressbar .pg -orient horizontal -mode determinate -maximum $seconds
pack .pg -side left
update idletasks
# Do some real work here for $seconds seconds
for {set i 0} {$i < $seconds} {incr i} {
after 1000; # Just waiting in this example, might as well do something useful here
.pg step; # After some part of the work, advance the progressbar
update idletasks; # Needed to update the progressbar
}
# Done, clean up the dialog and progressbar
}
proc run_structural_comparision {} {
type_run
global ENTRYfilename ENTRYfilename2 curDIR curDIR2 typep reflib compLib rundir hvt_verilog logfile
set path [concat $reflib $compLib]
## set path [concat $ENTRYfilename $ENTRYfilename2]
puts $path
set str "compare_structure -overlap_when -type {timing constraint} -report compare_structure_"
set trt ".txt"
set structure [concat [string trim $str][string trim $typep][string trim $trt] $path]
puts $structure
puts $rundir
cd $rundir
set filename [concat "compare_structure_" $typep ".tcl"]
if {[ file exists $rundir/$filename] == 1 } {
exec rm -rf $rundir/compare_structure_$typep.tcl
}
A button's -command callback is a Tcl script. It will be evaluated at the global level of the stack. If you want to run two commands, you can just put a script in there to run the two commands:
button .b -command { command_1; command_2 }
This will run them sequentially. Tcl is naturally single-threaded as that is by far the easiest programming model for people to work with. It's possible to make code that works by doing callbacks to appear to be doing multiple things at once (that's how Tk works, just like virtually all other GUIs on the planet) but you're really only doing one thing at a time.
But your real question…
The core of what you need is a way to run the program that takes a long time in the background so that you can monitor it and continue to update the GUI. This is not a trivial topic, unfortunately, and the right answer will depend on exactly what is going on.
One of the simplest techniques is where the CPU-bound processing is done in a subprocess. In that case, you can run the subprocess via a pipeline and set fileevent to give you a notification callback when output is produced or the program terminates. Tcl is very good at this sort of thing; things that many languages have as very advanced techniques just feel natural when done with Tcl, as a great deal of thought has been put into how to make I/O callbacks work nicely.
If it's in-process and long-running without the opportunity for callbacks, things get more complex as you have to have the processing and the GUI updates in different threads. Which isn't too hard if you've got everything set up right, but which might require substantial re-architecting of your program (as it is usual for threads in Tcl to be extremely strongly partitioned from each other).
The simplest thing to do is to create a procedure that calls the two functions. If you wantie:
proc on_button_press {seconds} {
after idle [list progressbar $seconds]
after idle [list run_structural_comparision]
}
You can put multiple calls in the immediate button handler command string but it quickly gets complicated. But in short, use a semicolon to separate the two commands.
Your use if update idletasks should be considered a "code smell". ie: avoid it. In this case, in the progressbar function, setup the bar then just have everything else called by after calls to update the state of the progress.
I suspect your rm -rf may not do what you want. It it likely to lockup the interface as you get nothing back until the command has completed. Better is to write a function to walk the directory tree and delete the files with file delete and you can then raise progress events as you go and keep the UI alive by breaking up the processing into chunks using after again.
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..)