Source code for pyDEA.core.gui_modules.scrollable_frame_gui
''' This module contains classes that implement multi-platform
scrolling for frames. Code is mainly borrowed from web and
slightly modified. Unfortunately, I don't have web-page address
any more.
'''
from tkinter import VERTICAL, Y, RIGHT, LEFT, BOTH, N, W
from tkinter.ttk import Frame, Scrollbar
from pyDEA.core.gui_modules.custom_canvas_gui import StyledCanvas
from pyDEA.core.utils.dea_utils import on_canvas_resize
import platform
os = platform.system()
[docs]class MouseWheel(object):
''' This class bind scrolling events to a scroll object.
'''
def __init__(self, root, factor=1):
global os
self.activeArea = None
if type(factor) == int:
self.factor = factor
else:
raise Exception("Factor must be an integer.")
if os == "Linux":
root.bind_all('<4>', self.onMouseWheel, add='+')
root.bind_all('<5>', self.onMouseWheel, add='+')
else:
# Windows and MacOS
root.bind_all("<MouseWheel>", self.onMouseWheel, add='+')
self.root = root
@staticmethod
[docs] def build_function_onMouseWheel(widget, orient, scrollbar, factor=1):
view_command = getattr(widget, orient+'view')
if os == 'Linux':
def onMouseWheel(event):
# scroll moves only when it is not disables
if (len(scrollbar.state()) == 0 or
scrollbar.state()[0] != 'disabled'):
if event.num == 4:
view_command("scroll", (-1)*factor, "units")
elif event.num == 5:
view_command("scroll", factor, "units")
elif os == 'Windows':
def onMouseWheel(event):
if (len(scrollbar.state()) == 0 or
scrollbar.state()[0] != 'disabled'):
view_command("scroll", (-1)*int((event.delta/120)*factor),
"units")
elif os == 'Darwin':
def onMouseWheel(event):
if (len(scrollbar.state()) == 0 or
scrollbar.state()[0] != 'disabled'):
view_command("scroll", event.delta, "units")
return onMouseWheel
[docs] def add_scrolling(self, scrollingArea, xscrollbar=None, yscrollbar=None):
scrollingArea.bind('<Enter>',
lambda event: self.mouseWheel_bind(scrollingArea))
scrollingArea.bind('<Leave>',
lambda event: self.mouseWheel_unbind())
scrollingArea.bind('<Configure>',
lambda event: on_canvas_resize(scrollingArea))
if xscrollbar and not hasattr(xscrollbar, 'onMouseWheel'):
setattr(xscrollbar, 'onMouseWheel',
self.build_function_onMouseWheel(scrollingArea, 'x',
xscrollbar, self.factor))
if yscrollbar and not hasattr(yscrollbar, 'onMouseWheel'):
setattr(yscrollbar, 'onMouseWheel',
self.build_function_onMouseWheel(scrollingArea, 'y',
yscrollbar, self.factor))
active_scrollbar_on_mouse_wheel = yscrollbar or xscrollbar
if active_scrollbar_on_mouse_wheel:
setattr(scrollingArea, 'onMouseWheel',
active_scrollbar_on_mouse_wheel.onMouseWheel)
for scrollbar in (xscrollbar, yscrollbar):
if scrollbar:
scrollbar.bind(
'<Enter>', lambda event,
scrollbar=scrollbar: self.mouseWheel_bind(scrollbar))
scrollbar.bind('<Leave>',
lambda event: self.mouseWheel_unbind())
[docs]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.
Attributes:
canvas (StyledCanvas): canvas.
interior (frame): frame where all widgets are palced.
Args:
parent (Tk object): parent of this widget.
'''
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT, expand=False)
self.canvas = canvas = StyledCanvas(
self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
vscrollbar.config(command=canvas.yview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=N+W)
# 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)
MouseWheel(self).add_scrolling(canvas, yscrollbar=vscrollbar)