Canvas Buttons in 3-D

Light weight widgets that are drawn on a canvas, rather than having their own window, might be useful in several situations.

Kevin Kenny made some nice canvas buttons.

I took Kevin's buttons and added a 3-D appearance. Kevin's file, slightly altered, appears here.

The 3-D effect is achieved using routines from the 3-D Boxes (Support for Canvas Buttons in 3-D) page.

Those routines allow you treat corners and sides separately, so if someone wanted to make a combobox, they could. It's not restricted to boxes, and it's not restricted to buttons.

Here's a link to the support routines, that these 3-D canvas buttons are built on.

3-D Boxes (Support for Canvas Buttons in 3-D)

Here's a link to the original Canvas Buttons.

Canvas Buttons

                         Rick Hedin

 # ----------------------------------------------------------------------
 #
 # cbutton3d.tcl --
 #
 #        Example of how to provide button-like behavior on canvas
 #        items.
 #
 #       This version has 3-D buttons. 
 
 set ::RCSID([info script]) \
   {$Id: 1379,v 1.3 2006-09-24 06:00:02 jcw Exp $}
 
 package provide canvasbutton 1.0
 
 source box3d.tcl  ;# Assumed to be in the working directory. 
 
 namespace eval canvasbutton {
 
 # nexttag - Next unique tag number for a "button" being
 #           created
 
 variable nexttag 0
 
 # command - command(tag#) contains the command to execute when
 #            a "button" is selected.
 
 variable command
 
 # cursor - cursor(pathName) contains the (saved) cursor
 #           symbol of the widget when the pointer is in
 #           a "button"
 
 variable cursor
 
 # enteredButton - contains the tag number of the button
 #                  containing the pointer.
 
 variable enteredButton {}
 
 # pressedButton - contains the tag number of the "button"
 #                  in which the mouse button was pressed
 
 variable pressedButton {}
 
 # buttoninfo - Info about the button, indexed by the button's id.  
 #     buttoninfo(<id>,id) - The first argument is the id of the button 
 #       according to the button logic.  Returns the id of the button according 
 #       to the button display. 
 #     buttoninfo(<id>,textx) - Returns the original x coordinate of the text, 
 #       without shifting. 
 #     buttoninfo(<id>,texty) - The original y coordinate of the text. 
 variable buttoninfo
 
 namespace export canvasbutton
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::canvasbutton --
 #
 #        Create a button-like object on a canvas.
 #
 # Parameters:
 #        w        Path name of the canvas
 #        x0        Canvas X co-ordinate of left edge
 #        y0        Canvas Y co-ordinate of top edge
 #        x1        Canvas X co-ordinate of right edge
 #        y1        Canvas Y co-ordinate of bottom edge
 #        text        Text to display in the button
 #        cmd        Command to execute when the button is selected.
 #
 # Results:
 #        Unique canvas tag assigned to the items that make
 #        up the button.
 #
 # Side effects:
 #        A rectangle and a text item are created in the canvas,
 #        and bindings are established to give them button-like
 #        behavior.
 #
 #----------------------------------------------------------------------
 
 proc canvasbutton::canvasbutton {w x0 y0 x1 y1 text cmd} {
     variable nexttag
     variable command
     variable buttoninfo
 
     set btag [list canvasb# [incr nexttag]]
 
     set command($btag) $cmd
 
     $w create rectangle [expr $x0 - 2] [expr $y0 - 2] [expr $x1 + 2] \
      [expr $y1 + 2] -outline black -width 3 -state hidden \
      -tags [list $btag [linsert $btag end frame] ]
 
     set id [Create3DBox $w $x0 $y0 $x1 $y1 5 -dx dx -dy dy \
      -tags [list $btag [linsert $btag end button] ] ]
 
     set buttoninfo($nexttag,id) $id
 
     set x [expr { ($x0+$x1) / 2 }]
     set y [expr { ($y0+$y1) / 2 }]
     
     set buttoninfo($nexttag,textx) $x
     set buttoninfo($nexttag,texty) $y
 
     $w create text [expr $x + $dx] [expr $y + $dx] -anchor center \
      -justify center -text $text \
      -tags [list $btag [linsert $btag end text]]
 
     set extent [Get3DBoxPolygon $id]
 
     $w create polygon $extent -fill "" -outline "" \
      -tags [list canvasb $btag [linsert $btag end region] ]
 
     # For an exciting error, reverse the order of creation of the text 
     # and the polygon, and click the buttons until you enter a tight, 
     # unescapeable loop. 
 
     $w bind canvasb <Enter> [list [namespace current]::enter %W]
     $w bind canvasb <Leave> [list [namespace current]::leave %W]
     $w bind canvasb <ButtonPress-1> \
             [list [namespace current]::press %W]
     $w bind canvasb <ButtonRelease-1> \
             [list [namespace current]::release %W]
 
     return $btag
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::enter --
 #
 #        Process the <Enter> event on a canvas-button.
 #
 # Parameters:
 #        w        Path name of the canvas
 #
 # Results:
 #        None.
 #
 # Side effects:
 #        When the mouse pointer is in a button, the button is
 #        highlighted with a broad outline and the cursor
 #        symbol changes to an arrow.  When the active button
 #        is pressed, it is highlighted in green.
 #
 # ----------------------------------------------------------------------
 
 proc canvasbutton::enter {w} {
     variable enteredButton
     variable pressedButton
     variable buttoninfo
     variable cursor
 
     set enteredButton [findBtag $w]
     set frame [linsert $enteredButton end frame]
     set button [linsert $enteredButton end button]
     set text [linsert $enteredButton end text]
     set cursor($w) [$w cget -cursor]
     $w configure -cursor arrow
     $w itemconfigure $frame -state normal
     $w lower $frame $button
     if {![string compare $enteredButton $pressedButton]} {
         set id [lindex $enteredButton 1]
         Set3DBoxRelief $buttoninfo($id,id) recessed -dx dx -dy dy
         $w coords $text [expr $buttoninfo($id,textx) + $dx] \
          [expr $buttoninfo($id,texty) + $dy]
     }
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::leave --
 #
 #        Process the <Leave> event on a canvas-button.
 #
 # Parameters:
 #        w        Path name of the canvas
 #
 # Results:
 #        None.
 #
 # Side effects:
 #        Reverts the cursor symbol, the border width
 #        if needed, the highlight color of the button.
 #
 # ----------------------------------------------------------------------
 
 proc canvasbutton::leave {w} {
     variable enteredButton
     variable pressedButton
     variable buttoninfo
     variable cursor
     if {[string compare $enteredButton {}]} {
         set btag [findBtag $w]
         set frame [linsert $btag end frame]
         set text [linsert $btag end text]
         $w itemconfigure $frame -state hidden
         $w configure -cursor $cursor($w)
         unset cursor($w)
         if {![string compare $btag $pressedButton]} {
             set id [lindex $btag 1]
             Set3DBoxRelief $buttoninfo($id,id) raised -dx dx -dy dy
             $w coords $text [expr $buttoninfo($id,textx) + $dx] \
              [expr $buttoninfo($id,texty) + $dy]
         }
         set enteredButton {}
     }
     return
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::press --
 #
 #        Process the <ButtonPress-1> event on a canvas-button.
 #
 # Parameters:
 #        w        Path name of the canvas
 #
 # Results:
 #        None.
 #
 # Side effects:
 #        Highlights the selected button in green.
 #
 # ----------------------------------------------------------------------
 
 proc canvasbutton::press {w} {
     variable pressedButton
     variable buttoninfo
     set pressedButton [findBtag $w]
     set text [linsert $pressedButton end text]
     set id [lindex $pressedButton 1]
     Set3DBoxRelief $buttoninfo($id,id) recessed -dx dx -dy dy
     $w coords $text [expr $buttoninfo($id,textx) + $dx] \
      [expr $buttoninfo($id,texty) + $dy]
     return
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::release --
 #
 #        Process the <ButtonRelease-1> event on a canvas-button.
 #
 # Parameters:
 #        w        Path name of the canvas
 #
 # Results:
 #        None.
 #
 # Side effects:
 #        Reverts the highlight color on the button.  If the
 #        mouse has not left the button, invokes the button's
 #        command.
 #
 # ----------------------------------------------------------------------
 
 proc canvasbutton::release {w} {
     variable enteredButton
     variable pressedButton
     variable buttoninfo
     variable command
 
     set pressedButtonWas $pressedButton
     set pressedButton {}
 
     set text [linsert $pressedButtonWas end text]
     set id [lindex $pressedButtonWas 1]
     Set3DBoxRelief $buttoninfo($id,id) raised -dx dx -dy dy
     $w coords $text [expr $buttoninfo($id,textx) + $dx] \
      [expr $buttoninfo($id,texty) + $dy]
 
     if {![string compare $enteredButton $pressedButtonWas]} {
         uplevel #0 $command($pressedButtonWas)
     }
     return
 }
 
 # ----------------------------------------------------------------------
 #
 # canvasbutton::findBtag --
 #
 #        Locate the unique tag of a canvas-button
 #
 # Parameters:
 #        w        Path name of the canvas
 #
 # Results:
 #        Button tag, or the null string if the current
 #        item is not a canvas-button
 #
 # Side effects:
 #        Searches the tag list of the current canvas item
 #        for a tag that begins with the string, `canvasb#',
 #        and returns the first two elements of the tag
 #        interpreted as a Tcl list.
 #
 # ----------------------------------------------------------------------
 
 proc canvasbutton::findBtag {w} {
     foreach tag [$w itemcget current -tags] {
         if {[regexp {^canvasb#} [lindex $tag 0]]} {
             return [lrange $tag 0 1]
         }
     }
     return {}
 }
 
 if {![string compare $argv0 [info script]]} {
 
     grid [canvas .c -width 300 -height 200 -cursor crosshair]
     
     namespace import canvasbutton::*
 
     .c create text 150 150 -anchor n -tags label \
             -font {Helvetica 10 bold}
 
     canvasbutton .c 30 70 80 120 "First\nButton" {
         .c itemconfigure label -text One
     }
     canvasbutton .c 125 70 175 120 "Second\nButton" {
         .c itemconfigure label -text Two
     }
     canvasbutton .c 220 70 270 120 "Third\nButton" {
         .c itemconfigure label -text Three
     }
     canvasbutton .c 240 160 280 180 "Quit" exit
 }