Resize control

N.B.: In Tcl 8.5 and beyond, the discussion on this page is obsolete, because ttk::sizegrip handles all the work.

Bryan Oakley posted in comp.lang.tcl:

 I can put something there that looks like a resize handle, but the window
 manager still requires I grab the actual edge of the window. On real windows
 apps you can actually drag by clicking inside the handle and not just on the
 edge. That's what makes me think it must be done in C.

 I once shipped a product with the glyph there. I knew it wouldn't actually
 behave like a real windows grab handle, but having the glyph and a bug
 report saying the resize handle didn't work was better than not having the
 glyph. Go figure. It sure *looked* like a real windows app, though, and
 that's all marketing cared about.

KBK (12 February 2002) - It's not all that hard to make the glyph actually work. The following code shows how. Note that it is set up to do nothing if not on windows because it depends on the Marlett font to do the right thing. It could be made to work on the other platforms by judicious use of strimj - string image routines, but I haven't bothered.

(one could also just use a tiny canvas with a couple of lines drawn on it)

Vince I notice that MacOS X uses a very similar corner glyph. What's the best way to get this working on Aqua-tk

# We begin by creating a namespace that will hold the resize control stuff.

 namespace eval Resizer {
 
     namespace export resizer
 
     bind Resizer <1> [namespace code [list begin_resize %W %X %Y]]
     bind Resizer <B1-Motion> [namespace code [list continue_resize %W %X %Y]]
     bind Resizer <ButtonRelease-1> [namespace code [list end_resize %W %X %Y]]
 
 }
 
# The following proc creates the control:

 # Resizer::resizer
 #
 #       Decorates a top-level window with a resize control
 #
 # Parameters:
 #       w - Path name of the resize control
 #
 # Results:
 #       Returns the path name of the resize control
 #
 # Side effects:
 #       Creates the resize control and places it within its toplevel.
 
 proc Resizer::resizer { w } {
 
     if { [string compare windows $::tcl_platform(platform)] } {
         return
     }
 
     canvas $w -width 16 -height 16 -highlightthickness 0 -borderwidth 0 \
         -cursor size_nw_se
 
     set t [winfo toplevel $w]
 
     $w create text 16 16 -text \u006f \
         -font {Marlett -16} -fill white -anchor se
     $w create text 16 16 -text \u0070 \
         -font {Marlett -16} -fill gray50 -anchor se
     place $w -relx 1 -rely 1 -anchor se
 
     bindtags $w [list all $t Resizer $w]
 
     return $w
 
 }
 
# The following proc handles the mouse click on the resize control.  It stores the original size of the window and the initial coords of the mouse relative to the root.

 proc Resizer::begin_resize { w rootx rooty } {
     variable info
     set t [winfo toplevel $w]
     set relx [expr { $rootx - [winfo rootx $t] }]
     set rely [expr { $rooty - [winfo rooty $t] }]
     set info(startx,$w) $relx
     set info(starty,$w) $rely
     set info(startw,$w) [winfo width $t]
     set info(starth,$w) [winfo height $t]
     return
 }
 
# The following proc handles mouse motion on the resize control by asking the wm to adjust the size of the window.

 proc Resizer::continue_resize { w rootx rooty } {
     variable info
     set t [winfo toplevel $w]
     set relx [expr { $rootx - [winfo rootx $t] }]
     set rely [expr { $rooty - [winfo rooty $t] }]
     set width [expr { $relx - $info(startx,$w) + $info(startw,$w) }]
     set height [expr { $rely - $info(starty,$w) + $info(starth,$w) }]
     if { $width < 0 } {
         set width 0
     }
     if { $height < 0 } {
         set height 0
     }
     wm geometry $t ${width}x${height}
     return
 }

# The following proc cleans up when the user releases the mouse button.
 
 proc Resizer::end_resize { w rootx rooty } {
     variable info
     continue_resize $w $rootx $rooty
     unset info(startx,$w)
     unset info(starty,$w)
     unset info(startw,$w)
     unset info(starth,$w)
 }

# The usual way to put the corner into the application main window is:
     
 grid [label .foo -text "Hello, world!"]
 grid columnconfigure . 0 -weight 1
 grid rowconfigure . 0 -weight 1
 Resizer::resizer .resizer

# which must be done AFTER everything else is created, or else followed by

 raise .resizer

# so that the resize control comes out ABOVE everything else.

#There are endless variations on this general theme, but this should give you a starting point.

EKB (10 March 2005) - It is possible to get around the Windows limitation by using a (base64 encoded) gif rather than the character from the Marlett font (see Creating image photo data from GIF files). Here's a variation on KBK's code that does that. Replace Resizer::resizer with:

 proc Resizer::resizer { w } {
     image create photo gripper -data {
        R0lGODlhEAAQAOMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwMDcwKbK8ICA
        gNTQyP///wAAAAAAAAAAACH5BAEAAA8ALAAAAAAQABAAAAQm8MlJq70468v2
        ZIr3gOFGemeXpSWnhKcLvxgLW/bYSnnM0zkRJQIAOw==
     }
     
     canvas $w -width 16 -height 16 -highlightthickness 0 -borderwidth 0 \
         -cursor size_nw_se

     set t [winfo toplevel $w]

     $w create image 16 16 -image gripper -anchor se
     place $w -relx 1 -rely 1 -anchor se

     bindtags $w [list all $t Resizer $w]

     return $w
 }

Note (16 April 2005) -- EKB replaced the original data string with one that's transparent where appropriate and also took out the "if { [string compare windows $::tcl_platform(platform)] }" check, since portability is the whole point of using the gif!


RS 2007-06-26: The above script crashes on Win XP (probably due to the image) in the combination Tcl/Tk 8.4.1 + Img 1.3. 8.4.1 alone does not crash, nor does 8.4.9 + Img 1.3. Strange...