Last active
April 13, 2020 08:47
-
-
Save RomneyDa/f45f4c520361aff9b858f0f52ac13f20 to your computer and use it in GitHub Desktop.
A simple but robust scrollable frame widget for tkinter. Vertical and horizontal scrollbars appear and disappear automatically. Scroll wheel support included. Adjustable scroll sensitivity with scroll_sensitivity attribute (1-10). Widgets should be added to the "frame" attribute of this object. Frame options not supported (see my ScrollableFrame…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Created on Sun Apr 12 19:16:07 2020 | |
Copyright 2020 Dallin Romney | |
License: CC BY 4.0 | |
""" | |
import tkinter as tk | |
class SimpleScrollableFrame(tk.Frame): | |
def __init__(self, parent, **kwargs): | |
self.parent = parent | |
# Initiate container frame | |
tk.Frame.__init__(self, self.parent) | |
# Create a frame within a scrollable canvas within the container | |
self.canvas = tk.Canvas(self) | |
self.frame = tk.Frame(self.canvas) | |
# (CUSTOM OPTION) Sets scroll sensitivity on a scale of 1 to 10 | |
if 'scroll_sensitivity' in kwargs and kwargs['scroll_sensitivity'] >= 1 and kwargs['scroll_sensitivity'] <= 10: | |
self.scroll_sensitivity = kwargs['scroll_sensitivity'] | |
else: | |
self.scroll_sensitivity = 3 # default scroll sensitivity is 3/10 | |
self.frame.bind("<Configure>", self._frame_changed) | |
self.canvas.bind("<Configure>", self._frame_changed) | |
self.canvas.create_window((0, 0), window = self.frame, anchor = "nw") | |
self.xscrollbar = tk.Scrollbar(self, orient = 'horizontal', command = self.canvas.xview) | |
self.canvas.configure(xscrollcommand = self.xscrollbar.set) | |
self.yscrollbar = tk.Scrollbar(self, orient = 'vertical', command = self.canvas.yview) | |
self.canvas.configure(yscrollcommand = self.yscrollbar.set) | |
# These functions prevent the canvas from scrolling unless the cursor is in it | |
self.canvas.bind('<Enter>', self._enter_frame) | |
self.canvas.bind('<Leave>', self._leave_frame) | |
# Place the canvas onto the container and weigh relevant rows/cols for proper expansion | |
self.canvas.grid(row = 0, column = 0, sticky = tk.S+tk.E+tk.N+tk.W) | |
tk.Grid.rowconfigure(self, 0, weight = 1) | |
tk.Grid.columnconfigure(self, 0, weight = 1) | |
tk.Grid.rowconfigure(self, 1, weight = 0) | |
tk.Grid.columnconfigure(self, 1, weight = 0) | |
def _frame_changed(self, event): | |
self.canvas.configure(scrollregion = self.canvas.bbox('all')) | |
if self.frame.winfo_width() > self.canvas.winfo_width(): | |
self.showX = True | |
self.xscrollbar.grid(row = 1, column = 0, sticky = tk.S+tk.E+tk.N+tk.W) | |
else: | |
self.showX = False | |
self.xscrollbar.grid_forget() | |
if self.frame.winfo_height() > self.canvas.winfo_height(): | |
self.showY = True | |
self.yscrollbar.grid(row = 0, column = 1, sticky = tk.S+tk.E+tk.N+tk.W) | |
else: | |
self.showY = False | |
self.yscrollbar.grid_forget() | |
# Scroll in the Y direction on mouse wheel movement | |
def _on_mouse_wheel(self, event): | |
self.canvas.yview_scroll(-1*int(event.delta*self.scroll_sensitivity/360), "units") | |
# Scroll in the X direction on shift + mouse wheel movement | |
def _on_shift_mouse_wheel(self, event): | |
self.canvas.xview_scroll(-1*int(event.delta*self.scroll_sensitivity/360), "units") | |
# These two fucntions simply prevent scrolling unless the cursor is in the frame | |
def _enter_frame(self, event): | |
if self.showY: self.frame.bind_all("<MouseWheel>", self._on_mouse_wheel) | |
if self.showX: self.frame.bind_all("<Shift-MouseWheel>", self._on_shift_mouse_wheel) | |
def _leave_frame(self, event): | |
if self.showY: self.frame.unbind_all("<MouseWheel>") | |
if self.showX: self.frame.unbind_all("<Shift-MouseWheel>") | |
class MainGUI: | |
def __init__(self, parent): | |
self.container = tk.Frame(parent) | |
# Create four scrollable frames and add them to the container | |
self.SF1 = SimpleScrollableFrame(self.container) | |
self.SF1.grid(row = 0, column = 0, sticky = tk.N+tk.E+tk.S+tk.W) | |
self.SF2 = SimpleScrollableFrame(self.container, scroll_sensitivity = 8) | |
self.SF2.grid(row = 1, column = 0, sticky = tk.N+tk.E+tk.S+tk.W) | |
self.SF3 = SimpleScrollableFrame(self.container) | |
self.SF3.grid(row = 0, column = 1, sticky = tk.N+tk.E+tk.S+tk.W) | |
self.SF4 = SimpleScrollableFrame(self.container) | |
self.SF4.grid(row = 1, column = 1, sticky = tk.N+tk.E+tk.S+tk.W) | |
# Configure all rows and columns present to have the same weight (so they expand with the window) | |
tk.Grid.columnconfigure(parent, 0, weight = 1) | |
tk.Grid.rowconfigure(parent, 0, weight = 1) | |
tk.Grid.columnconfigure(self.container, 0, weight = 1) | |
tk.Grid.columnconfigure(self.container, 1, weight = 1) | |
tk.Grid.rowconfigure(self.container, 0, weight = 1) | |
tk.Grid.rowconfigure(self.container, 1, weight = 1) | |
# Fill each frame with a grid of labels | |
self.lots_of_labels(self.SF1.frame, 'Frame 1', (20, 20)) | |
self.lots_of_labels(self.SF2.frame, 'Frame 2', (40, 10)) | |
self.lots_of_labels(self.SF3.frame, 'Frame 3', (20, 4)) | |
self.lots_of_labels(self.SF4.frame, 'Frame 4', (4, 20)) | |
# Add the frame | |
self.container.grid(row = 0, column = 0, sticky = tk.N+tk.E+tk.S+tk.W) | |
# Populates a frame with a grid of labes of given text and same bg as frame | |
def lots_of_labels(self, parent, text, dim): | |
for row in range(dim[0]): | |
for col in range(dim[1]): | |
tk.Grid.columnconfigure(parent, col, weight = 1) | |
tk.Grid.rowconfigure(parent, row, weight = 1) | |
tk.Label(parent, fg = 'black', text = text, bg = parent.cget('bg')).grid(row = row, column = col) | |
# Create and start app | |
root = tk.Tk() | |
app = MainGUI(root) | |
root.mainloop() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment