-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscrollFrame.py
More file actions
249 lines (189 loc) · 15.2 KB
/
scrollFrame.py
File metadata and controls
249 lines (189 loc) · 15.2 KB
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import tkinter as tk
from tkinter import ttk
# ############################################################################################################################
# To make a scrollable frame, Scrollable frame class first creates a container tkinter frame, then adds another frame in it
# called the Holder frame. Then a canvas is added to the Holder frame.
# Vertical and horizontal scrollbars are added to the holder frames and are attached to scroll the canvas
# Now since widgets CANNOT be placed directly on the canvas another frame is created. This is the frame on which the widgets
# are added.
# After creating the frame, a window is created inside the canvas, and the frame is set as the window for the canvas
# ############################################################################################################################
# MakeFrame class: creates a scrollable frame
class MakeFrame(tk.Frame):
def __init__(self, root, width=None, height=None, container=False, xScroll=True, yScroll=True, bdThickness=0, bdColor=None):
# root (tkinter widget): parent widget
# width, height (integer value): dimensions of the frame
# xScroll (boolean, default: True): allow horizontal Scrollbar
# yScroll (boolean, default: True): allow vertical Scrollbar
# bdThickness (integer value, default: 0): creates a border for the scrollable frame
# bdColor (string value, defalut: "black"): color of the frame border
# calling super class constrcutor and creating a tk.Frame, Holder frame
super().__init__(root, width=width, height=height, highlightthickness=bdThickness)
self.config(highlightbackground=bdColor, highlightcolor=bdColor) # applying container borders
# intializing the parent widget refrence variable and dimensions
self.root = root
self.Width = width
self.Height = height
# creating a canvas to attach a scrollbar to, and place the final frame into
self.canvas = tk.Canvas(self, width=self.master.winfo_reqwidth(), height=self.master.winfo_reqheight())
# add horizontal scrollbar
if(xScroll):
# creating a scrollbar object for horizontal scrolling and attaching it to the canvas by setting
# its command to the xview of the canvas
self.x_scrollbar = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.canvas.xview)
self.x_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) # packing horizontal scrollbar on the holder frame
# add vertical scrollbar
if(yScroll):
# creating a scrollbar object for vertical scrolling and attaching it to the canvas by setting
# its command to the yview of the canvas
self.y_scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.canvas.yview)
self.y_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # packing vertical scrollbar on the holder frame
self.canvas.pack(side=tk.LEFT,fill=tk.BOTH, expand=tk.YES) # packing the canvas in the holder and filling it up
# configuring the canvas to scroll when the scrollbars are moved
if(xScroll):
self.canvas.configure(xscrollcommand=self.x_scrollbar.set) # horizontal scrolling configuration
if(yScroll):
self.canvas.configure(yscrollcommand=self.y_scrollbar.set) # vertical scrolling configuration
# setting a the scrollable area for the canvas to the whole canvas
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion = self.canvas.bbox("all")))
# creating the frame to put the widgets in
self.frame = tk.Frame(self.canvas)
# limitng the y scroll to 0, (this is done because the y scroll kept going in negative)
def limY(event, prnt, chld):
if not (prnt.winfo_rooty()-chld.winfo_rooty()) < 0:
self.canvas.yview_scroll(-1, "units")
# scrolling the frame using the mouse wheel when on Windows OS
def windowWheel(event, prnt, child):
if event.num == 5 or event.delta == -120:
self.canvas.yview_scroll(1, "units")
if event.num == 4 or event.delta == 120:
limY(event, prnt, child)
# binding mouse wheel events to the canvas, frame [for container frames(scrollable frames that contain other scrollable frames)]
# when the cursor enters a frame or a canvas
def eventBind():
# binding to the canvas
self.canvas.bind('<MouseWheel>', lambda event: windowWheel(event, self.frame.master, self.frame)) # MouseWheel event for Windows OS
self.canvas.bind('<Button-4>', lambda event: limY(event, self.frame.master, self.frame)) # up scroll MouseWheel event for Linux
self.canvas.bind('<Button-5>', lambda event: self.canvas.yview_scroll(1, "units")) # down scroll MouseWheel event for Linux
# binding to the frame
self.frame.bind('<MouseWheel>', lambda event: windowWheel(event, self.frame.master, self.frame))
self.frame.bind('<Button-4>', lambda event: limY(event, self.frame.master, self.frame))
self.frame.bind('<Button-5>', lambda event: self.canvas.yview_scroll(1, "units"))
# binding mouse wheel events to the canvas and all the widgets in it(for a non container frame), when the cursor enters a non container frame
def eventBindAll():
self.canvas.bind_all('<MouseWheel>', lambda event: windowWheel(event, self.frame.master, self.frame))
self.canvas.bind_all('<Button-4>', lambda event: limY(event, self.frame.master, self.frame))
self.canvas.bind_all('<Button-5>', lambda event: self.canvas.yview_scroll(1, "units"))
# removing all event bindings when the cursor leaves the scrollable frames, this prevents weird things from happening
# when multiple nested windows are created
def eventRemove():
self.canvas.unbind_all('<MouseWheel>')
self.canvas.unbind_all('<Button-4>')
self.canvas.unbind_all('<Button-5>')
# mouse enter and leave events for the canvas to add remove the mouse wheel event bindings
self.canvas.bind('<Enter>', lambda event: eventBindAll() if not container else eventBind())
self.canvas.bind('<Leave>', lambda event: eventRemove())
# mouse enter and leave events for the frame to add remove the mouse wheel event bindings
#self.frame.bind('<Enter>', lambda event: eventBindAll() if not container else eventBind())
#self.frame.bind('<Leave>', lambda event: eventRemove())
# creating a "virtual" window in the canvas and setting the self.frame as the window
self.canvas.create_window((0,0), window=self.frame, anchor="n")
# when putting widgets in the scrollable frame, the widgets are actually being placed in self.frame
# ScrollableFrame class: creates the frame container and calls MakeFrame class to create a scrollable frame class
class ScrollableFrame(tk.Frame):
def __init__(self,root,width=None,height=None, container=False, xScroll=True, yScroll=True, bdThickness=0, innerbdThickness=0, bdColor="gray25", innerbdColor="black", label=None):
# root (tkinter widget, REQURIED): parent widget
# width, height (integer value, OPTINAL WHEN MAKING A FULL ROOT WINDOW SIZED FRAME): dimensions of the frame
# container (boolean, default: False): if the scrollable frame will contain another scrollable frame,
# for e.g a full window frame that contains other frames
# xScroll (boolean, default: True, OPTIONAL): allow horizontal Scrollbar
# yScroll (boolean, default: True, OPTIONAL): allow vertical Scrollbar
# bdThickness (integer value, default: 0, OPTIONAL): creates a border for the frame container (encloses the label)
# innerbdThickness (integer value, default: 0, OPTIONAL): creates a border for the frame (not enclosing the label)
# bdColor (string value, defalut: "gray25", OPTIONAL): color of the continer border
# innerbdColor (string value, defalut: "black", OPTIONAL): color of the frame border
# label (string value, OPTIONAL): text label for the frame
# calling super class constrcutor and creating a tk.Frame, Container frame
super().__init__(root,width=width,height=height, highlightthickness=bdThickness)
self.config(highlightbackground=bdColor, highlightcolor=bdColor) # applying container borders
# CALLING MakeFrame CLASS TO CREATE A SCROLLABLE FRAME IN THE CONTAINER
scrollFrame = MakeFrame(self, container=container, xScroll=xScroll, yScroll=yScroll, bdThickness=innerbdThickness, bdColor=innerbdColor)
scrollFrame.pack(side=tk.TOP,fill=tk.BOTH,expand=tk.YES, anchor="nw")
self.frame = scrollFrame.frame # frame object on which widgets will be placed,
# IT IS TO NOTE THAT, WIDGETS CAN'T BE PLACED DIRECTLY ON THE ScrollableFrame OBJECT, IT IS TO BE PLACED ON THE
# frame ATTRIBUTE OF THE ScrollableFrame OBJECT LIKE, tk.widget(ScrollableFrameObj.frame)
# creating a label for the scrollable frame
if not label == None:
tk.Label(self,text=label).pack()
# DEMO ##################################################################################################################
if __name__ == "__main__":
def AddManyWidgetsX(frm): # creating 20 button widgets in a single row
lim = 20
for i in range(1,lim):
tk.Button(frm,text=f'{i}').grid(row=0,column=i,padx=20,pady=100)
def AddManyWidgetsY(frm): # creating 20 button widgets in a single column
lim = 20
for i in range(0,lim):
tk.Button(frm,text=f'{i}').grid(row=i,column=0,padx=100,pady=20)
def AddManyWidgetsXY(frm): # creating a grid of 400 button widgets a 20x20 grid
lim = 20
for i in range(lim):
for j in range(lim):
tk.Button(frm,text=f'{i},{j}').grid(row=i,column=j,padx=5,pady=5)
rt = tk.Tk() # root window
rt.title("Scrollable Frames") # window title
rt.geometry("600x600") # initial size of the window
# creating a frame for the whole window
# this frame will act the the root for all the widgets that will be added, it is the same size as the window.
# this gives an effect of a scrollable window
superF = ScrollableFrame(rt, container=True , bdThickness=10, bdColor="maroon") # no size provided since it will expand to
# window size, when ever the window is resized. Since its a frame for the whole window and it will contain some other scrollable frame
# the container attribute is set to True.
superF.pack(fill=tk.BOTH, expand=tk.YES) # adding the scrollable frame to the root window and allowing it to take all the window space
paddingx = 50
# Scrollable frame with only horizonal scroll bar active
# ####################################################################################
# ################################################################################## #
# #
# f1 SCROLLABLE FRAME IS ADDED TO superF.frame AND NOT DIRECTLY TO superF. #
# #
# ANY WIDGET THAT IS TO BE ADDED ON THE SCROLLABLE FRAME SHOULD BE ADDED TO THE #
# "frame" ATTRIBUTE OF THE ScrollableFrame object. #
# #
# ################################################################################## #
# ####################################################################################
f1 = ScrollableFrame(superF.frame, width=300, height=300, innerbdThickness=4, innerbdColor="red", label="X Scrollbar") # f1 is added to superF.frame and not directly on superF
f1.grid(row=0, column=0, padx=paddingx, pady=20)
AddManyWidgetsX(f1.frame)
# Scrollable frame with only vertical scroll bar active
f2 = ScrollableFrame(superF.frame, width=300,height=300, innerbdThickness=4, innerbdColor="green", label="Y Scrollbar") # f2 is added to superF.frame and not directly on superF
f2.grid(row=1, column=0, padx=paddingx, pady=20)
AddManyWidgetsY(f2.frame)
# Scrollable frame with both horizontal and vertical scroll bars active
f3 = ScrollableFrame(superF.frame, width=300,height=300, innerbdThickness=4, innerbdColor="blue", label="X and Y Scrollbar") # f3 is added to superF.frame and not directly on superF
f3.grid(row=2, column=0, padx=paddingx, pady=20)
AddManyWidgetsXY(f3.frame)
# An Empty frame with both horizontal and vertical scroll inactive, and having double borders, one on the frame and other on the container
f4 = ScrollableFrame(superF.frame, 300, 300, bdThickness=4, bdColor="yellow", innerbdThickness=4, innerbdColor="dark green", label="Empty frame with Frame and Container borders.") # f4 is added to superF.frame and not directly on superF
f4.grid(row=0, column=1, padx=paddingx, pady=20)
# A Non empty scrollbar but with inactive horizonal and vertical scrollbars
f5 = ScrollableFrame(superF.frame, width=300,height=300, innerbdThickness=4, innerbdColor="cyan", label="No Scrollbar") # f5 is added to superF.frame and not directly on superF
f5.grid(row=1, column=1, padx=paddingx, pady=20)
tk.Button(f5.frame,text="qwerty").pack(padx=100,pady=100)
# A frame containg another frame, Nested Frames
f6 = ScrollableFrame(superF.frame, 300,300, container=True, innerbdThickness=4, innerbdColor="indian red", label="Nested Frames") # f6 is added to superF.frame and not directly on superF
f6.grid(row=2, column=1, padx=paddingx, pady=20)
ScrollableFrame(f6.frame, 200,200, bdThickness=4, bdColor="gold").pack(side=tk.TOP, anchor="nw", padx=50, pady=50)
# Frame with horizonal scroll disabled all together, providing only a vertical scrollbar
f7 = ScrollableFrame(superF.frame, 300,300, xScroll=False, innerbdThickness=4, innerbdColor="black", label="Only Y Scrollbar visible") # f7 is added to superF.frame and not directly on superF
f7.grid(row=0, column=2, padx=paddingx, pady=20)
AddManyWidgetsY(f7.frame)
# Frame with vertical scroll disabled all together, providing only a horizontal scrollbar
f8 = ScrollableFrame(superF.frame, 300,300, yScroll=False, innerbdThickness=4, innerbdColor="orange", label="Only X Scrollbar visible") # f8 is added to superF.frame and not directly on superF
f8.grid(row=1, column=2, padx=paddingx, pady=20)
AddManyWidgetsX(f8.frame)
# Frame with non rectangular dimension (its possible to size the scrollable frame)
f9 = ScrollableFrame(superF.frame, 200,150, innerbdThickness=4, innerbdColor="dodger blue", label="Custom Sized") # f9 is added to superF.frame and not directly on superF
f9.grid(row=2, column=2, padx=paddingx, pady=20)
AddManyWidgetsXY(f9.frame)
rt.mainloop()