Search code examples
pythontkinterscrolltk-toolkittkinter-canvas

Tkinter - Scroll with touchpad / mouse gestures / two fingers scrolling in tkinter?


I wanted to implement two finger scrolling in tkinter. Here is the result of my own attempt:

import tkinter as tk

class Scene:
    def __init__(self, canvas):
        self.canvas = canvas
        self.elements = [
            {
                "type": "rect",
                "x": canvas.winfo_width() / 2,
                "y": canvas.winfo_height() / 2,
                "width": 200,
                "height": 200,
                "color": (55 / 255, 55 / 255, 10 / 255),
            },
            {
                "type": "rect",
                "x": 100,
                "y": 300,
                "width": 200,
                "height": 200,
                "color": (155 / 255, 200 / 255, 10 / 255),
            },
        ]

    def update_scene(self, offset):
        for element in self.elements:
            element["x"] -= offset[0]
            element["y"] -= offset[1]
        self.render_scene()

    def render_scene(self):
        self.canvas.delete("all")
        for element in self.elements:
            if element["type"] == "rect":
                self.canvas.create_rectangle(
                    element["x"],
                    element["y"],
                    element["x"] + element["width"],
                    element["y"] + element["height"],
                    fill=f"#{int(element['color'][0] * 255):02x}{int(element['color'][1] * 255):02x}{int(element['color'][2] * 255):02x}",
                )
            else:
                print(f"Error: type {element['type']} is not supported.")


root = tk.Tk()
root.geometry("{}x{}".format(800, 600))

canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)

canvas_scroll = [0, 0]

scene = Scene(canvas)
scene.render_scene()


def on_mouse_scroll(event):
    canvas_scroll[0] = event.delta
    canvas_scroll[1] = event.delta
    scene.update_scene(canvas_scroll)


canvas.bind("<MouseWheel>", on_mouse_scroll)

root.mainloop()

The above only works in one diagonal/direction, instead of any direction (up, down, left, right, and all four diagonals)

The above was inspired by a Javascript snippet I found here: https://jsfiddle.net/qmyho24r/

I know using Shift-MouseWheel works, but then I have to also press the shift key, instead of just using the trackpad and two fingers (like in the Javascript example).

How can I use two fingers scrolling in Tkinter?


Solution

  • tkinter does only support horizontal scrolling on windows from patchlevel 8.6.10 <=
    I've created a small example that works for me with tkinter 8.6.12 on Win11. When using two fingers with a little gap between them I can successfully scroll in both direction and move the View in a circle. I retrieve two different events depending on which direction I swap. This is also mentioned in the documentation.

    import tkinter as tk
    import sys
    
    #https://stackoverflow.com/a/13874620/13629335
    OS = sys.platform
    
    def horizontal_scroll(event):
        if OS in ('win32','darwin'):
            canvas.xview_scroll(int(event.delta/120), 'units')
        elif OS == 'linux':
            if event.num == 5:
                canvas.xview_scroll(-1, 'units')
            elif event.num == 4:
                canvas.xview_scroll(1, 'units')
    
    def vertical_scroll(event):
        if OS in ('win32','darwin'):
            canvas.yview_scroll(int(event.delta/120), 'units')
        elif OS == 'linux':
            if event.num == 5:
                canvas.yview_scroll(-1, 'units')
            elif event.num == 4:
                canvas.yview_scroll(1, 'units')
    
    root = tk.Tk('test')
    if int(root.tk.call("info", "patchlevel").split('.')[-1]) >= 10:
        #https://docs.activestate.com/activetcl/8.6/get/relnotes/
        canvas = tk.Canvas(root,highlightthickness=0,bd=0)
        #something to show
        v = viewsize = 150
        cw = canvas.winfo_reqwidth()+v
        ch = canvas.winfo_reqheight()+v/2
        s = square = 50
        canvas.create_rectangle(0,0, s,s, tags=('NW',))
        canvas.create_rectangle(cw-s,0, cw,s, tags=('NE',))
        canvas.create_rectangle(cw-s,ch-s, cw,ch, tags=('SE',))
        canvas.create_rectangle(0,ch-s, s,ch, tags=('SW',))
        canvas.pack(fill='both', expand=True, padx=10)
        #update scrollregion
        canvas.configure(scrollregion=canvas.bbox('all'))
        #bindings
        #https://stackoverflow.com/a/17457843/13629335
        if OS in ('win32','darwin'):
            #https://apple.stackexchange.com/q/392936
            root.bind('<MouseWheel>', vertical_scroll)
            root.bind('<Shift-MouseWheel>', horizontal_scroll)
        if OS == 'linux':
            #https://stackoverflow.com/a/17452217/13629335
            root.bind('<Button-4>', vertical_scroll)
            root.bind('<Button-5>', vertical_scroll)
            root.bind('<Shift-Button-4>', horizontal_scroll)
            root.bind('<Shift-Button-5>', horizontal_scroll)
        root.mainloop()
    else:
        print('at least for windows it is supported at patchlevel 8.6.10')
        root.destroy()