Finding the type of a Tk widget - tcl

Is there a way to know what is the "type" of a Tk widget?
As an example, let's suppose I create a label, using the tk::label command or the ttk::label command.
After that, I just have the path name of the widget, for example .l. Now, I'd like to know what was the command used to build .l.
What I need is a programmatic solution, some code that given the .l path can return the name of the command, and I'm interested in Tcl solutions, or even Tcl extensions coded in C.
A way to do this could be renaming all the widget creation commands with procs that save that information somewhere and then does exactly what the original command would do, but this solution requires that you know all the available widget creation commands, and doesn't take into account user defined widget (or megawidgets as a whole).
Thank you very much for your help.

The winfo class command will tell you the class of a widget, which is usually good enough (see this interactive session):
% label .tklabel; puts [winfo class .tklabel]
Label
% ttk::label .ttklabel; puts [winfo class .ttklabel]
TLabel
Note that you can set the class of standard Tk toplevels and Ttk widgets at creation time. This can make things rather complex. With toplevels, you can see whether [winfo toplevel $w] eq $w is true, but that's not a guaranteed test from 8.5 onwards, as it is possible to reparent toplevels as children of another widget or turn classic frames into toplevels (via wm forget/wm manage).
If you have to get the exact command used to create a widget, the only truly sure method is to trap the creation command and log the info, like this:
rename frame the_real_frame
proc frame {pathName args} {
set ::creationInfo($pathName) [list frame $args]
the_real_frame $pathName {*}$args
}
It's usually easier to try to avoid such complexity (especially as in a production setting you'll also need to set things up to deal with <Destroy> events so that you clean up information about no-longer-extant widgets, and that just adds lots more trickiness).

Related

TK spinbox goes into infinite cycle of updating GUI

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.

Button with multiple command line arguments

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.

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..)

Combobox in tcl gives error when any item is selected in it

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