5

Why can't I get the python tkinter canvas to respond to the vertical and horizontal swiping/scrolling of Apple's Magic Mouse? The scrollbars for the canvas work properly (meaning the horizontal bar works when I swipe/scroll horizontally on the mouse but not when I swipe/scroll vertically, and the vertical scrollbar moves when I swipe/scroll vertically but doesn't react to any horizontal swiping/scrolling motion), but the canvas doesn't react to any swiping/scrolling of the mouse.

Here is my example/test code:

from tkinter import *
import tkinter.ttk as ttk
from PIL import Image, ImageTk
root = Tk()

h = Scrollbar(root, orient=HORIZONTAL)
v = Scrollbar(root, orient=VERTICAL)
canvas = Canvas(root, scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview
theImage = ImageTk.PhotoImage(Image.open('example.png')) #Assume a very large image
canvas.create_image(0,0,image=theImage, anchor='nw')
canvas.grid(column=0, row=0, sticky=(N,W,E,S))
h.grid(column=0, row=1, sticky=(W,E))
v.grid(column=1, row=0, sticky=(N,S))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.mainloop()

I'm a total novice when it comes to both Python and tkinter, but I feel like this should be really simple and obvious (or at least do-able). And yet I've looked all over and still can't find the answer (though I might be searching using the wrong jargon).

What does it take to make the canvas react to scrolling/swiping inputs from the Magic Mouse like the scrollbars do already?

Edit: I'm using Python 3.4, Tkinter 8.5.18, Mac OS X 10.9.5, and an Apple Magic Mouse

2 Answers2

4

Assuming that the magic mouse's scroll and swipe inputs work like the scrolling regions on a standard trackpad, you need to bind the <MouseWheel> and <Shift-MouseWheel> events.

First the code, then some notes.

from tkinter import *
import tkinter.ttk as ttk
from PIL import Image, ImageTk

def on_vertical(event):
    canvas.yview_scroll(-1 * event.delta, 'units')

def on_horizontal(event):
    canvas.xview_scroll(-1 * event.delta, 'units')

root = Tk()
h = Scrollbar(root, orient=HORIZONTAL)
v = Scrollbar(root, orient=VERTICAL)
canvas = Canvas(root, scrollregion=(0, 0, 1000, 1000), yscrollcommand=v.set, xscrollcommand=h.set)
h['command'] = canvas.xview
v['command'] = canvas.yview
theImage = ImageTk.PhotoImage(Image.open('img'))
canvas.create_image(0,0,image=theImage, anchor='nw')
canvas.grid(column=0, row=0, sticky=(N,W,E,S))

canvas.bind_all('<MouseWheel>', on_vertical)
canvas.bind_all('<Shift-MouseWheel>', on_horizontal)

h.grid(column=0, row=1, sticky=(W,E))
v.grid(column=1, row=0, sticky=(N,S))
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.mainloop()

As you can see there are only 2 changes.

  1. There are two callback functions to handle the scroll events on_vertical and on_horizontal
  2. The canvas <MouseWheel> and <Shift-MouseWheel> events are bound to on_vertical and on_horizontal respectively.
theB
  • 6,450
  • 1
  • 28
  • 38
  • Thank you! Your solution works, though the deltas were going the wrong way at first. But that was easily fixed by sticking a negative sign in front of the event.delta in the callback functions. – IntrepidArtisan Oct 29 '15 at 01:23
  • Yep, sorry about that, I'm on Windows, so the wheel delta has to be divided by 120 and inverted, and when I got rid of the division I accidentally got rid of the negation. I updated the code to fix the bug. Glad it worked for you. – theB Oct 29 '15 at 01:31
  • It doesn't work on the newest high sierra. I get `UnicodeDecodeError`. – Piotr Wasilewicz Oct 30 '17 at 14:45
0

Use ntk Canvas to create a scroll able Canvas, by default it works on mouse scroll, but if you create a ntk Scrollbar widget, and assign it to canvas, you are good to go.

ntk works on top of tkinter, to install it from pypi with pip

pip install ntk

use ntk widgets to get awesome design.

from ntk import Tk, Canvas, Scrollbar

def main():
    root = Tk()

canvas = Canvas(root)

    root.mainloop()    

if __name__=='__main__':
    main()

this canvas is scroll able by mouse scroll, but if you want to set a bar with it, you can create a scroll bar widget and pass this canvas object as second param

scroll = Scrollbar(root, canvas)

you can change scroll able area from canvas by configure or pass it when canvas is creating

canvas = Canvas(root, scrollregion=[0,0,100,50]) # [x1, y1, x2, y2]

or configure it by

canvas.config(scrollregion=[0,0,100,50])

welcome to beautiful design in less code, happy codding.

Nj Nafir
  • 570
  • 3
  • 13