0

below an example code:

import tkinter as tk

def configure(event):
    canvas.configure(scrollregion=canvas.bbox('all'))

root = tk.Tk()

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)

canvas = tk.Canvas(root)
canvas.grid(row=0, column=0, sticky="nswe")

scrollbar = tk.Scrollbar(root, command=canvas.yview)
scrollbar.grid(row=0, column=1, sticky="nse")

canvas.configure(yscrollcommand = scrollbar.set)
frame = tk.Frame(canvas)
canvas.create_window((0,0), window=frame, anchor='nw')

for obj in range (10):
    obj = tk.Label(frame, text="TEST", font="-size 50")
    obj.pack()

canvas.bind('<Configure>', configure)

root.mainloop()

two questons:

  1. to scroll the frame I put it in a canvas. it works but, is it the right practice?

  2. the main question is, how can I scroll the frame using only the mouse wheel when the cursor is placed on it? what is the best practice? keep in mind that I don't wanna see the scroll bar widget.

TurboC
  • 777
  • 1
  • 5
  • 19

1 Answers1

0

Here's an example of my Scrollable_frame class:

class Scrollable_frame(Frame):
def __init__(self, parent, _height, *args, **kw):
    Frame.__init__(self, parent, *args, **kw)
    # Create a canvas object and a vertical scrollbar for scrolling it.
    vscrollbar = ttk.Scrollbar(parent, orient=VERTICAL)
    vscrollbar.grid(row = 0, column = 1, sticky = "nse")
    self.canvas = Canvas(self, bd=0, highlightthickness=0, 
                            height = _height,
                            yscrollcommand=vscrollbar.set)
    self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
    vscrollbar.config(command = self.canvas.yview)
    # Create a frame inside the canvas which will be scrolled with it.
    self.interior = Frame(self.canvas)
    self.interior.bind('<Configure>', self._configure_interior)
    self.canvas.bind('<Configure>', self._configure_canvas)
    self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)
    parent.bind("<MouseWheel>", self._on_mousewheel)

def _configure_interior(self, *args):
    # Update the scrollbars to match the size of the inner frame.
    size = (self.interior.winfo_width(), self.interior.winfo_height())
    self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
    if self.interior.winfo_width() != self.canvas.winfo_width():
        # Update the canvas's width to fit the inner frame.
        self.canvas.config(width = self.interior.winfo_width())
    
def _configure_canvas(self, *args):
    if self.interior.winfo_width() != self.canvas.winfo_width():
        # Update the inner frame's width to fill the canvas.
        self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())

def _on_mousewheel(self, event):
    self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")

You can use it and modify it a little so that there is no scrollbar like this:

class Scrollable_frame(Frame):
def __init__(self, parent, _height, *args, **kw):
    Frame.__init__(self, parent, *args, **kw)
    # Create a canvas object and a vertical scrollbar for scrolling it.
    self.canvas = Canvas(self, bd=0, highlightthickness=0, 
                            height = _height,)
    self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
    # Create a frame inside the canvas which will be scrolled with it.
    self.interior = Frame(self.canvas)
    self.interior.bind('<Configure>', self._configure_interior)
    self.canvas.bind('<Configure>', self._configure_canvas)
    self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)
    parent.bind("<MouseWheel>", self._on_mousewheel)

def _configure_interior(self, *args):
    # Update the scrollbars to match the size of the inner frame.
    size = (self.interior.winfo_width(), self.interior.winfo_height())
    self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
    if self.interior.winfo_width() != self.canvas.winfo_width():
        # Update the canvas's width to fit the inner frame.
        self.canvas.config(width = self.interior.winfo_width())
    
def _configure_canvas(self, *args):
    if self.interior.winfo_width() != self.canvas.winfo_width():
        # Update the inner frame's width to fill the canvas.
        self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())

def _on_mousewheel(self, event):
    self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")

Here's an example of how to use this class properly, copy this code and see how it works:

from tkinter import *

class Scrollable_frame(Frame):
    def __init__(self, parent, _height, *args, **kw):
        Frame.__init__(self, parent, *args, **kw)
        # Create a canvas object and a vertical scrollbar for scrolling it.
        self.canvas = Canvas(self, bd=0, highlightthickness=0, 
                                height = _height,)
        self.canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
        # Create a frame inside the canvas which will be scrolled with it.
        self.interior = Frame(self.canvas)
        self.interior.bind('<Configure>', self._configure_interior)
        self.canvas.bind('<Configure>', self._configure_canvas)
        self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=NW)
        parent.bind("<MouseWheel>", self._on_mousewheel)
        
    def _configure_interior(self, *args):
        # Update the scrollbars to match the size of the inner frame.
        size = (self.interior.winfo_width(), self.interior.winfo_height())
        self.canvas.config(scrollregion=(0, 0, size[0], size[1]))
        if self.interior.winfo_width() != self.canvas.winfo_width():
            # Update the canvas's width to fit the inner frame.
            self.canvas.config(width = self.interior.winfo_width())
            
    def _configure_canvas(self, *args):
        if self.interior.winfo_width() != self.canvas.winfo_width():
            # Update the inner frame's width to fill the canvas.
            self.canvas.itemconfigure(self.interior_id, width=self.canvas.winfo_width())
            
    def _on_mousewheel(self, event):
        self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
        
        
        
root = Tk()
scrollable_frame = Scrollable_frame(root, 500)
scrollable_frame.pack()

for row in range(100):
    for column in range(10):
        Button(scrollable_frame.interior, width = 20, text = row+column).grid(row = row, column = column)
        
root.mainloop()

What is important to see is that when using this class, the widgets you want to place in the frame have to be children of scrollable_frame.interior which is the the frame inside the Canvas that will scroll. If you make your widgets children of scrollable_frame directly it won't work.

For example if you want to put a label in the scrollable frame you'll proceed like:

scrollable_frame = Scrollable_frame(root, 500)
scrollable_frame.pack()
lab = Label(scrollable_frame.interior, text = "")
lab.pack()

And not:

scrollable_frame = Scrollable_frame(root, 500)
scrollable_frame.pack()
lab = Label(scrollable_frame, text = "")
lab.pack()