mousewheel

Description

The <MouseWheel> event is documented on the bind manual page.

It is emitted by a mouse wheel on a mouse or by a mousepad gesture. It indicated vertical scrolling (positive delta - upwards) or horizontal scrolling with the shift modifier.

The unit of scrolling is different on the platforms:

  • Windows: A mousewheel click is translated by a delta of 120. Take care, that high resolution mousepads may emit smaller values which may accumulate.
  • MacOS: A mousewheel click is translated by a delta of 1.
  • X11: The mousewheel triggers button 4/5 (vertical) and Shift-4/Shift-5 (horizontal)

Support by TK over time

There are a couple of changes in TK which developped the MouseWheel binding. Below are many recipies, which address older TK versions and don't apply any more as the support is now quite native.

Here is an overview of the changes:

  • TK8.6.0: Mousewheel event delivered to the window below the mouse and not to the focus window. This replaced and obsolated the alternate solution in TIP 171.
  • TK8.6.0: Shift-Mousewheel indicate horizontal scrolling (TIP 171)
  • TK8.6.9 (Patchlevel to verify): Windows fine granularity mouse support (smaller increment than 120)
  • TK8.6.10: Hardware horizontal scrolling (e.g. tilting mousewheels, recent touchpads) triggers Shift-Mousewheel bindings on Windows: [L1 ], and Shift-4/Shift-5 bindings on X11 (was previously buttons 6/7): [L2 ]
  • TK8.7.0: ttk::scrollbars are sensible on mousewheel events
  • TK8.7.0: horizontal scrollbars and ttk::scrollbars are sensible on mousewheel events without shift. (TIP 563)

The package scrollutil by Csaba Nemethi gives currently a state of the art mousewheel support.

Examples

Windows 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}

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


  Pre Tk8.6 - focus

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 [L3 ] 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.


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 {*}$cmd}] } {
       catch {$widget {*}$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.

RWC Some links to other MouseWheel code:

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.



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

chrstphrchvz 2020-06-24: TIP #474 [L4 ] hopes to make <MouseWheel> available in Tk 8.7 on X11, eliminating the need for this. In the meantime, please remember to use if {[tk windowingsystem] eq "x11"} {…} when binding to buttons 4/5 for scrolling (as buttons with those numbers have different meaning on Windows and macOS Aqua, corresponding to buttons 8/9 on X11).


move a scale widget by the mouse wheel

Here you are another implementation for the mousewheel: making a scale scrolled by mousewheel

scale .s -orient horizontal -from 0 -to 100
pack .s
bind Scale <Enter> {focus %W} ;    # focus on the scale when mouse comes over it
bind Scale <Leave> {focus .} ;     # focus out the scale
bind Scale <MouseWheel> {set increment [expr (%D/120)]; if {$increment == 1} {event generate %W <Left>} else {event generate %W <Right>} }