4

I am making a kiosk app for a raspberry pi in python with a 7 inch touchscreen. Everything works well and now, I am trying to make the scroll works like it does on touchscreens. I know that raspbian haven't got a properly touch interface, so, every touch on the screen work as a mouse-click, and if I move my finger touching the screen, works like the drag function.

To make this on python I use my modified version code of this Vertical Scrolled Frame using canvas and I need to add events binding <ButtonPress-1> and <B1-Motion>.

<ButtonPress-1> might save the y position of the click and enable the bind_all function for <B1-Motion>.

<B1-Motion> might set the scroll setting up or down the differrence between the y value saved by <ButtonPress-1> and the event.y of this event.

<ButtonRelease-1> might disable the bind_all function with <unbind_all> of the scroll.

My added code of the events is this, but I don't know how to make it work properly the .yview function of the canvas to make my function works as desired.

def moving(event):
    #In this part I don't know how to make my effect
    self.canvas.yview('scroll',event.y,"units")

def clicked(event):
    global initialy
    initialy = event.y
    self.canvas.bind_all('<B1-Motion>', moving)

def released(event):
    self.canvas.unbind_all('<B1-Motion>')

self.canvas.bind_all('<ButtonPress-1>',clicked)
self.canvas.bind_all('<ButtonRelease-1>', released)

2 Answers2

3

Taking Saad's code as a base, I have modified it to make it work on every S.O. (win, linux,mac) using yview_moveto and I have applied some modifications as I explain here.

EDIT: I have edited the code to show the complete class.

class VerticalScrolledFrame(Frame):
    """A pure Tkinter scrollable frame that actually works!
    * Use the 'interior' attribute to place widgets inside the scrollable frame
    * Construct and pack/place/grid normally
    * This frame only allows vertical scrolling

    """
    def __init__(self, parent, bg,*args, **kw):
        Frame.__init__(self, parent, *args, **kw)

        # create a canvas object and a vertical scrollbar for scrolling it

        canvas = Canvas(self, bd=0, highlightthickness=0,bg=bg)
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)

        # reset the view
        canvas.xview_moveto(0)
        canvas.yview_moveto(0)

        self.canvasheight = 2000

        # create a frame inside the canvas which will be scrolled with it
        self.interior = interior = Frame(canvas,height=self.canvasheight,bg=bg)
        interior_id = canvas.create_window(0, 0, window=interior,anchor=NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
            canvas.config(scrollregion="0 0 %s %s" % size)
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the canvas's width to fit the inner frame
                canvas.config(width=interior.winfo_reqwidth())
        interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if interior.winfo_reqwidth() != canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
        canvas.bind('<Configure>', _configure_canvas)

        self.offset_y = 0
        self.prevy = 0
        self.scrollposition = 1

        def on_press(event):
            self.offset_y = event.y_root
            if self.scrollposition < 1:
                self.scrollposition = 1
            elif self.scrollposition > self.canvasheight:
                self.scrollposition = self.canvasheight
            canvas.yview_moveto(self.scrollposition / self.canvasheight)

        def on_touch_scroll(event):
            nowy = event.y_root

            sectionmoved = 15
            if nowy > self.prevy:
                event.delta = -sectionmoved
            elif nowy < self.prevy:
                event.delta = sectionmoved
            else:
                event.delta = 0
            self.prevy= nowy

            self.scrollposition += event.delta
            canvas.yview_moveto(self.scrollposition/ self.canvasheight)

        self.bind("<Enter>", lambda _: self.bind_all('<Button-1>', on_press), '+')
        self.bind("<Leave>", lambda _: self.unbind_all('<Button-1>'), '+')
        self.bind("<Enter>", lambda _: self.bind_all('<B1-Motion>', on_touch_scroll), '+')
        self.bind("<Leave>", lambda _: self.unbind_all('<B1-Motion>'), '+')
  • I paste the exact code in the `VerticalScrolledFrame` and it doesn't show the `interior` Frame. Make sure you haven't done any other modifications to `VerticalScrolledFrame` or if you did then you should probably post the whole class. – Saad May 16 '19 at 15:12
  • I have made the proper changes to the post, this is the full class. This works for me now. – David González Blazman May 17 '19 at 07:49
2

I modified the VerticalScrolledFrame with few lines of code which does scroll how you want. I tested the code with the VerticalScrolledFrame and it works fine with the mouse. Add the below code to the VerticalScrolledFrame.

self.offset_y = 0
def on_press(evt):
    self.offset_y = evt.y_root

def on_touch_scroll(evt): 
    if evt.y_root-self.offset_y<0: 
        evt.delta = -1
    else: 
        evt.delta = 1
    # canvas.yview_scroll(-1*(evt.delta), 'units') # For MacOS
    canvas.yview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows

self.bind("<Enter>", lambda _: self.bind_all('<Button-1>', on_press), '+')
self.bind("<Leave>", lambda _: self.unbind_all('<Button-1>'), '+')
self.bind("<Enter>", lambda _: self.bind_all('<B1-Motion>', on_touch_scroll), '+')
self.bind("<Leave>", lambda _: self.unbind_all('<B1-Motion>'), '+')

enter image description here

I hope you can find this useful.

Saad
  • 3,340
  • 2
  • 10
  • 32
  • I think I understand what you're trying to do, I do something similar with `yview_moveto()` but doesn't work as expected, however, your code doesn't work for me, I don't know if I doing something wrong, the event triggers correctly but, the scroll still doesn't work. – David González Blazman May 16 '19 at 11:32
  • 1
    It has to do with the `event.delta` as they're different on different systems. Check this [post](https://stackoverflow.com/questions/17355902/python-tkinter-binding-mousewheel-to-scrollbar). I know on windows you divide delta with 120 but not sure for raspberry pi. – Saad May 16 '19 at 11:32
  • I am testing it under windows, but I think `yview_moveto()` works similar. I am modifying part of your code to let work as I expected. Thank you so much for your aportation. – David González Blazman May 16 '19 at 11:39
  • 1
    Yes, I tried and it can be done with `yview_moveto()` as well but that will need few more lines of code and a variable that changes accordingly which will not vary on the size of the canvas window. Like if the size of canvas window is big then scrolling could slow down depending upon the modified value you give to `yview_moveto()`. – Saad May 16 '19 at 11:51
  • I've done modifying part of your work. Try my part if you want, and again, thank you so much for your help. – David González Blazman May 16 '19 at 13:46