Version 20 of mousewheel

Updated 2007-10-09 13:09:38 by LV

From the bind man page: Some mice on the Windows platform support a mouse wheel which is used for scrolling documents without using the scrollbars. By rolling the wheel, the system will generate MouseWheel events that the application can use to scroll. Like Key events the event is always routed to the window that currently has focus. When the event is received you can use the %D substitution to get the delta field for the event which is a integer value of motion that the mouse wheel has moved. The smallest value for which the system will report is defined by the OS. On Windows 95 & 98 machines this value is at least 120 before it is reported. However, higher resolution devices may be available in the future. The sign of the value determines which direction your widget should scroll. Positive values should scroll up and negative values should scroll down.

Example binding to scroll a canvas (in a little XML browser) up or down:

 bind .t.c <MouseWheel> {%W yview scroll [expr {-%D/120}] units}

Dividing by 120 leads to scrolling one line per wheel click (minimum effective wheel movement) - use a smaller divisor if you want to scroll faster. (RS)

By "cubing" the number of units, you have the effect that slow turning of the wheel moves slowly, while fast turning lets you jump up or down:

 bind .t.c <MouseWheel> {%W yview scroll [expr {int(pow(%D/-120,3))}] units}

Kevin Walzer On OS X/Aqua the correct mousewheel binding would be:

 bind .t.c <MouseWheel> {%W yview scroll [expr {- (%D)}] units}

PAK This didn't work on my Windows 2000 box with Tcl 8.4. Instead I accumulated all MouseWheel events for 200 ms before cubing. E.g.,

 bind .t.c <MouseWheel> { collect %W %D }
 set ::afterid {}
 set ::delta 0
 proc collect {W D} {
    global delta
    global afterid
    if {$afterid eq {}} {
        set delta $D
        set afterid [after 200 "$W yview scroll \[expr {int(pow(\$delta/-120,3))}] units"]
    } else {
        incr delta $D
    }
 }

This will also work on Unix with the Button-4/Button-5 to MouseWheel translation given below.


rmax AFAIK, <MouseWheel> works on Windows only. On X you have to bind to the <Button-4> and <Button-5> events. - IIRC, Button-4/5 is even not the only way of mapping the mouse wheel on X. As X doesn't support mouse wheels natively, there are at least three different possible mappings floating around, but I don't currently tremember the other two.


KBK Note that on Windows, the <MouseWheel> events don't go to the window that contains the mouse pointer, but rather the window that has the keyboard focus. For various arcane reasons, this behavior is The Right Thing, but it surprises most programmers the first time they see it. Also, note that directing the <MouseWheel> events to the window with the focus means that the window that is to be scrolled must be able to take the focus.

PAK If you insist on doing The Wrong Thing, you can do so by binding the toplevel window to the <MouseWheel> event then trigger a virtual event <<Wheel>> with the correct window. Because you can't set -delta for virtual events, you will need to save the delta value in a global variable which you can recover in the wheel binding.

 bind [winfo toplevel .t.graph] <MouseWheel> { trigger %W %X %Y %D }
 proc trigger {W X Y D} {
    set w [winfo containing -displayof $W $X $Y]
    if { $w ne "" } {
        set x [expr {$X-[winfo rootx $w]}]
        set y [expr {$Y-[winfo rooty $w]}]
        global delta
        set delta $D
        event generate $w <<Wheel>> -rootx $X -rooty $Y -x $x -y $y
    }
 }
 bind .t.graph <<Wheel>> { %W zoom %x %y $delta }

KPV Tip 171 [L1 ] proposes to change <MouseWheel> events from being sent to the focus window to the window containing the mouse. This is actually how BWidgets has been doing it all along. I find that this is more intuitive and is The Right Thing.


Font resizing via mousewheel: The following binding reconfigures a widget with -font attribute (e.g. text, message, entry ...) according to mousewheel rotation:

 pack [text .t -font {Helvetica 10} -wrap word -width 30 -height 10]
 .t insert end "This is a little demo text to test the font sizing via mousewheel"
 catch {bind .t <MouseWheel> {
     set font [%W cget -font]
     set fs [expr {[lindex $font 1]+%D/120}]
     %W config -font [lreplace $font 1 1 $fs] 
 }}

I've added the catch because on older Windows versions this event is not supported. Also, the default font does not scale in fine steps - but by specifying one that is implemented as TrueType, the effect is really nice ;-) RS


MGS Here's a quick little hack to get MouseWheel events on X (Linux):

 bind all <Button-4> \
   {event generate [focus -displayof %W] <MouseWheel> -delta  120}

 bind all <Button-5> \
   {event generate [focus -displayof %W] <MouseWheel> -delta -120}

Then you could do (for instance):

 bind Scrollbar <MouseWheel> {eval [%W cget -command] scroll [expr {%D/-120}] units}

but this won't work (as it is) for scrollbars with no -command set. RS: Then again, a scrollbar without -command is a pretty useless creature...


RWC Some links to other MouseWheel code:

http://colas.nahaboo.net/mouse-wheel-scroll/#tcl

http://aspn.activestate.com/ASPN/Cookbook/Tcl/Recipe/68394

Of the above, only the ASPN example will vertically scroll a canvas. Later in the ASPN article is another implementation that attempts to include horizontal scrolling, but doesn't appear to work for a canvas.


RS 2007-09-24: I'm not sure how the state of discussion is about mousewheel enabling by just mousing-over, but I wanted this for a UI with a listbox and a text widget, both scrollable. Simple but useful:

 foreach i {.listbox .text} {bind $i <Enter> {focus %W}}

MG has added this binding to one of his apps, which has a lot of textwidgets, for handling mousewheel events (requires Tcl 8.5):

 bind Text <MouseWheel> {}
 bind all <MouseWheel> [list mouseWheel %W %D]
 proc mouseWheel {widget delta} {

  if { $delta >= 0 } {
       set cmd [list yview scroll [expr {-$delta/3}] pixels]
     } else {
       set cmd [list yview scroll [expr {(2-$delta)/3}] pixels]
     }
  set over [winfo containing -displayof $widget {*}[winfo pointerxy $widget]]
  if { $over == "" || [catch {$over {expand}$cmd}] } {
       catch {$widget {expand}$cmd}
     }

  return;

 };# mouseWheel

It attempts to scroll the widget the mouse is over first, and then tries the one with the focus (where the event was actually generated) if it fails.


Tk syntax help - Arts and Crafts of Tcl-Tk Programming