A button is a widget that is typically used by a GUI programmer to receive a simple command from the user - a "press here to submit" type idea is typically represented by a button.
Newcomeers to Tk often experience several problems whose symptom is that "the button doesn't work right". The error usually arises from exactly when a variable like $i is evaluated. Recall that variables are substituted in strings quoted with "", but not in strings quoted with {}. So this code, which creates five buttons:
for {set i 0} {$i<5} {incr i} { grid [button .b$i -text "Button $i" -command "runMyProc $i"] }
has all the $i's substituted when the buttons are created. So the first button is named .b0, has the text "Button 0", and executes the command runMyProc 0. But if you put the command script in braces, like this:
for {set i 0} {$i<5} {incr i} { grid [button .b$i -text "Button $i" -command {runMyProc $i}] }
then all five buttons get the identical command script, runMyProc $i. This time, the $i gets evaluated much later, when the user clicks the button. The command script runs in the global scope, and there may or may not even be a variable named "i".
For more details, see the Frequently-Made Mistakes in Tcl
See also: Variable substitution in -command scripts
RS: I'm not sure whether that's the right term, but I mean buttons that normally have a flat relief, and only pop up lightly when the mouse is over them. I like this recent MS fancy, and it's easy to have in Tk, if you just globally declare
option add *Button.borderWidth 1 option add *Button.relief flat bind Button <Enter> {+ %W config -relief raised} bind Button <Leave> {+ %W config -relief flat}
Then all buttons of your app (even the Tk error dialog ;-) will have pop-up behavior.
MG: It's actually even easier than that, now (in Tk 8.4.9, but I'm sure since before - anyone know when exactly?). When you create the buttons you want this effect on, just use
button $name -relief flat -overrelief raised
Unfortunately, the relief used when you click is hardcoded (as 'sunken'), so you can't configure it per-widget without hacking the Tk internals for the <Button-1> binding for buttons.
TV 2003-06-10: I wrote a few lines to make all buttons in all windows appear in one column in a separate window, so they can be easily pressed.
Make a toplevel window:
toplevel .bs
Delete all previous content from it (when the next line is repeated for some reason (debugging)):
foreach i [ilist .bs] {destroy $i}
This uses the procedure ilist :
proc ilist { {begin {.}} {listf {winfo children}} {maxdepth {100}} {ident {0}} } { if {$maxdepth <1} return set de {}; set o {} for {set i 0} {$i < $ident} {incr i} {append de { }} foreach i [eval "$listf $begin"] { append o "$i " #puts "$de $i" append o [ilist $i $listf [expr $maxdepth-1] [expr $ident +1]] } return $o }
To do a recursive list, in this case of the default hierarchy: the windows and graphical elements hierarchy.
The actual listing of all buttons in the application, in the example I used I started up a copy of bwise, with one shell, which has a button, too, on the canvas, can go like this:
set c 0; foreach i [ilist] { if {[winfo class $i] eq {Button}} { eval "pack \[button .bs.b$c -text $i -command {[$i cget -command]}\] -fill x" ; incr c } }
This gives:
Agreed, there is a sort of chance of quoting hell in these one liners (I admit I just prettyprinted it), but this is fine with me; it's sort of intelligent list / function decomposition with a bit of an eye for commutation/unquoting.
Oh yes, and they work, I mean you press the button, and the effect is the same as the original button, anywhere in the widget hierarchy. And the behaviour is stored separately, which alternatively could be that one would
.original_button invoke
Just like one could use the -text of the original button instead of its relative window name, but some could have images.
If you want a button (or a label) really small, you'll have to give it a small image instead of text, for example:
image create bitmap dummyImage -data "#define dum_width 1\n#define dum_height 1\ static char dum_bits[] = {\n0x00\n};" button .1 -image dummyImage -width 1 -height 1 -padx 0 -pady 0 label .2 -text "Small enough?" pack .1 .2 -side left ;# RS
HolgerJ: Fine, but maybe this is too small. I find that buttons have quite a lot of space around the text. Can this be made smaller, thus looking less old-fashioned?
ulis: You can have a smaller button by adding -bd 1 -highlightthickness 0.
If you want colored buttons have a look at Lightbutton.
The -highlightbackground option is useful with buttons, but not explained much in the man page. Native buttons are not always rectangular, but the area occupied by a button widget is. The colour of the extra region is set using the -highlightbackground option. Thus the result of
% pack [frame .toolbar -background grey85] % pack [button .toolbar.do -text "Do"] -padx 20 -pady 20
can be that there, in the grey .toolbar frame, is a white rectangle that surrounds the button. To get the appearance of a uniform grey background throughout, have in addition
% .toolbar.do configure -highlightbackground grey85
See also [L1 ].
EKB: To make sure the highlightbg & the toolbar background always match, you can also use
% .toolbar.do configure -highlightbackground [.toolbar cget -background]
plasma 2009-10-29 06:38:44:
Q: How to insert a button in a frame at run time?
I have a button "Insert". When clicked on, a button with text "Inserted!" should appear on the same frame. Is this possible?
A: of course its possible. All buttons are inserted at a frame at run time.
The following isn't a perfect solution but it gives the general idea of how to do what I think you want to do:
frame .f button .f.b -text "Insert" -command [list insertButton .f] set count 0 proc insertButton {frame} { set button [button $frame.b[incr ::count] -text "Inserted!"] pack $button }
Q: Thanks a lot. It does work. Can the button be made dragable? The -dragevent and -dragenabled options on the button didn't work.I also tried the following code:
DragSite::register $button -dragenabled 1
But that didn't work as well.
zenith 2009-11-07 14:39:40:
I have an application in which there is an "Insert" button.On clicking it,a new button will be inserted.On clicking the newly inserted button,I should get a message box displaying the text of the inserted button.How to get the text of the newly inserted button?
dkf 2009-11-09 06:27:35:
If you have the pathname of the button in a variable, btn, use this:
set text [$btn cget -text]
zenith: I did try that out, but the $btn cget -text returns only the text of the last inserted button. I did it using the bind option:
bind $btn <1> { set tex [%W cget -text] }
plasma 2009-11-09 01:08:35: I have a button "save" in my application which is set to "disabled" state. When I click on another button "enable" the save button's state should become normal. I do this using $but configure -state normal command. However the save button's state should return to disabled state when I click any other widget in the application.How to do this? I tried binding "configure -state disabled" command to the Deactivate binding to the Enable button. But it didn't work.
dkf: 2009-11-09 06:20:03:
The <Deactivate> event is only used on OSX (and Windows?) to track how those OSes manage the notion of "current application". What you might be better off doing is using a <FocusOut> event, but you might instead need a binding to the 'all' bind tag which detects when someone clicks, works out that it is not in the button and disables the button once more. Which is a bit complex, but that's how it is. A (non-global) [[grab] might be another approach, but that has other down-sides too.
But to be fair, I think this sounds like a strange non-intuitive way to interact with the GUI. I'd be more inclined to use a [[checkbutton] (or [[radiobutton]-group) to enable this one, since then the user can predictably turn the button on and off.
plasma: The FocusOut event bind didn't work either(forgot to mention it). I'll try your all-binding method,that definitely looks promising.And as you said,using a grab(though non-global) definitely has its downsides.
rlf 2010-01-13 17:53:27:
How do you make a button have multiple commands? I want a button to load a new window and kill the current one but as of right now I can only get it to do one and not the other.
Beware - Use a proc:
button .b -text "Test" -command "buttonPushed" proc buttonPushed {} { # Load your new window # Kill the current window # Do other stuff }