Scrolling frame in Tkinter with mouse wheel support for different platforms.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | # Version: 0.22
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: ", "61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
try:
from Tkinter import Canvas, Frame
from ttk import Scrollbar
from Tkconstants import *
except ImportError:
from tkinter import Canvas, Frame
from tkinter.ttk import Scrollbar
from tkinter.constants import *
import platform
OS = platform.system()
class Mousewheel_Support(object):
# implemetation of singleton pattern
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance
def __init__(self, root, horizontal_factor = 2, vertical_factor=2):
self._active_area = None
if isinstance(horizontal_factor, int):
self.horizontal_factor = horizontal_factor
else:
raise Exception("Vertical factor must be an integer.")
if isinstance(vertical_factor, int):
self.vertical_factor = vertical_factor
else:
raise Exception("Horizontal factor must be an integer.")
if OS == "Linux" :
root.bind_all('<4>', self._on_mousewheel, add='+')
root.bind_all('<5>', self._on_mousewheel, add='+')
else:
# Windows and MacOS
root.bind_all("<MouseWheel>", self._on_mousewheel, add='+')
def _on_mousewheel(self,event):
if self._active_area:
self._active_area.onMouseWheel(event)
def _mousewheel_bind(self, widget):
self._active_area = widget
def _mousewheel_unbind(self):
self._active_area = None
def add_support_to(self, widget=None, xscrollbar=None, yscrollbar=None, what="units", horizontal_factor=None, vertical_factor=None):
if xscrollbar is None and yscrollbar is None:
return
if xscrollbar is not None:
horizontal_factor = horizontal_factor or self.horizontal_factor
xscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget,'x', self.horizontal_factor, what)
xscrollbar.bind('<Enter>', lambda event, scrollbar=xscrollbar: self._mousewheel_bind(scrollbar) )
xscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind())
if yscrollbar is not None:
vertical_factor = vertical_factor or self.vertical_factor
yscrollbar.onMouseWheel = self._make_mouse_wheel_handler(widget,'y', self.vertical_factor, what)
yscrollbar.bind('<Enter>', lambda event, scrollbar=yscrollbar: self._mousewheel_bind(scrollbar) )
yscrollbar.bind('<Leave>', lambda event: self._mousewheel_unbind())
main_scrollbar = yscrollbar if yscrollbar is not None else xscrollbar
if widget is not None:
if isinstance(widget, list) or isinstance(widget, tuple):
list_of_widgets = widget
for widget in list_of_widgets:
widget.bind('<Enter>',lambda event: self._mousewheel_bind(widget))
widget.bind('<Leave>', lambda event: self._mousewheel_unbind())
widget.onMouseWheel = main_scrollbar.onMouseWheel
else:
widget.bind('<Enter>',lambda event: self._mousewheel_bind(widget))
widget.bind('<Leave>', lambda event: self._mousewheel_unbind())
widget.onMouseWheel = main_scrollbar.onMouseWheel
@staticmethod
def _make_mouse_wheel_handler(widget, orient, factor = 1, what="units"):
view_command = getattr(widget, orient+'view')
if OS == 'Linux':
def onMouseWheel(event):
if event.num == 4:
view_command("scroll",(-1)*factor, what)
elif event.num == 5:
view_command("scroll",factor, what)
elif OS == 'Windows':
def onMouseWheel(event):
view_command("scroll",(-1)*int((event.delta/120)*factor), what)
elif OS == 'Darwin':
def onMouseWheel(event):
view_command("scroll",event.delta, what)
return onMouseWheel
class Scrolling_Area(Frame, object):
def __init__(self, master, width=None, anchor=N, height=None, mousewheel_speed = 2, scroll_horizontally=True, xscrollbar=None, scroll_vertically=True, yscrollbar=None, background=None, inner_frame=Frame, **kw):
Frame.__init__(self, master, class_="Scrolling_Area", background=background)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self._width = width
self._height = height
self.canvas = Canvas(self, background=background, highlightthickness=0, width=width, height=height)
self.canvas.grid(row=0, column=0, sticky=N+E+W+S)
if scroll_vertically:
if yscrollbar is not None:
self.yscrollbar = yscrollbar
else:
self.yscrollbar = Scrollbar(self, orient=VERTICAL)
self.yscrollbar.grid(row=0, column=1,sticky=N+S)
self.canvas.configure(yscrollcommand=self.yscrollbar.set)
self.yscrollbar['command']=self.canvas.yview
else:
self.yscrollbar = None
if scroll_horizontally:
if xscrollbar is not None:
self.xscrollbar = xscrollbar
else:
self.xscrollbar = Scrollbar(self, orient=HORIZONTAL)
self.xscrollbar.grid(row=1, column=0, sticky=E+W)
self.canvas.configure(xscrollcommand=self.xscrollbar.set)
self.xscrollbar['command']=self.canvas.xview
else:
self.xscrollbar = None
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.innerframe = inner_frame(self.canvas, **kw)
self.innerframe.pack(anchor=anchor)
self.canvas.create_window(0, 0, window=self.innerframe, anchor='nw', tags="inner_frame")
self.canvas.bind('<Configure>', self._on_canvas_configure)
Mousewheel_Support(self).add_support_to(self.canvas, xscrollbar=self.xscrollbar, yscrollbar=self.yscrollbar)
@property
def width(self):
return self.canvas.winfo_width()
@width.setter
def width(self, width):
self.canvas.configure(width= width)
@property
def height(self):
return self.canvas.winfo_height()
@height.setter
def height(self, height):
self.canvas.configure(height = height)
def set_size(self, width, height):
self.canvas.configure(width=width, height = height)
def _on_canvas_configure(self, event):
width = max(self.innerframe.winfo_reqwidth(), event.width)
height = max(self.innerframe.winfo_reqheight(), event.height)
self.canvas.configure(scrollregion="0 0 %s %s" % (width, height))
self.canvas.itemconfigure("inner_frame", width=width, height=height)
def update_viewport(self):
self.update()
window_width = self.innerframe.winfo_reqwidth()
window_height = self.innerframe.winfo_reqheight()
if self._width is None:
canvas_width = window_width
else:
canvas_width = min(self._width, window_width)
if self._height is None:
canvas_height = window_height
else:
canvas_height = min(self._height, window_height)
self.canvas.configure(scrollregion="0 0 %s %s" % (window_width, window_height), width=canvas_width, height=canvas_height)
self.canvas.itemconfigure("inner_frame", width=window_width, height=window_height)
if __name__== '__main__':
try:
from Tkinter import Tk, Label
except ImportError:
from tkinter import Tk, Label
root = Tk()
scrolling_area = Scrolling_Area(root)
scrolling_area.pack(expand=1, fill=BOTH)
for i in range(20):
rowFrame = Frame(scrolling_area.innerframe)
rowFrame.pack()
for j in range(8):
Label(rowFrame, text="Label %s, %s" % (str(i), str(j))).pack(side="left")
root.mainloop()
|